Refactoring is crucial to any software project out there, as it is something that determines the flexibility and maintainability of a project. And when it comes to Rails, it’s no different; it offers refactoring capabilities and one of its features that enforce refactoring in Rails is the usage of scopes.

Scopes are great for using methods that fetch data for you, and you need to use those methods over and over in your project. But that’s not the only reason to use ’em.

Following is how we define a scope in Rails:

class User < ActiveRecord::Base
 scope :unactive, -> { where.not(active: true) }
end

As you can see from the above example that scope is simply a method that accepts two attributes. One is a symbol that would actually be the name of the scope through which it will be called. The other is a lambda that will be invoked each time you call that scope.

Calling a scope is much like calling a class method like following:

User.unactive # equivalent to calling User.where.not(active : true)

Let’s define a little complex scope:

class User < ActiveRecord::Base
 scope :of_age, ->(age) { where.('age = ?', age) if age.pr
esent? } 
end

This scope picks up an age variable as an agrument and returns all the user objects whose age matches with the input. But what happens if we call the scope like following:

age = nil
User.of_age(age) # age is `nil` here.

Well, if we pass nil as an agrument, it’s gonna return us back all the objects. But if were using a class method instead, this wouldn’t happen:

class User < ActiveRecord::Base 
 def self.of_age(age)
  where('age = ?', age) if age.present?
 end
end

We’d get back an empty array of type: ActiveRecord::Relation and it’s not gonna contain any object. To make self.of_age class method behave like scope, we’d have to use the following methodology:

Class User < ActiveRecord::Bae 
 def self.of_age(age)
  if age.present?
   where('age = ?', age)
  else 
   all
  end 
 end
end

This is how we’d accomplish the same functionality as of scope if were using a class method instead. Obviously, a little more work to do than using a scope.

Before we further dive into more specific usages of using class methods over scopes, let’s first discuss a few advantages of using scopes in your code.

Scopes can be chained:

You read it right, scopes can be chained. If you have multiple scopes, you can chain them together like following:

User.active.of_age(24).admin

It’s gonna return all the users who are active, whose age is 24 and who are admin. Be wary of the fact that if a chain doesn’t return the same object, it can break the chaining process.

scope :i_will_break_chaining, -> { "Break..." }

Like if you have scope defined like above, that simply returns a string, it will break your chaining, and you will not be able to chain your scopes anymore.

Class methods come handy when your code contains complex logic, chaining of a few scopes, or it fetches data from sources other than the database of your application, like Reids, or an external API. Class methods also allow you to use super if your logic needs the same behavior as of super class, but with extra chunk of functionality.

It’s always a good idea to use scopes for selecting, sorting, joining or filtering data through your database, and then you can use class methods to chain those scopes, or even write more complex logic. Whether you use class methods or scopes, both give flexibility to your code, and at the same time, they increase the readability of your code which in turn is the essence of writing good piece of software. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *