Chaining ActiveRecord::Relation Methods in Rails 5

Objective

To learn the alternative to with_scope that is no longer available in Rails 5.

Steps

Step 1

The task model has a find_incomplete class method that finds all the incomplete tasks ordered by most recent first.

class Task < ApplicationRecord
  belongs_to :project

  def self.find_incomplete
    where(complete: false).order('created_at DESC')
  end
end

Step 2

In the rails console, we can call it.

Task.find_incomplete
  Task Load (0.2ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."complete" = ? ORDER BY created_at DESC  [["complete", false]]
 => #<ActiveRecord::Relation [#<Task id: 3, name: "Buy a puppy", complete: false, created_at: "2016-03-10 00:48:39", updated_at: "2016-03-10 00:48:39">, #<Task id: 1, name: "Get rich quick", complete: false, created_at: "2016-03-10 00:48:39", updated_at: "2016-03-10 00:48:39">]> 

This returns an ActiveRecord::Relation object.

Step 3

Can we use with_scope to limit the number of results in the query?

def self.find_incomplete(options = {})
  with_scope find: options do
    where(complete: false).order('created_at DESC')
  end
end

No, we cannot, it gives an error:

NoMethodError: undefined method `with_scope' for Task (call 'Task.connection' to establish a connection):Class
Did you mean?  with_options

The with_scope was removed from Rails 4. Last version that had this method was Rails 3.1.

Step 4

We can chain the ActiveRecord::Relation methods to limit the result set.

class Task < ApplicationRecord
  belongs_to :project

  def self.find_incomplete(limit)
    where(complete: false).order('created_at DESC').limit(limit)
  end
end

Step 5

We can now limit the result set.

 Task.find_incomplete 1
   Task Load (0.2ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."complete" = ? ORDER BY created_at DESC LIMIT ?  [["complete", false], ["LIMIT", 1]]
  => #<ActiveRecord::Relation [#<Task id: 3, name: "Buy a puppy", complete: false, created_at: "2016-03-10 00:48:39", updated_at: "2016-03-10 00:48:39">]> 

You can use also use keyword arguments to make the interface more friendly:

class Task < ApplicationRecord
  belongs_to :project

  def self.find_incomplete(limit: 1)
    where(complete: false).order('created_at DESC').limit(limit)
  end
end

You can use the keyword arguments like this:

 Task.find_incomplete(limit: 2)
  Task Load (0.4ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."complete" = ? ORDER BY created_at DESC LIMIT ?  [["complete", false], ["LIMIT", 2]]
 => #<ActiveRecord::Relation [#<Task id: 7, name: "Buy a puppy", complete: false, created_at: "2016-03-10 01:28:27", updated_at: "2016-03-10 01:28:27", project_id: 4>, #<Task id: 5, name: "Get rich quick", complete: false, created_at: "2016-03-10 01:28:27", updated_at: "2016-03-10 01:28:27", project_id: 3>]>

Step 6

Let's create a project resource.

rails g scaffold project name:string

Step 7

Create sample data in seeds.rb.

wealthy = Project.create(name: 'Wealth Building')
wealthy.tasks.create(name: 'Get rich quick', complete: false)
wealthy.tasks.create(name: 'Write a book', complete: true)

happy = Project.create(name: 'Be Happy')
happy.tasks.create(name: 'Buy a puppy', complete: false)
happy.tasks.create(name: 'Dance in the rain', complete: true)

Step 8

Define the has_many declaration in the project model.

class Project < ApplicationRecord
  has_many :tasks
end

Step 9

Create the migration to add the project_id foreign key to the tasks table.

rails g migration add_project_id_to_tasks project_id:integer

Step 10

Create the table and populate the database.

rails db:migrate
rails db:seed

Step 11

We can now use the find_incomplete that limits the result set through the has_many association.

 p = Project.find 1
    Project Load (0.2ms)  SELECT  "projects".* FROM "projects" WHERE "projects"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   => #<Project id: 1, name: "Wealth Building", created_at: "2016-03-10 01:26:12", updated_at: "2016-03-10 01:26:12"> 
   > tasks = p.tasks.find_incomplete(1)
    Task Load (0.1ms)  SELECT  "tasks".* FROM "tasks" WHERE "tasks"."project_id" = ? AND "tasks"."complete" = ? ORDER BY created_at DESC LIMIT ?  [["project_id", 1], ["complete", false], ["LIMIT", 1]]
   => #<ActiveRecord::AssociationRelation []>

Summary

In this article, you learned how to chain ActiveRecord::Relation methods to limit result set.


Related Articles


Create your own user feedback survey