Learn Ruby on Rails Book

Class methods, scopes, and other goodies

Class methods

Class methods are methods that are invoked using a class name itself. These methods provide the functionality to a class itself.

Let's take an example:

1class ClassName
2  def self.class_method_name
3    # some code
4  end
5end

The code above is the same as below:

1class ClassName
2  class << self
3    self.class_method_name
4      # some code
5    end
6  end
7end

In the above example, we can access the class method directly without creating any instance of that class.

The self keyword refers to the entire class itself, and not to an instance of the class.

The class << self style is often preferred when our class methods are large and when we need a clear separation between class and instance methods.

All the methods defined inside class << self scope is automatically a class method.

Let's see an example:

1class SayHello
2  def self.from_the_class
3    "Hello, from a class method"
4  end
5
6  def from_an_instance
7    "Hello, from an instance method"
8  end
9end

We can test it out from Rails console:

1>> SayHello.from_the_class
2=> "Hello, from a class method"
3
4>> SayHello.from_an_instance
5=> undefined method `from_an_instance' for SayHello:Class
6
7>> hello = SayHello.new
8>> hello.from_an_instance
9=> "Hello, from an instance method"
10
11>> hello.from_the_class
12=> undefined method `from_the_class' for #<SayHello:0x0000557920dac930>

In the above example, we can see that, to use class methods we are not required to create an instance of the class.

We can directly query the class methods using the class name itself.

Please don't confuse "class methods" as methods defined within a class.

Please test out the above mentioned examples to understand the differences between class methods and instance methods.

Active Record model scopes

Scopes are custom queries and works very similar to a class method.

Scopes are defined inside our Rails models using the scope method.

Every scope takes two arguments:

  • A name, which we can use to call this scope in our code.
  • A lambda function, which implements the query.

Let's see an example:

1class Fruit < ApplicationRecord
2  scope :with_juice, -> { where("juice > 0") }
3end

The value returned when calling a scope is the ActiveRecord::Relation object, which in turn helps us to avoid errors when our scope returns nothing.

We can also chain and combine scopes.

Let's see an example:

1Fruit.with_juice.with_round_shape.first(3)

Which one to prefer

It's all about consistency. Scopes are usually used when the logic is very small or, for simple where/order clauses.

Class methods are used when it involves a bit more complexity, and when we need a finer grain of control over the execution of queries.

Let's take an example to see when to use scope method:

1def index
2  @books = Book.where("LENGTH(title) > 20")
3end

In the above code, there is an index controller action that wants to display books with titles longer than 20 characters.

The code is fine but it's not reusable yet.

Thus writing queries like these in controllers makes the code not reusable.

Let’s move this query into a scope:

1class Book
2  scope :with_long_title, -> { where("LENGTH(title) > 20") }
3end

Now our controller action looks like this:

1def index
2  @books = Book.with_long_title
3end

We could do the same thing using class methods:

1class Book
2  def self.with_long_title
3    where("LENGTH(title) > 0")
4  end
5end

As we can see from the above example, we did the same thing using a class methods.

Although this class method does the job, we usually give preference for scope for such simple queries.

The following is an example of where class methods come in handy:

1class Book < ActiveRecord::Base
2  def self.by_audience(audience)
3    if audience == "children"
4      where("age < 13")
5    else
6      where("age >= 13")
7    end
8  end
9end

Why to prefer carriers over helpers

The given examples are for understanding purpose, and thus we don't need to create them in our granite app.

Carriers are plain Ruby objects which allow us to encapsulate complex business logic in simple Ruby objects.

Let's take an example:

1# app/helpers/users_helper.rb
2
3module UsersHelper
4  def class_for_user user
5    if @user.manager?
6      'hidden'
7    elsif @user.super_admin?
8      'active'
9    end
10  end
11end
1# app/views/layouts/users_helper.erb
2
3<h3 class="<%= class_for_user(@user) %>">Hello, <%= @user.name %></h3>

The above solution is correct. However, in a large Rails application, it will start creating problems.

UsersHelper is a module and it is mixed into ApplicationHelper.

So if the Rails project has a large number of helpers then all of them are mixed into the ApplicationHelper, then sometimes there might be name collisions.

For example, let's say that there is another helper called ShowingHelper and this helper also has the method class_for_user.

Now ApplicationHelper is mixing in both modules UsersHelper and ShowingHelper.

One of those methods will be overridden and we would not even know about it.

Also, writing tests for helpers is possible but testing a module directly feels weird since most of the time we test a class.

Let us take a look at how we can revamp the view logic using carriers:

1class UserCarrier
2  attr_reader :user
3
4  def initialize user
5    @user = user
6  end
7
8  def user_message_style_class
9    if user.manager?
10      'hidden'
11    elsif user.super_admin?
12      'active'
13    end
14  end
15end

This is how a controller would invoke a Carrier:

1class UserController < ApplicationController
2  def show
3    @user = User.find(params[:id])
4    @user_carrier = UserCarrier.new @user
5  end
6end

Since instance variables are available in corresponding action's views, the view will look something like this:

1<% if @user.super_admin? %>
2  <%= link_to 'All Profiles', profiles_path %>
3<% elsif @user.manager?%>
4  <%= link_to 'Manager Profile', manager_profile_path %>
5<% end %>
6
7<h3 class="<%= @user_carrier.user_message_style_class %>">
8  Hello, <%= @user.name %>
9</h3>

Since the carriers are simple Ruby objects it's easy to test them:

1require 'test_helper'
2
3class UserCarrierTest < ActiveSupport::TestCase
4  fixture :users
5
6  def setup
7    manager = users(:manager)
8    @user_carrier = UserCarrier.new manager
9  end
10
11  def test_css_class_returned_for_manager
12    assert_equal 'hidden', @user_carrier.user_message_style_class
13  end
14end

Carriers allow us to encapsulate complex business logic in simple Ruby objects.

This helps us achieve clearer separation of concern, helps clean up our views and avoid skewed and complex views.

A rule of thumb to keep in mind on when to use Carriers, is to check whether the logic written in helpers plays a crucial role in the overall execution of the application or not.

If the logic is complex, then we should use Carriers, such that we can test it properly and ensure it won't break with input changes.

Active Record reload method and its usage

Reloading is commonly used in test suites to test something is actually written to the database, or when some action modifies the corresponding row in the database but not the object in memory.

Let's take an example, where we are going to test the change in the database level count for a particular table.

Let's say that we have a users table and each user has an association with a user_notifications table.

In this example, let's say we are invoking an asynchronous/background worker, named notification_worker, which will send a notification to a particular user, and add a new entry into user_notifications table for that user once notification is sent.

The aim of our example is to test whether that worker ,when invoked, will work properly or not.

In order to make sure that the worker executes successfully, we can check for new entries in user_notification or more accurately check for change in count of the user_notification table.

If we have a reference to a user via the instance variable @sam, then we can check for changes in count of associated user_notifications using the following test case:

1assert_difference -> { @sam.user_notifications.count }, 1 do
2  notification_worker.process
3end

We will talk more about background worker in the upcoming chapters.

The logic of the above test case boils down to testing whether there is a difference in the number of records within user_notifications of Sam, after the notification worker completes execution.

The above test case would work just fine without any issues.

Let's take another example where we use a background worker to update sam's name from "sam" to "samantha".

In our test case we might be tempted to write the test case like so:

1def test_background_worker_should_update_name
2  background_worker.process # updates Sam's name to samantha in DB
3  assert_equal @sam.name, "samantha"
4end

In the above code, if we use @sam.name, then it will be "sam" itself and will not reflect the latest update.

Hence our assertion will fail.

That's why it's a good practice to use @sam.reload.name instead of @sam.name to check the updated column values, like so:

1def test_background_worker_should_update_name
2  background_worker.process
3  assert_equal @sam.reload.name, "Samantha"
4end

Now, if we run the above test case, the assertion will pass.

You might be wondering why @sam.user_notifications.count worked and @sam.name didn't work.

The reason is that the .count method dynamically runs an SQL query to fetch count from corresponding database.

Thus even if we don't reload our instance variable, we will get the latest count from database itself.

But when using @sam.name, we are using @sam from our object memory.

During the creation of the ActiveRecord relation instance, Rails will pull all the latest values of the attributes and we can query them using @sam itself.

But once changes occur in database, we need to update our instance variable.

Thus we should call reload on it, in order to fetch latest data.

This is another reason why we prefer to use model name queries like UserNotification.where(user: 'sam').count in test cases.

This statement will always fetch latest database values, since we are directly querying DB.

We wil discuss more about writing test cases in our Unit Tests chapter.

The lib folder and its use cases

The only cases where we should use the lib folder are in these cases:

  • Overriding 3rd party library specifics or modules.

  • Overriding String class from Ruby.

  • For keeping logic shared by controllers, models as well as say configs or even Rake tasks.

  • The lib folder was used a lot in old Rails where all helpers were used to be written in this folder.

There is nothing to commit in this chapter.

⌘K
    to navigateEnterto select Escto close
    Previous
    Next