I've recently started client work on a new Rails 7 application and while I'm impressed always impressed by the developer experience that the Rails community has achieved, one thing that I instantly missed from my recent work with Phoenix was the idea of contexts.
What are Phoenix contexts
If you're new to Phoenix, here's a short explanation: Like Rails, Phoenix has an opinion about how your code should be structured.
First of all, Phoenix separates between HTTP-related code that lives in
lib/myapp_web and your actual application logic that lives in
lib/myapp . Simply put: your controllers and views go into the
lib/myapp_web directory and thus away from everything else.
On top of that, Phoenix goes on and talks about contexts, which fundamentally means that your code should be organized around the problem domain: Related code should be close together and boundaries between unrelated code should be explicit.
There's a lot more that can be said about this idea and I feel like I should write a separate post about why it's so important to me, but I'll focus on how to implement it in Rails for this article.
Phoenix Contexts in Rails land
The first step is easy: We just create an
app/contexts directory and put code into a new module there. Our controller then calls the module.
# app/contexts/blog.rb module Blog module_function def publish_article(article_id) Article.find!(article_id).update(published: true) # Notify your subscribers here etc. end end # app/controllers/admin/articles_controller.rb class Admin::ArticlesController < ApplicationController def publish if Blog.publish_article(params[:article_id]) # Success! else # Ouch, render an error. end end end
Fairly straightforward. But our models still live in the
app/models directory and outside of the context, so let's fix that.
# app/contexts/blog/article.rb class Blog::Article < ApplicationRecord end
Nice! At this point, Rails still hooks these models up to the
articles table - no mention of our context in that table name! Let's fix this to avoid any conflicts if the same model name is used in more than one context:
# app/contexts/blog.rb module Blog module_function def table_name_prefix 'blog_' end # ... end
From that point on, all models in the
Blog module will have their table names prefixed:
If you followed along with your actual code, you'll need a migration to rename your existing table.
And that's it! For now, I'm quite happy with how this turned out. If you do this, I'd be happy to hear about your experience. I'm very interested in seeing this applied to a larger code base.