10 Ruby on Rails Best Practices

Darcy Laycock
Darcy Laycock
Share

If you’re new to Ruby on Rails, one of the most daunting aspects is knowing what’s the preferred way of accomplishing a given task. While a lot of techniques and libraries have come and gone as the community’s preferred way of doing something, there are some best practices that remain, and can lead to writing the cleanest, most secure and maintainable Rails code possible. Listed here today are ten of the most popular and useful best practices you can use as a Ruby developer.

Fat Model, Skinny Controller

Arguably one of the most important ways to write clear and concise code in Ruby on Rails, the motto “Fat Model, Skinny Controller” refers to how the M and C parts of MVC ideally work together. Namely, any non-response-related logic should go in the model, ideally in a nice, testable method. Meanwhile, the “skinny” controller is simply a nice interface between the view and model. In practice, this can require a range of different types of refactoring, but it all comes down to one idea: by moving any logic that isn’t about the response (for example, setting a flash message, or choosing whether to redirect or render a view) to the model (instead of the controller), not only have you promoted reuse where possible but you’ve also made it possible to test your code outside of the context of a request. Let’s look at a simple example. Say you have code like this:
def index
  @published_posts = Post.all :conditions => {['published_at <= ?', Time.now]}
  @unpublished_posts = Post.all :conditions => {['published_at IS NULL OR published_at > ?', Time.now]}
end
You can change it to this:
def index
  @published_posts = Post.all_published
  @unpublished_posts = Post.all_unpublished
end
Then, you can move the logic to your post model, where it might look like this:
def self.all_published
  all :conditions => {['published_at <= ?', Time.now]}
end

def self.all_unpublished
  all :conditions => {['published_at IS NULL OR published_at > ?', Time.now]}
end
With the methods Post.all_published
and Post.all_unpublished, we’ve not only made it simpler to test our code, we’ve also made it possible to reuse that same set of conditions in another location. But as we’ll see shortly, even this is still not ideal.

Reusable Scopes and Relations

In the example above, we still weren’t quite at the optimal level. For example, what if we wanted to fetch a single published post? We’d have to duplicate the conditions in another method—which just leads to more and more junk code. Fortunately, Rails provides a better way—scopes (in older versions of Rails they were called named scopes). Put simply, a scope is a set of constraints on database interactions (such as a condition, limit, or offset) that are chainable and reusable. As a result, I can call MyModel.my_scope.another_scope, orMyModel.my_scope.first, or MyModel.my_scope.all
. So, taking our previous example again, we could rewrite it as follows in Rails 3:
scope :published, lambda { where('published_at < = ?', Time.now) }
scope :unpublished, lambda { where('published_at > ?', Time.now) }
And in Rails 2:
named_scope :published, lambda { {:conditions => ['published_at < = ?', Time.now]} }
named_scope :unpublished, lambda { {:conditions => ['published_at > ?', Time.now]} }
This would then allow us to use Post.published.all andPost.unpublished.all where needed. Even better, as of Rails 3, Rails now supports relations—essentially, arbitrary scopes that can be used anywhere. For example, Post.where(:title => 'Hello World').first
instead of Post.first :conditions => {:title => 'Hello World'}, meaning you now get powerful features such as chaining for arbitrary database calls. This leads to better constructed code and more reusable queries—you start to think in terms of what a scope or combination of scopes can achieve—in other words, the big picture—rather than just what your one query is. As a bonus, it makes it much nicer to compose very complex queries such as searches by simply adding the relations and scopes you need.

Package Your Code into Gems and Plugins

If you’ve used Ruby on Rails a decent amount, you’ve most likely noticed the wealth of rubygems available to Rails developers. If there is a relatively general problem, it’s highly likely another developer has already solved it. So, when you write code you think is general enough—which usually just means you’ve written it more than once before in another application, stop and think about how you can extract it into a plugin or gem suitable for a wider range of purposes. This not only pays off the next time you need said functionality, but it also forces you to stop and evaluate your approach to the problem—more often than not, I’ve found that extracting code from an application has led to a simpler design. You also shouldn’t forget that as a developer, releasing open source code can pay off in other ways. When it comes to using that code next time, as an added bonus it’ll generally be tested already and well explored—resulting in generally better code from the multiple stages of refactoring. Along the same lines, spend some time looking at open source gems that already solve your problems. If you’re not sure where to start, a GitHub search is usually a good jumping-off point, and Ruby Toolbox contains a listing of the most popular plugins in the community.

Use the Built-in Ruby Duck Typing Methods

As a language, Ruby uses several conventions that can make development easier. For example, implementing a to_s instance method on an object will give you a standard way of getting a string representation of your object. By implementing these standard type conversions—in addition to to_s, there’s also to_i for integers and to_a for arrays—you make it possible for your Ruby code to be more concise. As an example, have a look at the following string interpolation:
"Hello there, #{user.name}"
If you alias the name attribute to to_s, you could instead write simply:
"Hello there, #{user}"
Other places in Ruby that use to_s (and, for other situations, to_i
and the like) will automatically take advantage of this string representation of your object. Alongside this, you can also implement the Enumerable module for any of your classes that you want to provide with useful iteration features. All you need to write in your class are the each and < => methods. These two simple additions give you a whole heap of extra functionality for free: methods like map, inject
,sort, max, min, and a number of others.

Manage Attribute Access

By default, when using mass assignment in Rails—that is, code similar toUser.new(params[:user])
and @user.update_attributes params[:user]—Rails will assign every attribute without doing any checking. Your validations prevent bad data but they don’t, for example, prevent you from overwriting an attribute that you don’t want to change. To solve this, ActiveRecord uses two methods—attr_protected andattr_accessible. Using attr_protected, you declare a blacklist of variables you don’t want assigned (for instance, attr_protected :admin, :password_hash
). Using attr_accessible, which is generally prefered, you declare the ones you do want to be able to assign (for instance,attr_accessible :login, :email, :password, :password_confirmation). By doing this, you prevent any mass assignment that could occur via your application’s forms—just because you don’t have a field for a given attribute doesn’t mean a hacker can’t add it to the request. This way you’re either forced you to manually set certain attribute values or, more usefully, provide a protected method to use when you want to set the value. From a security perspective, using attr_accessible and attr_protectedforces you to think about what should be editable and how to protect the ways in which your class’s attributes are set.

Use Non-database-backed Models

Although models in Rails are mostly based on ActiveRecord::Base
or some other type of object mapper for a database, it’s important to remember that in MVC, the M isn’t restricted to database-backed models. Using non-database-backed models can help to organize logic which might otherwise become muddy. For example, there are libraries that give you anActiveRecord-like interface for contact form emails. Using ActiveModel (available in Rails 3 and higher), it’s possible to take arbitrary objects that encapsulate a set of common behavior and use them as your models. Adding virtual models also makes it easier to adhere to RESTful controller design, as you can represent data other than database entries as resources. As a prime example, several popular authentication libraries in Rails now represent the user’s current authenticated session as a model, and I’ve personally implemented a password reset as a model. When it comes time to interact with these models in your controller code, your code will be that much cleaner, as you can use the exact same approach as with database-backed models.

Virtual Attributes

If you find that you’re manipulating data before passing it to a model (for example, converting the type of an object), it’s likely time you started structuring your code to take advantage of virtual attributes. Virtual attributes are a very simple idea—essentially, all you’re doing is defining your own getter and setter methods. Let’s say you were using the following code to set a user’s name:
@user = User.new(params[:user])
@user.first_name, @user.last_name = params[:user][:full_name].split(" ", 2)
You could remove the second line, and instead add the following to your User model:
def full_name=(value)
  self.first_name, self.last_name = value.to_s.split(" ", 2)
end
Whenever you set the full_name attribute, your model will now automatically set the first_name and last_name attributes for you as well, even thoughfull_name
doesn’t exist in the database. Likewise, you’ll typically want to define a getter method, full_name, that returns "#{first_name} #{last_name}". Using virtual attributes, you can use alternative representations of data in forms with relatively little effort. It’s also much simpler to test the logic in isolation, which is always a good thing.

Use Translations

As of Rails 2.2, the framework itself has shipped with strong support for internationalization (or i18n) out of the box. All you need to do is maintain a YAML file of translations, and use I18n.t / t
in your code where there is data shown to the user. Essentially, the Rails i18n framework makes it easy to declare the map of an abstract context to a string. From a developer’s perspective, using I18n is useful for more than just rolling your app out to more locales. When it comes time to rename something in your interface, having a single place to look for the string in question in order to replace it across the whole application is much quicker than scouring your code for every occurrence. If you want to get started with Rails’ I18n framework, there is a very thorough guide made freely available on it by the community.

In Conclusion

There are literally hundreds of coding practices or techniques that can make your life as a Ruby on Rails developer easier, but I’ve tried to pick out ten that are broadly applicable to just about every project, and which are often ignored. Do you follow these practices already? Will you start? Do you have any others you’d add? Let me know in the comments. And if you enjoyed reading this post, you’ll love Learnable; the place to learn fresh skills and techniques from the masters. Members get instant access to all of SitePoint’s ebooks and interactive online courses, like Learning Ruby on Rails 3. Comments on this article are closed. Have a question about Ruby on Rails? Why not ask it on our forums?

Frequently Asked Questions on Ruby on Rails Best Practices

What is the concept of ‘Fat Model, Skinny Controller’ in Ruby on Rails?

The ‘Fat Model, Skinny Controller’ concept is a design pattern in Ruby on Rails that encourages developers to keep the business logic in the model and the retrieval of data in the controller. This approach helps to maintain a clean and organized codebase. The model, being ‘fat’, contains validations, database relationships, computations, and business rules. On the other hand, the ‘skinny’ controller handles user inputs, sessions, and templates rendering. This separation of concerns enhances the maintainability and scalability of the application.

How can I keep my controllers skinny in Ruby on Rails?

Keeping controllers skinny in Ruby on Rails involves limiting the responsibilities of the controller. The controller should only be responsible for receiving the user’s request, interacting with the model to process data, and then returning the appropriate response to the user. Any business logic or complex computations should be moved to the model or service objects. This can be achieved by using before_action filters, form objects, service objects, and presenters.

What are the benefits of using service objects in Ruby on Rails?

Service objects in Ruby on Rails are plain old Ruby objects (PORO) that encapsulate specific business logic that doesn’t necessarily belong to any model or controller. They promote single responsibility principle, making your code easier to maintain and test. Service objects can be used to handle complex calculations, third-party integrations, background jobs, or any other tasks that are not directly related to models or controllers.

How can I implement DRY principle in Ruby on Rails?

DRY (Don’t Repeat Yourself) principle can be implemented in Ruby on Rails by extracting repeated code into methods, modules, or classes. For instance, if you find yourself writing the same code in different controllers, you can extract that code into a method and place it in the ApplicationController. Similarly, if the same code is used in different models, you can extract it into a module and include it in the models.

What is the role of database indexes in Ruby on Rails?

Database indexes in Ruby on Rails are used to speed up the retrieval of data from the database. They work like a book index, allowing the database to find data without having to scan every row in a table. Indexes are particularly useful in large databases where data retrieval can be time-consuming. However, they should be used judiciously as they take up disk space and can slow down write operations.

How can I use background jobs in Ruby on Rails?

Background jobs in Ruby on Rails can be used to perform time-consuming tasks asynchronously, such as sending emails, processing images, or calling third-party APIs. This improves the performance of the application by offloading these tasks from the main application thread. Ruby on Rails provides Active Job, a framework for declaring jobs and making them run on a variety of queueing backends.

What is the purpose of using version control in Ruby on Rails?

Version control in Ruby on Rails is used to track and manage changes to the codebase. It allows multiple developers to work on the same codebase without overwriting each other’s changes. It also provides a history of changes, making it easier to find and fix bugs. Git is the most commonly used version control system in Ruby on Rails projects.

How can I improve the performance of my Ruby on Rails application?

There are several ways to improve the performance of a Ruby on Rails application. These include optimizing database queries, using caching, reducing the number of HTTP requests, compressing assets, and using background jobs for time-consuming tasks. Additionally, you can use performance monitoring tools like New Relic or Skylight to identify and fix performance bottlenecks.

What are the best practices for testing in Ruby on Rails?

Testing is a crucial part of Ruby on Rails development. Some of the best practices for testing in Ruby on Rails include writing tests for all critical paths of the application, using factories instead of fixtures, keeping tests DRY by extracting common setup code, and using test doubles to isolate tests. Ruby on Rails provides a built-in testing framework, but you can also use third-party libraries like RSpec and FactoryBot.

How can I keep my Ruby on Rails code clean and maintainable?

Keeping your Ruby on Rails code clean and maintainable involves following good coding practices and principles. These include adhering to the Ruby style guide, using meaningful names for variables and methods, keeping methods and classes small and focused, and writing comments to explain complex code. Additionally, you should regularly refactor your code to remove duplication and improve its structure.