Rails Architecture with Service and Decorator

Architecture

After we start to develop the application, we continue to develop and the application become bigger. Then you may encounter to the problem like “fat controller” or “fat model”.

If you need to put the business logic, but it’s not related to any model or related to more than 2 models, where are you gonna locate it? And if the models got fat, how are you gonna make it slim? I’m gonna introduce my architecture how I separate and keep clean.

Standard Rails

  • app/controllers (Controller)
  • app/models (Model)
  • app/views (View)

Ruby on Rails is based on MVC. You can separate the codes to the correct folders and easy to understand. But what are you going to do when the controllers or the models becomes too fat. What if we divide more correct classes?

Service Class

  • app/services

While developing the application, you’ll encounter the situation like communicate with the external API. You can put the codes into controllers but isn’t it clearer to create an another class for communicate with the API and make a public method to control? In that case, It’s better to create the “Service” class, located under the app/ folder.

For example, when I need to communicate with WordPress REST API, I’d create the WordpressService Class under the app/services. So you just call the public methods of WordPressService class and your controller is still clean!

After you create the ‘services’ folder under ‘app’, don’t forget to add the ‘app/services’ to the autoload paths.

# config/application.rb
config.autoload_paths << "#{config.root}/app/services"

And put your actual codes into your new service.

# app/services/wordpress_service
class WordpressService
  class << self
    def get_posts(url)
      # your codes
    end
  end
end

Just call the method from the controller and that’s it. Clean.

# app/controllers/top_controller.rb
def index
  posts = WordpressService.get_posts('http://example.com')
end

1 more good thing is it’s easier to test. As we divide the transaction to one class, we can test just input the params and check the outputs. easy.

Decorator Class

  • app/decorators

I started to feel I want to separate the methods which is used only in views. Some methods defined in models has the business logic but some are not. Those methods which does not have business logic and used only in views can be separated and put into decorators.

To separate to decorators, I recommend to use ActiveDecorator gem.

# app/decorators/user_decorator.rb
module UserDecorator
  def full_name
    "#{first_name} #{last_name}"
  end
end

And you can call this method from view files.

# app/views/users/index.html.erb
<% @users.each  do |user| %>
  <%= user.full_name %><br>
<% end  %>

Yes it’s very convenient. You can see more detail at ActiveDecorator github page.

Finally…

  • app/controllers (Controller)
  • app/models (Model)
  • app/views (View)
  • app/services (Service)
  • app/decorators (Decorator)

For now, I feel like this is not too big but not too small, good architecture. If I find better way, I’m willing to change this.

I created the Rails5 Boilerplate based on this architecture, please take a look and try it!

Hope this article could be a help for someone. Thanks!

@takp