Structuring Rails applications
There’ve been a lot of discussions recently about applying Object Oriented Programming in Rails applications, how ActiveRecord callbacks make testing painful and how Rails makes it hard to do OOP the right way. Is it really true? Rails makes everything easy - you can easily write terrible code, which will be a maintenance nightmare, but is also easy to apply good practices, especially with available gems. What is the good way then to extract logic in Rails applications and the best place to put it?
Standard structure
By default we have four directories where we can put our code: models, views, controllers and helpers. The basic explanation of them is following:
- Models - dealing with database, validations, repository, persistence.
- Controllers - parsing requests, sessions, rendering views etc.
- Views - user interface, data presenting.
- Helpers - place to put reusable methods for views.
Does it mean these are the only places where you can put your code? No! It’s just a default structure with basic parts of your application. You can easily extend it by creating new directories and then adding them to autoload paths, e.g.:
config.autoload_paths += Dir["#{AppName::Application.config.root}/app/usecases"]
How about lib directory?
Unless you are extracting something to a gem, I would discourage you from putting anything there. Some developers put in the lib all the code that doesn’t belong to models/controllers/helpers, but if something is part of your application why not add it to the app directory?
Skinny controllers, fat models
The design mantra for the last few years in Rails was to move logic from controllers to models. Well, it’s partially a good thing - skinny controllers are clear, easy to test and maintain, but why should models be like 1000 lines of code with tens of responsibilities (and no, everything concerning User is not a single responsibility)? It makes models in most cases the most problematic layer in Rails applications. They often include a lot of unrelated methods, conditional validations, cross-models operations, callbacks etc. - all the things that ActiveRecord makes really easy to add. So, the important question is:
Is ActiveRecord evil?
ActiveRecord is a wonderful ORM, which indeed makes everythings easy. But with great power comes great responsibility. Think about callbacks: you can add new logic in a blink of an eye and it does the job. So you keep adding other callbacks until you discover that there are some cases where you don’t want them to be executed. So you add conditionals or bypass-like methods. After some time, the logic in callbacks is so complexed that you waste few hours with other developers to understand why something was executed at all. If other developers join the project, it is even harder for them to understand what model class really does. But it isn’t the worst part. Think about some gems and their integration with Rails. Often it means extending models with another callbacks. Here are some real world problems:
Imagine the situation where you need to implement the ability for admin to register other users and the application uses Devise gem for authentication. Furthermore, the Confirmable
module is included in the User
class. Accidentally, you forgot to pass a date to :confirmed_at
field or use confirm!
method. What happens then? The confirmation email is sent to all registered users. Oops, welcome to the wonderful world of callbacks. I don’t want to criticize Devise, because it is a great gem, which I use in almost every project, but I am not sure if sending emails being directly coupled to the model layer was a good design decision. In docs you can read about the skip_confirmation!
method and if you use external gem for such a critical part of your application, you should read the entire docs, but it can be really suprising, that the confirmation email is sent in all cases, even if you create user from Rails Console. Oh, and guess what happens if you want to change one’s email from the console and reconfirmable
option is enabled? The confirmation email is sent… Well, remember about reading docs, especially when the gem may include callbacks.
Any other examples? Of course. So, you want to implement a generic forum. There is a gem called Forem, which provides you with basic forum functionality. And one day you want to change state of some posts to be approved. So you enter Rails Console and using update
or update_attributes
you perform the update. What happens then? There is a callback in Forem::Post
model:
after_save :email_topic_subscribers, if: Proc.new { |p| p.approved? && !p.notified? }
A lot of emails have been just sent! That was really unexpected. If you are used to skipping callbacks in such situatons by using update_columns
method or any other way, you are safe, but callbacks are so tighly coupled to models, that you cannot be sure if you are safe, even in console. What is the conslusion? Beware of callbacks. And read docs and code of the gems you use :).
So, how to avoid unexpected situations and have clean and understandable code?
Structuring Rails applications - my way
I’ve been working on several projects and the best solution in terms of maintenance, understandability and ease of testing is the following:
Models
I use models for: factory methods, queries, scopes, general validations, which are always applicable e.g. presence and uniqueness validations for fields with null: false
and / or unique: true
constraints, also “domain constraints”, especially with many-to-many associations. The example of domain constraint is assigning users, who belong to the same organization, to some subgroups - assigning users from other organizations is prohibited. Putting this kind of logic in controllers’ before_filters or permission classes is not enough for me, I want to ensure the integrity of the data and make it impossible to bypass this restriction. Here is an example: we have User
model and Group
model and the many-to-many relationship between them, which is established by has_many , through:
macro with GroupsUsers
join model. Also, users and groups belong to Organization
. Here is a validation for creating relation in join model:
class GroupsUsers < ActiveRecord::Base
belongs_to :group
belongs_to :user
validates :group, presence: true
validates :user, presence: true
validate :ensure_valid_organization
private
def ensure_valid_organization
if user.organization != group.organization
raise InvalidOrganizationError, "User's organization does not match Group's organization."
end
end
end
Other example of domain constraint is validation of inclusion.
Sometimes I do use callbacks. The basic rule when applying callbacks for me is to use them for processing some data, which should always take place. In most cases, it is limited to three callbacks:
before_save :create_parameterized_name
after_save :calculate_statistics
after_destroy :calculate_statistics
Pretty easy to understand: everytime the record is saved, I want to have parameterized form of name, e.g. for a slug. Also, after the record is saved or destroyed, I want the statistics to be updated. For instance, in real estate search engine application, investment has many apartments and I want to keep track of total count of apartments, average price, minimum and maximum price etc. without performing calculations each time. And one more callback concerning associations: dependent: :destroy
option. It is pretty useful and keeps the integrity of data, but you have to be sure when using it. If you think for a moment, these are “low-level” callbacks - they don’t concern business logic and are something that you would like to have on a database level. It can be also achieved by using trigger functions in the database, but Rails callbacks are much easier to handle.
This is not the only right way for using callbacks, if you are absolutely sure that something should really be executed as callback, feel free to use them, but please, don’t send notifications, don’t connect with Facebook API or download files from Dropbox in callbacks. You will be safe, the logic will be easy to understand and testing will be much easier.
Sometimes I use model as an interface for some service objects / usecases / whatever you call it. Here is an example:
class Article < ActiveRecord::Base
def publish(publisher = DefaultPublisher)
publisher.new(self).publish
end
end
It is a great way to have a flexibility in publishing articles - by dependency injection we can control, how it is being published - just pass publisher class as a strategy. Having default publisher makes it easy to use: just call article.publish
. Also, calling article.publish
in e.g. controller feels much better than calling DefaultPublisher(article).publish
. If you have very simple logic, like this one:
def publish
self.published_at = DateTime.now if self.published_at.blank?
self.save
end
don’t bother with extracting it to external class, it would be pointless.
Controllers
Everything related to parsing requests, sessions, rendering templates, redirecting and flash messages should be put in controllers. What about application logic? In most cases it should be limited to a control-flow, for example:
class ArticlesController < ApplicationController
def create
@article = Article.find(params[:id])
if @article.save
flash[:notice] = "You have successfully created an article."
redirect_to articles_path
else
render :new
end
end
end
Depending on the action, it could be much more complex. Consider the following:
class OrdersController < ApplicationController
def buy
order = Order.new(order_params)
order_proccesor = BooksOrderProcessor.new(order, current_user)
begin
order_processor.pay
rescue BooksOrderProcessor::InsufficientAmount
flash.now[:error] = "Not enough books are available."
render :new
rescue BooksOrderProcessor::InsufficientFounds
flash[:error] = "You have run out of funds."
redirect_to profile_path(current_user)
else
flash[:notice] = "You have bought a book."
redirect_to books_path
end
end
private
def order_params
params.require(:order).permit!
end
end
It is still good, such control-flow can take place in a controller, but order processing logic cannot. But what should be done with creating articles and sending notification or logging action? If it is only one additional line of code with method call like Tracker.register("create", @article)
or NewArticleNotfier.delay.notify
, for example:
class ArticlesController < ApplicationController
def create
@article = Article.find(params[:id])
if @article.save
NewArticleNotfier.delay.notify
flash[:notice] = "You have successfully created an article."
redirect_to articles_path
else
render :new
end
end
end
don’t extract it to usecase or service object, it is ok to keep it in a controller.
Cells
You have probably had many situations with setting up the same instance variables in several controller actions for some widgets like: tags cloud, recent articles, recent comments, top visited articles etc. and it can be really inconvenient. Fortunately, there’s a great gem: Cells, which are like mini controllers. Consider the following:
class ArticlesCell < Cell::Rails
def top_visited
@articles = Article.top_visited
render
end
end
In cells/articles/top_visited.html.haml/erb you put the related markup and invoke cells from views by:
= render_cell :articles, :top_five
Another great thing about cells is that you can inject a dependency:
= render_cell :widgets, :newsletter, newsletter_form: @newsletter_form
You can easily create a widget with newsletter submission form with enabled remote: true option and then render error messages or notice message that the email has been submitted.
Helpers
In most cases I use helpers to extract some things that aren’t related to any particular model - rendering flash messages, titles, html templates etc., so nothing really fancy. Everything else should be extracted to the presenters/decorators. The good example of helper is the following:
def section_marker(text)
content_tag(:h2, class: "section-marker") do
"<i class='icon-align-left'></i> #{text}".html_safe
end
end
Before each section I had to insert header with nested icon and some text, so instead of writing the same thing several times, I extracted it to a helper, which is much cleaner. It doesn’t belong to any model, so helper is a good place to put this kind of code. If you use Boostrap a lot, you may consider writing modal_activator
method:
def modal_activator(text, path, options)
link_to(text, path, options.merge(role: "button", "data-toggle" => "modal"))
end
Presenters/Decorators
Helpers aren’t the best place to extract logic related to models - the code in helpers tends to be messy and difficult to maintain and test. The good solution would be to use Presenters - objects that encapsulate presentation logic in a neat way. There’s a gem that is perfect for this kind of problems: Draper. Just create a decorator class, like UserDecorator
:
class UserDecorator < Draper::Base
delegate_all
decorates :user
def link_to_edit
h.link_to("Edit", user_path(model))
end
def full_name
if model.name.present? and model.surname.present?
"#{model.name} #{model.surname}"
end
end
def display
full_name || model.email
end
end
Looks great! You don’t have to keep presentation logic in helpers or even worse in models.
You have an access to Rails helpers via h, also all method calls are delegated to model if it isn’t implemented in a decorator. To decorate model just use decorate
method:
@user = User.find(params[:id]).decorate
You can also decorate collection by using decorate
method.
Forms
Imagine a situation where you need a form concerning more than one model. Also, some conditional validation is required. What would you do? Probably use nested attributes and add some complex validations, which would make model messy and maybe cause some bugs. There’s much better way to do it: use form object and Reform gem. Then you can create following objects:
require 'reform/form/coercion'
require 'reform/rails'
class UserRegistrationForm < Reform::Form
include DSL
include Reform::Form::ActiveRecord
include Reform::Form::Coercion
properties [:email, :name, :country_id], on: :user
property :birth_date, on: :user_profile, type: Date
properties [:age, :photo], on: :user_profile
validates :email, :photo, :birth_date, :name, :age, presence: true
validates :age, numericality: true
validates :email, uniqueness: { case_sensitive: false }
model :user
def countries_collection
Country.all.pluck(:id, :name)
end
def persist!(params)
if validate(params)
begin
save do |data, map|
UserRegistration.new.register!(
User.new(map[:user]),
UserProfile.new(map[:user_profile])
)
end
rescue UserRegistration::RegistrationFailed
false
end
end
end
end
By using Reform gem, you can easily create clean form objects, which would deal with validations, coercions (thanks to Virtus) and persisting data concerning multiple models. Also, you can put some form interface logic here - consider countries_collection
method: instead of passing: collection: Country.all.pluck(:id, :name)
to the select field, you can just pass form_object.countries_collection
. This example is trivial, but if you had some filtering and ordering logic needed to display collection, then it would be great way to keep everything clean. Using form objects doesn’t change control-flow in controllers:
class UsersController < ApplicationController
def new
@registration = registration_form
end
def create
@registration = registration_form
if @registration.persist!(user_params)
redirect_to root_path
else
render :new
end
end
private
def registration_form
UserRegistrationForm.new(user: User.new, user_profile: UserProfile.new)
end
def user_params
params.require(:user).permit!
end
end
Form objects are also great way to extract search forms and logic related to filtering. Reform can deal with has_many
associations and nested collections, so it is pretty powerful. However, there are some cases where you would still want to use accepts_nested_attributes_for
- when you need funcionality provided by nested_form gem. It is not really clean to use accepts_nested_attributes_for
macro, but the benefits are great. In other cases, form object is a way to go.
Uploaders
For file uploading I use Carrierwave where the uploaders’ configuration is kept in the /uploaders directory. That’s a really good approach, because thumb-processing strategy etc. has nothing to do with ActiveRecord model, so there’s no reason to keep this kind of information there. Here is an example of general uploader:
class ApplicationUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
def store_dir
"system/#{Rails.env}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(jpg jpeg gif png pdf tiff tif eps bmp ps)
end
private
def rgbify
begin
manipulate! do |img|
img.colorspace "sRGB"
img
end
end
end
end
And example of some images uploader:
class LogoUploader < ApplicationUploader
def filename
"original.#{model.logo.file.extension}" if original_filename
end
version :thumb do
process :rgbify
process :resize_to_fill => [100, 100]
process :convert => 'jpg'
def full_filename(for_file)
"thumb.jpg"
end
end
end
Looks great and doesn’t clutter models with unrelated image-processing configuration options.
Services
You may expect that in /services directory so called service-objects would be placed. Well, not really. I prefer to call them (service objects) usecases and in /services I put some wrappers concerning third party APIs. In one application I’ve been working on, I had to deal with Google Calendar integration and the adapter layer for performing requests to GC was a service, for instance:
module GoogleCalendar
class Calendars
attr_reader :client
def initialize(client)
@client = client
end
#some other methods
def patch(calendar_id, calendar_data)
client.execute(api_method: client.service.calendars.patch), body: calendar_data,
parameters: { "calendarId" => calendar_id }, headers: {'Content-Type' => 'application/json'})
end
end
end
In smaller applications you won’t probably need this layer, otherwise it is a neat way to separate services from the rest of the application.
Usecases
This is a place where most of the business logic should be extracted - almost everything that would be in models, according to “skinny controllers, far models”. The benefits of using usecase objects / service objects are great - they area easy to undestand and maintain, testing is simple and don’t lead to unexpected actions (like the ones I pointed out in callbacks). Let’s take a look again at the user registration process from form object, the implementation of UserRegistration
could be following:
class UserRegistration
attr_reader :admin_notifier, :external_service_notifier
def initialize(notifiers = {})
@admin_notifier = notifiers.fetch(:admin_notifier) { AdminNotifier.new }
@external_service_notifier = notifiers.fetch(:external_service_notifier) { ExternalServiceNotifier.new }
end
def register!(user, profile)
profile.user = user
ActiveRecord::Base.transaction do
begin
user.save!
profile.save!
rescue
raise UserRegistration::RegistrationFailed
end
end
notify_admin
notify_external_service
end
def notify_admin
admin_notifier.notify(user)
end
def notify_external_service
external_service_notifier.new_user(user, profile)
end
class RegistrationFailed < Exception
end
end
Let’s discuss some design choices here: in the constructor I added a possibility to inject some notifiers and provide reasonable defaults using Hash#fetch
method to avoid nils. In register
method I wrap the persistence process in ActiveRecord::Base.transaction
, to ensure that user is not created without the profile if any error occurs (notice the bang methods), if it fails, the exception is raised. Then, some notifiers are called, one is a mailer, the other one connects with an external service and does some stuff. They should be executed asynchronously, in Delayed Job, Resque or Sidekiq to make sure they are completed if failure occurs - there might be a temporary problem with connecting to Gmail, Facebook, Twitter etc. but it’s not a reason for an entire registration process to fail.
Policies
Policy objects are a bit special - it’s a decorator, but Draper decorators are not the best place to put them, because they concern presentation logic. Models, except small applications, are also wrong place to write policy logic as they encapsulate important domain logic which can be quite complex. So it is a good idea to create separate objects - policy objects. Depending on the size of your application, you may have several policy objects or just one for a model, here is an example:
class InvestmentPromotionPolicy
attr_reader :investment, :clock
def initialize(investment, clock = DateTime)
@investment = investment
@clock = clock
end
def promoted?
valid_promotion_date? and owner_promotable?
end
def owner_promotable?
investment.owner.active_for_promotion?
end
def promotion_status
case
when promoted?
:promoted
when valid_promotion_date? and !owner_promotable?
:pending_for_promotion
else
:not_promoted
end
end
private
def valid_promotion_date?
(investment.promotion_starts_at..investment.promotion_ends_at).cover? clock.now
end
end
In the constructor I pass an investment and the clock, which by default is DateTime
. I had some issues with concept of time, especially in policy objects where I had to implement own Clock
, because DateTime
was not sufficient, so just to be on a safe side, I add a possibility for a dependency injection. It doesn’t increase a complexity of the class and I wouldn’t consider it as a premature optimization. Then we have some methods that encapsulate promotion logic and one that returns proper status. You will probably use policy objects in many cases - e.g. promotion_status
method looks like it could be used in a presenter, which would display proper content, depending on the returned status and the promoted?
method could be used in the usecases or in a model class method that would return promoted investments. You can use policy objects in many ways: inject into a model and delegate method calls:
class Investment < ActiveRecord::Base
delegate :promoted?, :owner_promotable?, :promotion_status to: :promotion_policy
# some methods
private
def promotion_policy
@promotion_policy ||= InvestmentPromotionPolicy.new(self)
end
end
In the same way you can use them in presenters if you don’t want to keep it in the model layer. They can also be injected as a dependency into a usecase. Choose whatever suits you better and the complexity of the application.
Value objects
In many applications you may encounter a situation where a concept deserves own abstraction and whose equality isn’t based on identity but on the value, some examples would be Ruby’s Date
or Money
concept, typical for e-commerce applications. Extraction to a value object (or domain model) is a great convenience. Imagine a situation where you have a hierarchy of roles of users - you will probably want to compare the roles if one is “greater” than another to decide, if some action can be performed - the RoleRank
would solve this problem:
class RoleRank
include Comparable
ROLES = %w(superadmin admin junior_admin user guest)
attr_reader :value
def initialize(role)
check_role_existence(role)
@value = value
end
def <=>(other_role)
ROLES.index(other_role.value) <=> ROLES.index(value)
end
def to_s
value
end
class InvalidRole < Exception
end
private
def check_role_existence(specified_role)
unless ROLES.include? specified_role
raise RoleRank::InvalidRole, "Specified Role Doesn't Exist"
end
end
end
Looks great. The Comparable
module and <=>
takes care of implementing comparison operators. You can add a method with ranked role to the user model:
class User < ActiveRecord::Base
def role
@role ||= RoleRank.new(permission_level)
end
end
and then compare users’ roles:
user = User.new(permission_level: "superadmin")
other_user = User.new(permission_level: "admin")
user.role > other_user.role => true
What about Observers and Concerns?
There are two more ways to extract logic “the standard way”: observers (removed from Rails 4) and concerns. Observers aren’t really different from callbacks, except they are more difficult to deal with - you will probably forget that you’ve used them and debugging or following the application logic would be even harder than in callbacks and I’m glad they were removed. And the new thing in Rails 4: concerns. They are great, expecially for shared behaviour. When you need to parameterize field name in several models, you can extract it to a module in concerns, and then include Parameterizable concern, some custom finders and factory methods also can be extracted into concerns. If you use the same before_filters in more than controller, extracting them to the concerns would be a good idea. In Presenters, such concern as Presenters::Publishable
would be beneficial when you have articles, posts and some other models that act in a similar way, so introducing concerns and encouragement to extract similar behaviour was definitely a good idea.
Any other application layers?
Depending on your application, you may introduce additional layers like JSON representations of models, jobs that should be done in a background or XML importers, but they can be considered as presentation logic (jsons) or usecases / services (importers, background jobs). If XML importers are important part of your business logic, then maybe extracting them from usecases and treating in a special way would be beneficial.
Wrapping up
The concepts mentioned here might not be popular among some developers and be considered as over-engineering. When writing simple CMS, introducing services, form objects etc. probably is a premature optimization and models / contollers / helpers and maybe presenters would be sufficient, including writing business logic in ActiveRecord callbacks. But in more complex application applying some OOP techniques can save you (and other developers you work with) from a lot of problems.
Further reading
- 7 Patterns to Refactor Fat ActiveRecord Models
- The Problem With Rails Callbacks
- The Secret to Rails OO Design
- Objects On Rails
- Making ActiveRecord Models Thin
- Services - what are they and why we need them?