10 Ruby on Rails Best Practices

Tweet

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 10 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, or MyModel.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 and Post.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 to User.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 and attr_accessibile. 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_protected forces 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 an ActiveRecord-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 though full_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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • igmarin

    Thank you very much Darcy is a very good article.

  • Rubymonkey

    Nice article!

    Your “Related Posts” section could use a little scoping. Namely, filtering by date! … “Ruby on Rails 1.0 is out” … :O

    • http://www.onsman.com Ricky Onsman

      The article author doesn’t get to choose the Related Posts, Rubymonkey, so you’d better point the finger at us at SitePoint. I’ll check out why the older ones are in there. Thanks for pointing it out.

  • flyerhzm

    check here to see more best practices: http://rails-bestpractices.com

  • Lee Smith

    I think you hit some good points in the article…well done.

    The only thing I would change is the “Reusable Scopes and Relations”. Basically, scopes are dead. Just use plain ole’ ruby.

    http://elabs.se/blog/24-scopes-are-obsolete
    http://www.railway.at/2010/03/09/named-scopes-are-dead/

    • Darcy Laycock

      I disagree in part because the readabiity of using scopes / named_scopes is a big win in many cases. Using them for complex code (e.g. if you find yourself using lambdas, then using a class method is a better option_ but calling them ‘dead’ is misleading.
      It’s simply a matter of it being easier (and in many cases, more readable) with class methods in Rails 3. In fact, if you look at the implementation of scopes in Rails 3 you’ll see they’re effectively doing similar to what you would by hand; Having a formalised way of declaring this for simpler relations (which quite often are the cases, esp. when you’re building them up from composites of other relations) leads to easier to scan code and stronger reflection in some cases.

      • Dan Croak

        It isn’t just a matter of syntax. With lambdas, you cannot have default values for arguments. With class methods, you can.

        So, you can do more with class methods.

  • Brad Haydon

    I agree with Lee Smith. Although scopes were an amazing feature added to Rails2, they are no longer necessary.

  • beerlington

    Using protected/accessible attributes is definitely something that’s easily overlooked. I recently started using attr_accessible in all my models for attributes that users would be updating through forms. This is a nice way to ensure you don’t accidentally add a security hole, but I found it to be a little tedious to manually set the protected attributes when I needed them. I wrote a gem to alleviate this nuisance for things like admin controllers (https://github.com/beerlington/sudo_attributes), but I’m curious how other people handle that situation.

  • Bernardo Arancibia

    Thanks for these nice tips. I find virtual attributes very helpful! I use them a lot. And also I’m trying to follow the rule of fat models, skinny controllers. Sometimes, when you start programming a feature, it’s really tedious to be organised with the code, but I think if we have this approach (best practice) first time, it will be very helpful for the whole development process.

  • AJ

    Thanks a lot darcy.this article was really helpful.

  • http://twitter.com/cronis7 Marc

    Great post. It really sheds light on some of the benefits of thinking about code vs just coding