Here at Plataformatec we use Github Pull Requests a lot for code review and this usually yields tons of constructive comments and excellent discussions from time to time. One of the recent topics was about whether we should use scopes or class methods throughout the project to be consistent. It’s also not hard to find discussions about it all over the internet. The classic comment usually boils down to “there is no difference between them” or “it is a matter of taste”. I tend to agree with both sentences, but I’d like to show some slight differences that exist between both.
Defining a scope
First of all, lets get a better understanding about how scopes are used. In Rails 3 you can define a scope in two ways:
class Post < ActiveRecord::Base scope :published, where(status: 'published') scope :draft, -> { where(status: 'draft') } end |
The main difference between both usages is that the :published condition is evaluated when the class is first loaded, whereas the :draft one is lazy evaluated when it is called. Because of that, in Rails 4 the first way is going to be deprecated which means you will always need to declare scopes with a callable object as argument. This is to avoid issues when trying to declare a scope with some sort of Time argument:
class Post < ActiveRecord::Base scope :published_last_week, where('published_at >= ?', 1.week.ago) end |
Because this won’t work as expected: 1.week.ago will be evaluated when the class is loaded, not every time the scope is called.
Scopes are just class methods
Internally Active Record converts a scope into a class method. Conceptually, its simplified implementation in Rails master looks something like this:
def self.scope(name, body) singleton_class.send(:define_method, name, &body) end |
Which ends up as a class method with the given name and body, like this:
def self.published where(status: 'published') end |
And I think that’s why most people think: “Why should I use a scope if it is just syntax sugar for a class method?”. So here are some interesting examples for you to think about.
Scopes are always chainable
Lets use the following scenario: users will be able to filter posts by statuses, ordering by most recent updated ones. Simple enough, lets write scopes for that:
class Post < ActiveRecord::Base scope :by_status, -> status { where(status: status) } scope :recent, -> { order("posts.updated_at DESC") } end |
And we can call them freely like this:
Post.by_status('published').recent # SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' # ORDER BY posts.updated_at DESC |
Or with a user provided param:
Post.by_status(params[:status]).recent # SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' # ORDER BY posts.updated_at DESC |
So far, so good. Now lets move them to class methods, just for the sake of comparing:
class Post < ActiveRecord::Base def self.by_status(status) where(status: status) end def self.recent order("posts.updated_at DESC") end end |
Besides using a few extra lines, no big improvements. But now what happens if the :status parameter is nil or blank?
Post.by_status(nil).recent # SELECT "posts".* FROM "posts" WHERE "posts"."status" IS NULL # ORDER BY posts.updated_at DESC Post.by_status('').recent # SELECT "posts".* FROM "posts" WHERE "posts"."status" = '' # ORDER BY posts.updated_at DESC |
Oooops, I don’t think we wanted to allow these queries, did we? With scopes, we can easily fix that by adding a presence condition to our scope:
scope :by_status, -> status { where(status: status) if status.present? } |
There we go:
Post.by_status(nil).recent # SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC Post.by_status('').recent # SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC |
Awesome. Now lets try to do the same with our beloved class method:
class Post < ActiveRecord::Base def self.by_status(status) where(status: status) if status.present? end end |
Running this:
Post.by_status('').recent NoMethodError: undefined method `recent' for nil:NilClass |
And :bomb:. The difference is that a scope will always return a relation, whereas our simple class method implementation will not. The class method should look like this instead:
def self.by_status(status) if status.present? where(status: status) else all end end |
Notice that I’m returning all for the nil/blank case, which in Rails 4 returns a relation (it previously returned the Array of items from the database). In Rails 3.2.x, you should use scoped there instead. And there we go:
Post.by_status('').recent # SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC |
So the advice here is: never return nil from a class method that should work like a scope, otherwise you’re breaking the chainability condition implied by scopes, that always return a relation.
Scopes are extensible
Lets get pagination as our next example and I’m going to use the kaminari gem as basis. The most important thing you need to do when paginating a collection is to tell which page you want to fetch:
Post.page(2) |
After doing that you might want to say how many records per page you want:
Post.page(2).per(15) |
And you may to know the total number of pages, or whether you are in the first or last page:
posts = Post.page(2) posts.total_pages # => 2 posts.first_page? # => false posts.last_page? # => true |
This all makes sense when we call things in this order, but it doesn’t make any sense to call these methods in a collection that is not paginated, does it? When you write scopes, you can add specific extensions that will only be available in your object if that scope is called. In case of kaminari, it only adds the page scope to your Active Record models, and relies on the scope extensions feature to add all other functionality when page is called. Conceptually, the code would look like this:
scope :page, -> num { # some limit + offset logic here for pagination } do def per(num) # more logic here end def total_pages # some more here end def first_page? # and a bit more end def last_page? # and so on end end |
Scope extensions is a powerful and flexible technique to have in our toolchain. But of course, we can always go wild and get all that with class methods too:
def self.page(num) scope = # some limit + offset logic here for pagination scope.extend PaginationExtensions scope end module PaginationExtensions def per(num) # more logic here end def total_pages # some more here end def first_page? # and a bit more end def last_page? # and so on end end |
It is a bit more verbose than using a scope, but it yields the same results. And the advice here is: pick what works better for you but make sure you know what the framework provides before reinventing the wheel.
Wrapping up
I personally tend to use scopes when the logic is very small, for simple where/order clauses, and class methods when it involves a bit more complexity, but whether it receives an argument or not doesn’t really matter much to me. I also tend to rely more on scopes when doing extensions like showed here, since it’s a feature that Active Record already gives us for free.
I think it’s important to clarify the main differences between scopes and class methods, so that you can pick the right tool for the job™, or the tool that makes you more comfortable. Whether you use one or another, I don’t think it really matters, as long as you write them clear and consistently throughout your application.
Do you have any thought about using scopes vs class methods? Make sure to leave a comment below telling us what you think, we’d love to hear.
Tags: activerecord, rails, rails 3.2, rails 4, scopes
Posted in English | 18 Comments »
Rails 3.2 is out with great features on spotlight: faster development reloading, faster router and explain queries. However, every Rails release ships with minor features that do not get that much attention but still would be a great fit to your application. This blog post is about my five favorites “hidden” features of Rails 3.2.
1) Smarter content_tag_for
This feature written by Prem Sichanugrist provides a very simple but welcome clean up to your views. Both content_tag_for and div_for now accepts an array of records and automatically loop over each record. Therefore, instead of writing this:
@posts.each do |post| content_tag_for(:li, post) do ... end end |
You can simply write:
content_tag_for(:li, @posts) do |post| ... end |
2) Smarter migration generators
It is funny how some parts of Rails as old as the migration generators continue receiving improvements day after day. Rails 3.1 already added a feature that automatically generate indexes for associations, by simply invoking:
rails g scaffold Comment post:references title:string body:text
With the above, Rails will detect that post is a reference and it will automatically 1) add a post_id integer column, 2) add an association to your model and 3) add an index to that column.
Right after 3.1 came out, I have pushed another small feature to the migration generator that simply makes the type attribute default to string. Therefore, you no longer need to write:
rails g scaffold Person name:string email:string
You could simply write:
rails g scaffold Person name email
Oddly enough, the idea for this feature came when I was preparing a presentation and the scaffold command could not fit in a slide (the so-called Presentation Driven Development). Anyhow, this small addition would not be enough to make to the best five “hidden” features of Rails 3.2. That’s when Dmitrii Samoilov comes in.
Dmitrii sent a pull request that allows you to specify which columns should have an (unique) index. So one could write:
rails g scaffold Person name:index email:uniq
And the generator will automatically generate an index for name and an unique index for e-mail. There are other features there as well, so don’t forget to checkout the CHANGELOG.
3) Flexible exception handling
When Rails 3.0 came out, one of the features that people suddenly missed was the ability to better handle exceptions. The issue was: since Rails 3 became a lot more Rack “fluent”, we had to move some features to the middleware stack and this forced us to move the whole exceptions handling as well. Rails 3.2 attempts to bring some customization back to the game by allowing you to set your own exceptions rack application that is invoked when a failure happens. For instance, you could set the exceptions application to your own router in your config/application.rb:
config.exceptions_app = self.routes |
Now, every time there is an exception, your router is going to be invoked. Therefore, to render custom 404 pages, you could simply add to your router:
match "/404", :to => "errors#not_found" |
And implement the logic in the controller as you wish! However, there are a few things to keep in mind if you go down this road:
- You need to use
matchin your routes and notget/post/put/deletebecause such exceptions can happen in any HTTP request; - You won’t be able to see your custom exceptions in development unless you set
config.consider_all_requests_localto false in yourconfig/environments/development.rb. The reason is, if the request is considered local, Rails will always favor to show the debug exceptions page; - You can always access the original exception in the controller at
env["action_dispatch.exception"]; - It is not possible to set cookies, the session nor the flash after an exception happens. They all were already serialized back to the client;
- Finally, the default exceptions application used by Rails that simply renders a page in
public/STATUS.htmlis available here: action_dispatch/middleware/public_exceptions.rb
Remember that whatever you do in the errors controller, it should not be anything “fancy”. Keep it simple because something already went wrong with your application!
4) Custom partial paths
In order to render a partial for a given model, Rails 3.0 retrieved the partial name by calling: model.class.model_name.partial_path. Grant Hutchins & Peter Jaros noticed that this was not very flexible because the class was responsible to define the partial path and therefore they decided to move this responsibility to the instance. In order to better understand how you can use this feature, let’s consider the following practical example.
Imagine your application have an activity feed and each activity in the feed has a certain type. Usually, each type is rendered differently. For example, if you consider a to-do-list application, activities could be both “marking a list as favorite” or “marking a task as done”. Usually, applications solve this by looping for each item and rendering its respective partial, something like this:
@activities.each do |activity| render :partial => "activities/#{activity.kind}", :locals => { :activity => activity } end |
Now, you can solve this problem by defining to_partial_path in the model (the method to_partial_path is part of the ActiveModel API and can be implemented in any object. The example above implements it in the model for convenience, but it could be a presenter, another ORM, etc):
class Activity < ActiveRecord::Base def to_partial_path() "activities/#{kind}" end end |
And then invoking:
render :partial => @activities, :as => :activity |
This will now work on Rails 3.2 because even though all activities are of the same class, each instance is actually responsible for telling Rails which partial should be rendered.
The difference here is not only in brevity, but also in performance. Although the first snippet works fine, it is slow. In the scenario where only one kind of activity happened, the first snippet will go through the render stack 30 times and lookup the same template in your filesystem 30 times. If you read Crafting Rails Applications you know that this lookup is cached, but even though it would certainly be faster if we didn’t have to do this 30 times, but once.
That’s where render :collection or render :partial with an array comes in. In such cases Rails will retrieve all templates up front skipping duplicates, and this new feature allows us to take advantage of it even if the partial lookup is dynamic. So, in the scenario where all the activities are of the same kind, the template lookup will happen just once and no longer 30 times. In other words, best case scenario becomes O(1), worst case scenario is still O(n).
5) Filtered chain logging is back
Another very small change that will make development more pleasant is that Rails will now log “Filter chain halted as CALLBACK_NAME rendered or redirected” every time a before/around/after filter in your controller halts the request. This was the case in Rails 2.3 but somehow got lost when Rails 3 came out. It is one of those small things you don’t know how much you missed until you see it again!
And what is your favorite Rails 3.2 “hidden” feature? Don’t forget to take a good look at the CHANGELOGs and check out many others improvements!
Tags: crafting rails applications, exception handling, rails 3.2
Posted in English | 14 Comments »

All
English only
Em português apenas