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