{"id":3354,"date":"2013-02-07T15:51:06","date_gmt":"2013-02-07T17:51:06","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=3354"},"modified":"2013-02-07T15:51:06","modified_gmt":"2013-02-07T17:51:06","slug":"active-record-scopes-vs-class-methods","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2013\/02\/active-record-scopes-vs-class-methods\/","title":{"rendered":"Active Record scopes vs class methods"},"content":{"rendered":"

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<\/a>. The classic comment usually boils down to “there is no difference between them”<\/em> or “it is a matter of taste”<\/em>. I tend to agree with both sentences, but I’d like to show some slight differences that exist between both.<\/p>\n

Defining a scope<\/h2>\n

First of all, lets get a better understanding about how scopes are used. In Rails 3 you can define a scope in two ways:<\/p>\n

\nclass Post < ActiveRecord::Base\n  scope :published, where(status: 'published')\n  scope :draft, -> { where(status: 'draft') } \nend\n<\/pre>\n

The main difference between both usages is that the :published<\/code> condition is evaluated when the class is first loaded, whereas the :draft<\/code> 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:<\/p>\n

\nclass Post < ActiveRecord::Base\n  scope :published_last_week, where('published_at >= ?', 1.week.ago)\nend\n<\/pre>\n

Because this won’t work as expected: 1.week.ago<\/code> will be evaluated when the class is loaded, not every time the scope is called.<\/p>\n

Scopes are just class methods<\/h2>\n

Internally Active Record converts a scope into a class method. Conceptually, its simplified implementation in Rails master looks something like this:<\/p>\n

\ndef self.scope(name, body)\n  singleton_class.send(:define_method, name, &body)\nend\n<\/pre>\n

Which ends up as a class method with the given name and body, like this:<\/p>\n

\ndef self.published\n  where(status: 'published')\nend\n<\/pre>\n

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?”<\/em>. So here are some interesting examples for you to think about.<\/p>\n

Scopes are always chainable<\/h2>\n

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:<\/p>\n

\nclass Post < ActiveRecord::Base\n  scope :by_status, -> status { where(status: status) }\n  scope :recent, -> { order(\"posts.updated_at DESC\") }\nend\n<\/pre>\n

And we can call them freely like this:<\/p>\n

\nPost.by_status('published').recent\n# SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"status\" = 'published' \n#   ORDER BY posts.updated_at DESC\n<\/pre>\n

Or with a user provided param:<\/p>\n

\nPost.by_status(params[:status]).recent\n# SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"status\" = 'published' \n#   ORDER BY posts.updated_at DESC\n<\/pre>\n

So far, so good. Now lets move them to class methods, just for the sake of comparing:<\/p>\n

\nclass Post < ActiveRecord::Base\n  def self.by_status(status)\n    where(status: status)\n  end\n  \n  def self.recent\n    order(\"posts.updated_at DESC\")\n  end\nend\n<\/pre>\n

Besides using a few extra lines, no big improvements. But now what happens if the :status<\/code> parameter is nil<\/code> or blank<\/code>?<\/p>\n

\nPost.by_status(nil).recent\n# SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"status\" IS NULL \n#   ORDER BY posts.updated_at DESC\n\nPost.by_status('').recent\n# SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"status\" = '' \n#   ORDER BY posts.updated_at DESC\n<\/pre>\n

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:<\/p>\n

\nscope :by_status, -> status { where(status: status) if status.present? }\n<\/pre>\n

There we go:<\/p>\n

\nPost.by_status(nil).recent\n# SELECT \"posts\".* FROM \"posts\" ORDER BY posts.updated_at DESC\n\nPost.by_status('').recent\n# SELECT \"posts\".* FROM \"posts\" ORDER BY posts.updated_at DESC\n<\/pre>\n

Awesome. Now lets try to do the same with our beloved class method:<\/p>\n

\nclass Post < ActiveRecord::Base\n  def self.by_status(status)\n    where(status: status) if status.present?\n  end\nend\n<\/pre>\n

Running this:<\/p>\n

\nPost.by_status('').recent\nNoMethodError: undefined method `recent' for nil:NilClass\n<\/pre>\n

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:<\/p>\n

\ndef self.by_status(status)\n  if status.present?\n    where(status: status)\n  else\n    all\n  end\nend\n<\/pre>\n

Notice that I'm returning all<\/code> for the nil\/blank<\/code> 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<\/code> there instead. And there we go:<\/p>\n

\nPost.by_status('').recent\n# SELECT \"posts\".* FROM \"posts\" ORDER BY posts.updated_at DESC\n<\/pre>\n

So the advice here is: never return nil<\/code> 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.<\/p>\n

Scopes are extensible<\/h2>\n

Lets get pagination as our next example and I'm going to use the kaminari<\/a> gem as basis. The most important thing you need to do when paginating a collection is to tell which page you want to fetch:<\/p>\n

\nPost.page(2)\n<\/pre>\n

After doing that you might want to say how many records per page you want:<\/p>\n

\nPost.page(2).per(15)\n<\/pre>\n

And you may to know the total number of pages, or whether you are in the first or last page:<\/p>\n

\nposts = Post.page(2)\nposts.total_pages # => 2\nposts.first_page? # => false\nposts.last_page?  # => true\n<\/pre>\n

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<\/code> scope to your Active Record models, and relies on the scope extensions feature to add all other functionality when page<\/code> is called<\/a>. Conceptually, the code would look like this:<\/p>\n

\nscope :page, -> num { # some limit + offset logic here for pagination } do\n  def per(num)\n    # more logic here\n  end\n  \n  def total_pages\n    # some more here\n  end\n  \n  def first_page?\n    # and a bit more\n  end\n  \n  def last_page?\n    # and so on\n  end\nend\n<\/pre>\n

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:<\/p>\n

\ndef self.page(num)\n  scope = # some limit + offset logic here for pagination\n  scope.extend PaginationExtensions\n  scope\nend\n\nmodule PaginationExtensions\n  def per(num)\n    # more logic here\n  end\n  \n  def total_pages\n    # some more here\n  end\n  \n  def first_page?\n    # and a bit more\n  end\n  \n  def last_page?\n    # and so on\n  end\nend\n<\/pre>\n

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.<\/p>\n

Wrapping up<\/h2>\n

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.<\/p>\n

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<\/em>\u2122, or the tool that makes you more comfortable<\/em>. 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.<\/p>\n

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.<\/p>\n","protected":false},"excerpt":{"rendered":"

One of the recent topics in our discussions at Plataformatec 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. This is an attempt to show the differences between scopes and class methods, to help you understanding what scopes can give you for free and deciding what makes you feel more comfortable when writing your code.<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[106,7,170,176,108],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/3354"}],"collection":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=3354"}],"version-history":[{"count":22,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/3354\/revisions"}],"predecessor-version":[{"id":3386,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/3354\/revisions\/3386"}],"wp:attachment":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=3354"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=3354"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=3354"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}