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:

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.

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

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:

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:

And something like the following for list/show actions:

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

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.

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:

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:

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:

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:

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).

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):

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.