Your website is gaining traction, and you are growing rapidly. Ruby/Rails is your programming language of choice. Your team is bigger and you’ve given up on “fat models, skinny controllers” as a design style for your Rails apps. However, you still don’t want to abandon using Rails.

No problem. Today, we’ll discuss how to use OOP’s best practices to make your code cleaner, more isolated, and more decoupled.

Is Your App Worth Refactoring?

Let’s start by looking at how you should decide if your app is a good candidate for refactoring.

Here is a list of metrics and questions I usually ask myself to determine whether or not my code needs refactoring.

  • Slow unit tests. PORO unit tests usually run fast with well-isolated code, so slow running tests can often be an indicator of a bad design and overly-coupled responsibilities.
  • FAT models or controllers. A model or controller with more than 200 lines of code (LOC) is generally a good candidate for refactoring.
  • Excessively large code base. If you have ERB/HTML/HAML with more than 30,000 LOC or Ruby source code (without GEMs ) with more than 50,000 LOC, there’s a good chance you should refactor.

Try using something like this to find out how many lines of Ruby source code you have:

find app -iname "*.rb" -type f -exec cat {} \;| wc -l

This command will search through all the files with .rb extension (ruby files) in the /app folder, and print out the number of lines. Please note that this number is only approximate since comment lines will be included in these totals.

Another more precise and more informative option is to use the Rails rake task stats which outputs a quick summary of lines of code, number of classes, number of methods, the ratio of methods to classes, and the ratio of lines of code per method:

 bundle exec rake stats                                                                       
+----------------------+-------+-----+-------+---------+-----+-------+
| Name                 | Lines | LOC | Class | Methods | M/C | LOC/M |
+----------------------+-------+-----+-------+---------+-----+-------+
| Controllers          |   195 | 153 |     6 |      18 |   3 |     6 |
| Helpers              |    14 |  13 |     0 |       2 |   0 |     4 |
| Models               |   120 |  84 |     5 |      12 |   2 |     5 |
| Mailers              |     0 |   0 |     0 |       0 |   0 |     0 |
| Javascripts          |    45 |  12 |     0 |       3 |   0 |     2 |
| Libraries            |     0 |   0 |     0 |       0 |   0 |     0 |
| Controller specs     |   106 |  75 |     0 |       0 |   0 |     0 |
| Helper specs         |    15 |   4 |     0 |       0 |   0 |     0 |
| Model specs          |   238 | 182 |     0 |       0 |   0 |     0 |
| Request specs        |   699 | 489 |     0 |      14 |   0 |    32 |
| Routing specs        |    35 |  26 |     0 |       0 |   0 |     0 |
| View specs           |     5 |   4 |     0 |       0 |   0 |     0 |
+----------------------+-------+-----+-------+---------+-----+-------+
| Total                |  1472 |1042 |    11 |      49 |   4 |    19 |
+----------------------+-------+-----+-------+---------+-----+-------+
  Code LOC: 262     Test LOC: 780     Code to Test Ratio: 1:3.0

  • Can I extract recurrent patterns in my codebase?

Decoupling in Action

Let’s start with a real-world example.

Pretend we want to write an application that tracks time for joggers. At the main page, the user can see the times they entered.

Each time entry has a date, distance, duration, and additional relevant “status” info (e.g. weather, type of terrain, etc.), and an average speed that can be calculated when needed.

We need a report page that displays the average speed and distance per week.

If the average speed for the entry is higher than the overall average speed, we’ll notify the user with an SMS (for this example we will be using Nexmo RESTful API to send the SMS).

The homepage will allow you to select the distance, date, and time spent jogging to create an entry similar to this:

We also have a statistics page which is basically a weekly report that includes the average speed and distance covered per week.

  • You can check out the online sample here.

The Code

The structure of the app directory looks something like:

      ⇒  tree
    .
    ├── assets
    │   └── ...
    ├── controllers
    │   ├── application_controller.rb
    │   ├── entries_controller.rb
    │   └── statistics_controller.rb
    ├── helpers
    │   ├── application_helper.rb
    │   ├── entries_helper.rb
    │   └── statistics_helper.rb
    ├── mailers
    ├── models
    │   ├── entry.rb
    │   └── user.rb
    └── views
        ├── devise
        │   └── ...
        ├── entries
        │   ├── _entry.html.erb
        │   ├── _form.html.erb
        │   └── index.html.erb
        ├── layouts
        │   └── application.html.erb
        └── statistics
            └── index.html.erb

I won’t discuss the User model as it’s nothing special since we are using it with Devise to implement authentication.

As for the Entry model, it contains the business logic for our application.

Each Entry belongs to a User.

We validate the presence of distance, time_period, date_time and status attributes for each entry.

Every time we create an entry, we compare the average speed of the user with the average of all other users in the system, and notify the user by SMS using Nexmo(we won’t discuss how the Nexmo library is used, though I wanted to demonstrate a case in which we use an external library).

Notice, that the Entry model contains more than the business logic alone. It also handles some validations and callbacks.

The entries_controller.rb has the main CRUD actions (no update though). EntriesController#index gets the entries for the current user and orders the records by date created, while EntriesController#create creates a new entry. No need to discuss the obvious and the responsibilities of EntriesController#destroy :

While statistics_controller.rb is responsible for calculating the weekly report, StatisticsController#index gets the entries for the logged in user and groups them by week, employing the #group_by method contained in the Enumerable class in Rails. It then tries to decorate the results using some private methods.

We don’t discuss the views much here, as the source code is self-explanatory.

Below is the view for listing the entries for the logged-in user (index.html.erb). This is the template that will be used to display the results of the index action (method) in the entries controller:

Note that we are using partials render @entries, to pull the shared code out into a partial template _entry.html.erb so we can keep our code DRY and reusable:

The same goes for the _form partial. Instead of using the same code with (new and edit) actions, we create a reusable partial form:

As for the weekly report page view, statistics/index.html.erb shows some statistics and reports the weekly performance of the user by grouping some entries :

And finally, the helper for entries, entries_helper.rb, includes two helpers readable_time_period and readable_speed which should make the attributes more humanly readable:

Nothing fancy so far.

Most of you will argue refactoring this is against the KISS principle and will make the system more complicated.

So does this application really need refactoring?

Absolutely not, but we’ll consider it for demonstration purposes only.

After all, if you check out the previous section, and the characteristics that indicate an app needs refactoring, it becomes obvious that the app in our example is not a valid candidate for refactoring.

Life Cycle

So let’s start by explaining the Rails MVC pattern structure.

Usually, it starts by the browser making a request, such as https://www.toptal.com/jogging/show/1.

The web server receives the request and uses routes to find out which controller to use.

The controllers do the work of parsing user requests, data submissions, cookies, sessions, etc., and then ask the model to get the data.

The models are Ruby classes that talk to the database, store and validate data, perform the business logic, and otherwise do the heavy lifting. Views are what the user sees: HTML, CSS, XML, Javascript, JSON.

If we want to show the sequence of a Rails request lifecycle, it would look something like this:

Rails decoupling MVC life cycle

What I want to achieve is to add more abstraction using plain old ruby objects (POROs) and make the pattern something like the following for create/update actions:

Rails diagram create form

And something like the following for list/show actions:

Rails diagram list query

By adding POROs abstractions we will assure full separation between responsibilities SRP, something that Rails is not very good at.

Guidelines

To achieve the new design, I’ll use the guidelines listed below, but please note these are not rules you have to follow to the T. Think of them as flexible guidelines that make refactoring easier.

  • ActiveRecord models can contain associations and constants, but nothing else. So that means no callbacks (use service objects and add the callbacks there) and no validations (use Form objects to include naming and validations for the model).
  • Keep Controllers as thin layers and always call Service objects. Some of you would ask why use controllers at all since we want to keep calling service objects to contain the logic? Well, controllers are a good place to have the HTTP routing, parameters parsing, authentication, content negotiation, calling the right service or editor object, exception catching, response formatting, and returning the right HTTP status code.
  • Services should call Query objects, and should not store state. Use instance methods, not class methods. There should be very few public methods in keeping with SRP.
  • Queries should be done in query objects. Query object methods should return an object, a hash or an array, not an ActiveRecord association.
  • Avoid using Helpers and use decorators instead. Why? A common pitfall with Rails helpers is that they can turn into a big pile of non-OO functions, all sharing a namespace and stepping on each other. But much worse is that there’s no great way to use any kind of polymorphism with Rails helpers — providing different implementations for different contexts or types, over-riding or sub-classing helpers. I think the Rails helper classes should generally be used for utility methods, not for specific use cases, such as formatting model attributes for any kind of presentation logic. Keep them light and breezy.
  • Avoid using concerns and use Decorators/Delegators instead. Why? After all, concerns seem to be a core part of Rails and can DRY up code when shared among multiple models. Nonetheless, the main issue is that concerns don’t make the model object more cohesive. The code is just better organized. In other words, there’s no real change to the API of the model.
  • Try to extract Value Objects from models to keep your code cleaner and to group related attributes.
  • Always pass one instance variable per view.

Refactoring

Before we get started, I want to discuss one more thing. When you start the refactoring, usually you end up asking yourself: “Is that really good refactoring?”

If you feel you are making more separation or isolation between responsibilities (even if that means adding more code and new files), then this is usually a good thing. After all, decoupling an application is a very good practice and makes it easier for us to do proper unit testing.

I won’t discuss stuff, like moving logic from controllers to models, as I assume you are doing that already, and you are comfortable using Rails (usually Skinny Controller and FAT model).

For the sake of keeping this article tight, I won’t discuss testing here, but that doesn’t mean you shouldn’t test.

On the contrary, you should always start with a test to make sure things are ok before moving forward. This is a must, especially when refactoring.

Then we can implement changes and make sure the tests all pass for the relevant parts of the code.

Extracting Value Objects

First, what is a value object?

Martin Fowler explains:

Value Object is a small object, such as a money or date range object. Their key property is that they follow value semantics rather than reference semantics.

Sometimes you may encounter a situation where a concept deserves its own abstraction and whose equality isn’t based on value, but on the identity. Examples would include Ruby’s Date, URI, and Pathname. Extraction to a value object (or domain model) is a great convenience.

Why bother?

One of the biggest advantages of a Value object is the expressiveness that they help achieve in your code. Your code will tend to be far clearer, or at least it can be if you have good naming practices. Since the Value Object is an abstraction, it leads to cleaner code and fewer errors.

Another big win is immutability. The immutability of objects is very important. When we are storing certain sets of data, which could be used in a value object, I usually don’t want that data to be manipulated.

When is this useful?

There is no single, one-size-fits-all answer. Do what is best for you and what makes sense in any given situation.

Going beyond that, though, there are some guidelines I use to help me make that decision.

If you think of a group of methods is related, with Value objects they are more expressive. This expressiveness means that a Value object should represent a distinct set of data, which your average developer can deduce simply by looking at the name of the object.

How is this done?

Value objects should follow some basic rules:

  • Value objects should have multiple attributes.
  • Attributes should be immutable throughout the object’s life cycle.
  • Equality is determined by the object’s attributes.

In our example, I’ll create an EntryStatus value object to abstract Entry#status_weather and Entry#status_landform attributes to their own class, which looks something like this:

Note: This is just a Plain Old Ruby Object (PORO) that does not inherit from ActiveRecord::Base. We have defined reader methods for our attributes and are assigning them upon initialization. We also used a comparable mixin to compare objects using (<=>) method.

We can modify Entry model to use the value object we created:

We can also modify the EntryController#create method to use the new value object accordingly:

Extract Service Objects

So what is a Service object?

A Service object’s job is to hold the code for a particular bit of business logic. Unlike the “fat model” style, where a small number of objects contain many, many methods for all necessary logic, using Service objects results in many classes, each of which serves a single purpose.

Why? What are the benefits?

  • Decoupling. Service objects help you achieve more isolation between objects.
  • Visibility. Service objects (if well-named) show what an application does. I can just glance over the services directory to see what capabilities an application provides.
  • Clean-up models and controllers. Controllers turn the request (params, session, cookies) into arguments, pass them down to the service and redirect or render according to the service response. While models only deal with associations, and persistence. Extracting code from controllers/models to service objects would support SRP and make the code more decoupled. The responsibility of the model would then be only to deal with associations and saving/deleting records, while the service object would have a single responsibility (SRP). This leads to better design and better unit tests.
  • DRY and Embrace change. I keep service objects as simple and small as I can. I compose service objects with other service objects, and I reuse them.
  • Clean up and speed up your test suite. Services are easy and fast to test since they are small Ruby objects with one point of entry (the call method). Complex services are composed with other services, so you can split up your tests easily. Also, using service objects makes it easier to mock/stub related objects without needing to load the whole rails environment.
  • Callable from anywhere. Service objects are likely to be called from controllers as well as other service objects, DelayedJob / Rescue / Sidekiq Jobs, Rake tasks, console, etc.

On the other hand, nothing is ever perfect. A disadvantage of Service objects is that they can be an overkill for a very simple action. In such cases, you may very well end up complicating, rather than simplifying, your code.

When should you extract service objects?

There is no hard and fast rule here either.

Normally, Service objects are better for mid to large systems; those with a decent amount of logic beyond the standard CRUD operations.

So whenever you think that a code snippet might not belong to the directory where you were going to add it, it’s probably a good idea to reconsider and see if it should go to a service object instead.

Here are some indicators of when to use Service objects:

  • The action is complex.
  • The action reaches across multiple models.
  • The action interacts with an external service.
  • The action is not a core concern of the underlying model.
  • There are multiple ways of performing the action.

How should you design Service Objects?

Designing the class for a service object is relatively straightforward, since you need no special gems, don’t have to learn a new DSL, and can more or less rely on the software design skills you already possess.

I usually use the following guidelines and conventions to design the service object:

  • Do not store state of the object.
  • Use instance methods, not class methods.
  • There should be very few public methods (preferably one to support SRP.
  • Methods should return rich result objects and not booleans.
  • Services go under the app/services directory. I encourage you to use subdirectories for business logic-heavy domains. For instance, the file app/services/report/generate_weekly.rb will define Report::GenerateWeekly while app/services/report/publish_monthly.rb will define Report::PublishMonthly.
  • Services start with a verb (and do not end with Service): ApproveTransaction, SendTestNewsletter, ImportUsersFromCsv.
  • Services respond to the call method. I found using another verb makes it a bit redundant: ApproveTransaction.approve() does not read well. Also, the call method is the de facto method for lambda, procs, and method objects.

If you look at StatisticsController#index, you’ll notice a group of methods (weeks_to_date_from, weeks_to_date_to, avg_distance, etc.) coupled to the controller. That’s not really good. Consider the ramifications if you want to generate the weekly report outside statistics_controller.

In our case, let’s create Report::GenerateWeekly and extract the report logic from StatisticsController:

So StatisticsController#index now looks cleaner:

By applying the Service object pattern we bundle code around a specific, complex action and promote the creation of smaller, clearer methods.

Homework: consider using Value object for the WeeklyReport instead of Struct.

Extract Query Objects from Controllers

What is a Query object?

A Query object is a PORO which represent a database query. It can be reused across different places in the application while at the same time hiding the query logic. It also provides a good isolated unit to test.

You should extract complex SQL/NoSQL queries into their own class.

Each Query object is responsible for returning a result set based on the criteria / business rules.

In this example, we don’t have any complex queries, so using Query object won’t be efficient. However, for demonstration purpose, let’s extract the query in Report::GenerateWeekly#call and create generate_entries_query.rb:

And in Report::GenerateWeekly#call, let’s replace:

  def call
    @user.entries.group_by(&:week).map do |week, entries|
      WeeklyReport.new(
       ...
      )
    end
  end

with:

  def call
    weekly_grouped_entries = GroupEntriesQuery.new(@user).call


    weekly_grouped_entries.map do |week, entries|
      WeeklyReport.new(
       ...
      )
    end
  end

The query object pattern helps keep your model logic strictly related to a class’ behavior, while also keeping your controllers skinny. Since they are nothing more than plain old Ruby classes, query objects don’t need to inherit from ActiveRecord::Base, and should be responsible for nothing more than executing queries.

Extract Create Entry to a Service Object

Now, let’s extract the logic of creating a new entry to a new service object. Let’s use the convention and create CreateEntry:

And now our EntriesController#create is as follows:

  def create
    begin
      CreateEntry.new(current_user, entry_params).call
      flash[:notice] = 'Entry was successfully created.'
    rescue Exception => e
      flash[:error] = e.message
    end

    redirect_to root_path
  end

Move Validations into a Form Object

Now, here things start to get more interesting.

Remember in our guidelines, we agreed we wanted models to contain associations and constants, but nothing else (no validations and no callbacks). So let’s start by removing callbacks, and use a Form object instead.

A Form object is a Plain Old Ruby Object (PORO). It takes over from the controller/service object wherever it needs to talk to the database.

Why use Form objects?

When looking to refactor your app, it’s always a good idea to keep the single responsibility principle (SRP) in mind.

SRP helps you make better design decisions around what a class should be responsible for.

Your database table model (an ActiveRecord model in the context of Rails), for example, represents a single database record in code, so there is no reason for it to be concerned with anything your user is doing.

This is where Form objects come in.

A Form object is responsible for representing a form in your application. So each input field can be treated as an attribute in the class. It can validate that those attributes meet some validation rules, and it can pass the “clean” data to where it needs to go (e.g., your database models or perhaps your search query builder).

When should you use a Form object?

  • When you want to extract the validations from Rails models.
  • When multiple models can be updated by a single form submission, you might want to create a Form object.

This enables you to put all the form logic (naming conventions, validations, and so on) into one place.

How do you create a Form object?

  • Create a plain Ruby class.
  • Include ActiveModel::Model (in Rails 3, you have to include Naming, Conversion, and Validations instead)
  • Start using your new form class as if it were a regular ActiveRecord model, the biggest difference being that you cannot persist the data stored in this object.

Please note that you can use the reform gem, but sticking with POROs we’ll create entry_form.rb which looks like this:

And we will modify CreateEntry to start using the Form object EntryForm:

      class CreateEntry
       
       ......
       ......

        def call
          @entry_form = ::EntryForm.new(@params)

          if @entry_form.valid?
             ....
          else
             ....
          end
        end
      end

Note: Some of you would say that there’s no need to access the Form object from the Service object and that we can just call the Form object directly from the controller, which is a valid argument. However, I would prefer to have clear flow, and that’s why I always call the Form object from the Service object.

Move Callbacks to the Service Object

As we agreed earlier, we don’t want our models to contain validations and callbacks. We extracted the validations using Form objects. But we are still using some callbacks (after_create in Entry model compare_speed_and_notify_user).

Why do we want to remove callbacks from models?

Rails developers usually start noticing callback pain during testing. If you’re not testing your ActiveRecord models, you’ll begin noticing pain later as your application grows and as more logic is required to call or avoid the callback.

after_* callbacks are primarily used in relation to saving or persisting the object.

Once the object is saved, the purpose (i.e. responsibility) of the object has been fulfilled. So if we still see callbacks being invoked after the object has been saved, what we are likely seeing is callbacks reaching outside of the object’s area of responsibility, and that’s when we run into problems.

In our case, we are sending an SMS to the user after we save an entry, which is not really related to the domain of Entry.

A simple way to solve the problem is by moving the callback to the related service object. After all, sending an SMS for the end user is related to the CreateEntry Service Object and not to the Entry model itself.

In doing so, we no longer have to stub out the compare_speed_and_notify_user method in our tests. We’ve made it a simple matter to create an entry without requiring an SMS to be sent, and we’re following good Object Oriented design by making sure our classes have a single responsibility (SRP).

So now our CreateEntry looks something like:

Use Decorators Instead of Helpers

While we can easily use Draper collection of view models and decorators, I’ll stick to POROs for the sake of this article, as I’ve been doing so far.

What I need is a class that will call methods on the decorated object.

I can use method_missing to implement that, but I’ll use Ruby’s standard library SimpleDelegator.

The following code shows how to use SimpleDelegator to implement our base decorator:

    % app/decorators/base_decorator.rb
    require 'delegate'


    class BaseDecorator < SimpleDelegator
      def initialize(base, view_context)
        super(base)
        @object = base
        @view_context = view_context
      end


      private


      def self.decorates(name)
        define_method(name) do
          @object
        end
      end


      def _h
        @view_context
      end
    end

So why the _h method?

This method acts as a proxy for view context. By default, the view context is an instance of a view class, the default view class being ActionView::Base. You can access view helpers as follows:

    _h.content_tag :div, 'my-div', class: 'my-class'

To make it more convenient, we add a decorate method to ApplicationHelper:

    module ApplicationHelper


      # .....


      def decorate(object, klass = nil)
        klass ||= "#{object.class}Decorator".constantize
        decorator = klass.new(object, self)
        yield decorator if block_given?
        decorator
      end


      # .....


    end

Now, we can move EntriesHelper helpers to decorators:

    # app/decorators/entry_decorator.rb
    class EntryDecorator < BaseDecorator
      decorates :entry


      def readable_time_period
        mins = entry.time_period
        return Time.at(60 * mins).utc.strftime('%M <small>Mins</small>').html_safe if mins < 60
        Time.at(60 * mins).utc.strftime('%H <small>Hour</small> %M <small>Mins</small>').html_safe
      end


      def readable_speed
        "#{sprintf('%0.2f', entry.speed)} <small>Km/H</small>".html_safe
      end
    end

And we can use readable_time_period and readable_speed like so:

    # app/views/entries/_entry.html.erb
    -  <td><%= readable_speed(entry) %> </td>
    +  <td><%= decorate(entry).readable_speed %> </td>
    -  <td><%= readable_time_period(entry) %></td>
    +  <td><%= decorate(entry).readable_time_period %></td>

Structure After Refactoring

We ended up with more files, but that’s not necessarily a bad thing (and remember that, from the onset, we acknowledged that this example was for demonstrative purposes only and was not necessarily a good use case for refactoring):

    app
    ├── assets
    │   └── ...
    ├── controllers
    │   ├── application_controller.rb
    │   ├── entries_controller.rb
    │   └── statistics_controller.rb
    ├── decorators
    │   ├── base_decorator.rb
    │   └── entry_decorator.rb
    ├── forms
    │   └── entry_form.rb
    ├── helpers
    │   └── application_helper.rb
    ├── mailers
    ├── models
    │   ├── entry.rb
    │   ├── entry_status.rb
    │   └── user.rb
    ├── queries
    │   └── group_entries_query.rb
    ├── services
    │   ├── create_entry.rb
    │   └── report
    │       └── generate_weekly.rb
    └── views
        ├── devise
        │   └── ..
        ├── entries
        │   ├── _entry.html.erb
        │   ├── _form.html.erb
        │   └── index.html.erb
        ├── layouts
        │   └── application.html.erb
        └── statistics
            └── index.html.erb

Conclusion

Even though we focused on Rails in this blog post, RoR is not a dependency of the described service objects and other POROs. You can use this approach with any web framework, mobile, or console app.

By using MVC as the architecture of web apps, everything stays coupling and makes you go slower because most changes have an impact on other parts of the app. Also, it forces you to think where to put some business logic – should it go into the model, the controller, or the view?

By using simple POROs, we have moved business logic to models or services that don’t inherit from ActiveRecord, which is already a big win, not to mention that we have a cleaner code, which supports SRP and faster unit tests.

Clean architecture aims to put the use cases in the center/top of your structure, so you can easily see what your app does. It also makes it easier to adopt changes since it is much more modular and isolated.

I hope I demonstrated how using Plain Old Ruby Objects and more abstractions decouples concerns, simplifies testing and helps produce clean, maintainable code.

About the author

Eqbal Quran, Jordan
member since March 5, 2014
Eqbal is a senior full-stack developer with more than a decade of experience working in web and mobile development. He is a masterful problem solver, and boasts an extensive portfolio of finished professional products. [click to continue...]
Hiring? Meet the Top 10 Freelance Ruby on Rails Developers for Hire in June 2017

Comments

Tonatiuh IIII
Great article! Thanks for creating this all-in-one guide for implementing this type of architecture - hexagonal architecture I guess, right? One of the biggest advantages I see following this architecture is that it must make a HUGE positive-difference when having to migrate to another framework/language.
Eqbal
Happy you liked it Tonatiuh. You are right. With following POROs we end-up with a framework agnostic code that should make your code more flexible. Another good advantage (specially if you are using TDD/BDD ) that it makes your code more isolated and easier to test (you can easily mock/stub). I'm a big fan of SRP principle and it's not very easy to adopt SRP if you are dependent on MVC. I hope that this wont end up as provoking article (against Rails) as I LOVE ROR myself, it's just I'm using POROs and abstraction more now rather than depending on rails MVC.
Josh
Hi Eqbal -- loved the article. And it came at the perfect time for me...the business logic in my application is starting to outgrow the "let's dump this new code in the model and then separate it into a module" approach. We had been cautiously starting to use service objects and form objects and this article really helped me to see that these are the correct set of abstractions, plus the others you mentioned, to develop a robust application. And you're right on the money regarding ease of testing. There's now much less of a need to mock or stub out the various disparate parts of ActiveRecord (which makes you wonder if the tests ultimately even give you assurance that your code works) since these components are now extracted into their own specialized objects. I know this article was written on the context of Rails & server-side development, but I wonder if you have found a similar set of principles or abstractions that work when developing complex UIs client-side?
Eqbal
Thanks Josh. For complex client-side apps it could be useful to use some JS framework (ex: Ember.js/Angular/React), using some JS framework for interactive apps can provide a lot of powerful abstractions, just like in Ember.js (templates, controllers, models and views). The nature of functional Javascript could make it harder to do OOPs style of abstraction tho. With my Ember.js experience at least, I usually try to separate concerns to support SRP as much as possible with something like: (Templates: hold the HTML, Helpers: keep HTML DRY, Controls: provide a place for user input, Handlers: initiate state changes based on user input, Viewables: represent temporary application state, Storables: represent state that should be persisted, Bindings: communicate changes in state to the relevant listeners). Remember there is no general rule of thumb here, just don't be afraid of adding more folders as long you separate the concerns and end up with smaller classes with each has one thing to do. Hope that helps.
Victor Cortes
Thanks, Eqbal! This is something I've been discussing lately with my colleagues, a new "Rails architecture" using well know patterns relying on POROs. You said: "Methods should return rich result objects and not booleans." but you are returning booleans in CreateEntry service. Why is that? The question I always ask when working with services. How do you handle errors in the service if for example `entry.save` or `compare_speed_and_notify_user`? You are returning `true` but an error might happen. Also, in case of an error how do you manage the different flows when an error occurs (flash messages and redirects)?
Eqbal
Very valid point Victor, we should raise an error instead of returning true/false and in the controller to catch the the error and change the flow. Let me see if I can make some changes to the code. Thanks
Tonatiuh Núñez
Just contributing a bit here. That's a great tip Eqbal. I have made me that same question (return false or raise an error when the operation failed in the service object). Now that you're mentioning it, I'm thinking that going with "raising exceptions" may be the best choice, because that can improve the code readability (given that the exception errors will have a custom name related to the business logic). I saw this other post some time ago: https://www.toptal.com/abap/clean-code-and-the-art-of-exception-handling, I think that combining what you recommend with what is recommended on the other post is a strong combination.
Alexandr
Eqbal thanks for great article! May I translate it to Russian?
Augusts Bautra
Thanks for the article, Eqbal! The great visuals and extensive code samples create a powerful presentation. I have been working with RoR for over two years now and lately I have been following the idea of keeping Controllers as thin as possible with Service objects, so seeing you reiterate this was very helpful for me. I also find your discussion of moving Validations and Callbacks into a Form Objects interesting and inspiring. I can get behind the rationale that following SRP, Models should only store data and have associations. Perhaps I could ask your thoughts on the issue of instance VS local variables in views? (http://rails-bestpractices.com/posts/2010/07/24/replace-instance-variable-with-local-variable/) I have seen a great many problems with views in the apps I have worked on stem precisely from using instance variables in partials and losing track of where what is instantiated.
Eqbal
I guess so :P glad you liked the article.
Mark
Hey Eqbal, I really like the way how you organise different responsibilities here. This is practically important especially when your application is starting to get bigger and bigger. Will totally recommend this article to my Ruby friends!
kakukiko
Just a note, I would not recommend the use of draper as is not a well mantained gem, at least for the last two years, and it has a lot of issues with rails 5. The main developers left the project years ago and since then there are some forks here and there, but nothing to take it too seriously. https://github.com/drapergem/draper/graphs/contributors I would definitely go for those PORO for decoration. Great article BTW
Aleksey Ivanov
Hi Eqbal, thank you for the article. As for me I found it useful to namespace services since if one has many services and models there can be name collisions. For example, Services::Entry::Create#call.
Vick
Hello Eqbal. Thank you for this great article, this approach is helping me so much in my projects. Just a question, using Rspec and FactoryGirl for testing, I have had some problems testing the models; since the validations are placed in the form-object, and it is called from the service-object, each time that a record is created it looks for the validations on the model, but as they are not there the tests don't pass when a record is expected not to be valid. I've tried with initialize_with and to_create FactoryGirl methods to create a record through the service-object and form-object; however it seems that the the result of any of the objects is not what rspec expects to say that the record is no valid (.to_not be_valid). Please, do you have any suggestion to solve this? I'll really appreciate your help. Thanks so much Eqbal.
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant Technologies
About the author
Eqbal Quran
Ruby Developer
Eqbal is a senior full-stack developer with more than a decade of experience working in web and mobile development. He is a masterful problem solver, and boasts an extensive portfolio of finished professional products.