How to make peace with Ruby on Rails and code happily
Tags: ruby, rails
Published: 2021-07-04 11:57:19

This post sums up few ideas from various articles and videos which helped me to realize why most of the big Ruby on Rails applications are hard to maintain.

I've been working on small Ruby on Rails apps, gigantic monoliths and tangled microservices for about 10 years. 

Ruby on Rails takes full advantage of the flexibility of the Ruby language.

Matz (Yukihiro Matsumoto) built the language to make life easy for developers, and even make them happy.

In fact, Ruby's motto is *A Programmer's Best Friend*.

I code in Ruby every day, and I have to admit, I rarely feel "happiness".

Sometimes I am pleased with my solution, but most of the time I strive to gain an understanding of what the code is doing.

Why does it happen?

Shouldn't I feel joy while coding in a perfect status of flow?

Let's start with a basic principle: 

to add or change functionalities to an application, developers needs to understand its code.

Readable code is easy to understand and easy to change. 

Code is read many more times than it is written, so investing time to write readable code pays off, even in the short term. The less [bad code smells](https://alessandro.desi/code-smells) in the code, the better.

There are many books about writing good code, two good ones are:

- Practical Object-Oriented Design, by Sandi Metz.  
  This is my favourite book about writing maintainable Object-Oriented applications in Ruby.

- Refactoring: Ruby Edition, by Jay Fields, Martin Fowler, and Shane Harvie.  
  This is the Ruby version of the most famous book about [bad code smells](https://alessandro.desi/code-smells) and how to refactor them.
 
The issues present in most of the Rails apps I've worked with are linked to the topics of the two books: poor *OOP* and *Bad Smell in code*.

These are the most common issues I've seen in many Rails  apps:

- Fat model
- Fat controller
- Logic in the view
- Lack of a clear design

## Fat Model
The responsibility of a model is to persist data in a DB table and manage associations with other models.

It is very easy to add functionalities to them, and common to see models named `User`, `Company` or `Booking` having tens of methods and many hundreds of lines.

Inside those fat models, you can find logic about validations, policies, business and views.

Also, they are often soiled with many callbacks which turns the model into a bowl of spaghetti code.

There are few patterns you can follow to keep models clean. Here is a helpful post that describes [7 Patterns to Refactor Fat ActiveRecord Models](https://codeclimate.com/blog/7-ways-to-decompose-fat-activerecord-models/)

## Fat Controller
Fat controllers are even worse than fat models.  
The controller is the home of the actions linked to the HTTP calls. 

The only duty of a controller action should be "to call an action" and render the result (or redirect to a different page).

Often the controller actions contain logic about:

- business rules
- database access
- presentation logic

When that happens, it makes the actions long, confusing and hard to maintain. Also the tests would be long and messy.

Moving all the business logic to a Service object is a step forward.  
The controller action will call the service and then render/redirect with the proper HTTP status based on the service result.

Beware of misusing Service Objects as they do not provide a good abstraction for your Business Domain, they just procedure.

Jason Swett summarised well why using Service Object should not be the rule in [Beware of service objects in Rails](https://www.codewithjason.com/rails-service-objects/).

Avdi Grimm suggests that it is fine to write procedure instead of services, especially when the domain concepts are not yet well defined.  
He wrote about this in [Enough With the Service Objects Already](https://avdi.codes/service-objects/).

## Logic in the view
The view should contain as little logic as possible.

Mixing up Ruby code with HTML code makes the views unreadable and hard to test.

Messy Rails views often contain one or more of the following:
- Nested conditionals
- Calculations
- DB queries
- Variable assignments

Thus, you need to keep those outside the views.  
Two common techniques to help you to keep the views clean are Decorators and the View objects.

Here is an article explaining what they are and why moving the logic to the models is not a solution: [Cleaning Up Your Rails Views With View Objects](https://jtway.co/cleaning-up-your-rails-views-with-view-objects-42cf048ea491)

## Lack of a clear design

This is the result of the previous three subjects.  
If the business logic pervades the models, controllers and even views, you'll have a hard time understanding what is the business domain of the application. The design is abstract and entangled with the concepts of Ruby on Rails, and business concepts are mixed with those of the framework.

A possible solution is to start up front to adopt design patterns like Clean Architecture or Exagonal Architecture.

Those patterns are not convenient for small Rails apps, but they pay off for big projects.

However, note that if the application eventually succeeds, it will grow and more functionalities will be added by you and your workmates, and if it lacks a clear design, you will have a hard time maintaining it.

At the GoRuCo 2012 conference, Matt Wynne gave a great talk about [Hexagonal Rails](https://www.youtube.com/watch?v=CGN4RFkhH2M)

## Conclusion
Ruby is a flexible and powerful language and it should be a pleasure coding with it.

Often big messy Rails applications (and not just Rails) make your life as a developer really hard.

That should not be the norm, there are various strategies that you can follow to keep your project readable and maintainable, and in fact, there are many successful start-ups based on Rails applications beautifully coded.

I hope you've found this post and its links helpful.

---

This was initially published on my blog here: [How to make peace with Ruby on Rails and code happily](https://alessandro.desi/clean-rails)