Hobo Scopes

Hobo scopes are an extension of the named scope and dynamic finder functionality introduced in Rails 2.1, 2.2 and 2.3.

Most of these scopes work by calling named_scope the first time they are invoked. They should work at the same speed as a named scope on subsequent invocations.

However, this does substantially slow down method_missing on your model’s class. If ActiveRecord::Base.method_missing is used often, you may wish to disable this module. (FIXME: how to do that)

Contents

Let’s set up a few models for our testing:

>> File.open("#{Rails.root}/app/models/person.rb", "w") do |f|
 f.write("""
 class Person < ActiveRecord::Base
   hobo_model

   fields do
     name :string
     born_at :date
     code :integer
     male :boolean
     timestamps
   end
   attr_accessible :name, :born_at, :code, :male

   lifecycle(:key_timestamp_field => false) do
     state :inactive, :active
   end

   has_many :friendships
   has_many :friends, :through => :friendships
 end
 """)
 end
>> File.open("#{Rails.root}/app/models/friendship.rb", "w") do |f|
 f.write("""
 class Friendship < ActiveRecord::Base
   hobo_model
   fields
   attr_accessible :person, :friend
   belongs_to :person
   belongs_to :friend, :class_name => 'Person'
 end
 """)
 end

And create a couple of fixtures:

>>
 Bryan = Person.new(:name => "Bryan", :code => 17,
    :born_at => Date.new(1973,4,8), :male => true)
>> Bryan.state = "active"
>> Bryan.save!
>>
 Bethany = Person.new(:name => "Bethany", :code => 42,
    :born_at => Date.new(1975,5,13), :male => false)
>> Bethany.state = "inactive"
>> Bethany.save!
>> Friendship.new(:person => Bryan, :friend => Bethany).save!

Hack the created_at column to get predictable sorting.

>> Bethany.created_at = Date.new(2000)
>> Bethany.save!

We’re ready to get going.

Simple Scopes

_is

Most Hobo scopes work by appending an appropriate query string to the field name. In this case, the hobo scope function name is the name of your database column, followed by _is. It returns an Array of models.

It works the same as a dynamic finder:

>> Person.find_all_by_name("Bryan").*.name
=> ["Bryan"]
>> Person.name_is("Bryan").*.name
=> ["Bryan"]
>> Person.code_is(17).*.name
=> ["Bryan"]
>> Person.code_is(99).length
=> 0

_is_not

But the Hobo scope form allows us to supply several variations

>> Person.name_is_not("Bryan").*.name
=> ["Bethany"]

_contains

Case insensitive.

>> Person.name_contains("y").*.name
=> ["Bryan", "Bethany"]

_does_not_contain

Case insensitive.

>> Person.name_does_not_contain("b").*.name
=> []

_starts

Case insensitive.

>> Person.name_starts("b").*.name
=> ["Bryan", "Bethany"]

_does_not_start

Case insensitive.

>> Person.name_does_not_start("B").length
=> 0

_ends

>> Person.name_ends("y").*.name
=> ["Bethany"]

_does_not_end

>> Person.name_does_not_end("y").*.name
=> ["Bryan"]

Boolean scopes

_

If you use the name of the column by itself, the column is of type boolean, and no function is already defined on the model class with the name, Hobo scopes adds a dynamic finder to return all records with the boolean column set to true

>> Person.male.*.name
=> ["Bryan"]

not_

You can also search for boolean records that are not true. This includes all records that are set to false or NULL.

>> Person.not_male.*.name
=> ["Bethany"]

Date scopes

Date scopes work only with columns that have a name ending in “_at”. The “_at” is omitted when using these finders.

_before

>> Person.born_before(Date.new(1974)).*.name
=> ["Bryan"]

_after

>> Person.born_after(Date.new(1974)).*.name
=> ["Bethany"]

_between

>> Person.born_between(Date.new(1974), Date.today).*.name
=> ["Bethany"]

Lifecycle scopes

If you have a lifecycle defined, each state name can be used as a dynamic finder.

>> Person.active.*.name
=> ["Bryan"]

Key scopes

This isn’t very useful:

>> Person.is(Bryan).*.name
=> ["Bryan"]

But this is:

>> Person.is_not(Bryan).*.name
=> ["Bethany"]

Static scopes

These scopes do not contain the column name.

by_most_recent

Sorting on the created_at column:

>> Person.by_most_recent.*.name
=> ["Bryan", "Bethany"]

recent

Gives the N most recent items:

>> Person.recent(1).*.name
=> ["Bryan"]

limit

>> Person.limit(1).*.name
=> ["Bryan"]

order_by

>> Person.order_by(:code).*.name
=> ["Bryan", "Bethany"]

include

DEPRECATED: Automatic scope :include has been deprecated: use :includes instead.

includes

>> Person.search("B", :name).includes(:friends).*.name  # test LH#839
=> ["Bryan", "Bethany"]

>> Person.includes(:friends).*.name
=> ["Bryan", "Bethany"]

Search for text in the specified column(s).

>> Person.search("B", :name).*.name
=> ["Bryan", "Bethany"]

Association Scopes

with_

Find the records that contain the specified record in an association

>> Person.with_friendship(Friendship.first).*.name
=> ["Bryan"]
>> Person.with_friend(Bethany).*.name
=> ["Bryan"]

You can also specify multiple records with the plural form

>> Person.with_friends(Bethany, nil).*.name
=> ["Bryan"]

any_of_

This works on plural associations to find a model associated with any of the arguments

>> Person.any_of_friends(Bethany, Bryan).*.name
=> ["Bryan"]

without_

>> Person.without_friend(Bethany).*.name
=> ["Bethany"]
>> Person.without_friends(Bethany, nil).*.name
=> ["Bethany"]

_is

You can use _is on a :has_one or a :belongs_to relationship:

>> Friendship.person_is(Bryan).*.friend.*.name
=> ["Bethany"]

_is_not

>> Friendship.person_is_not(Bryan)
=> []

Scoping Associations

When defining an association, you can add a scope:

>>
 class Person
   has_many :active_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => :active
   has_many :inactive_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => :inactive
 end

>> Bryan.inactive_friends.*.name
=> ["Bethany"]
>> Bryan.active_friends.*.name
=> []

or several scopes:

>>
 class Person
   has_many :inactive_female_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => [:inactive, :not_male]
   has_many :active_female_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => [:active, :not_male]
   has_many :inactive_male_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => [:inactive, :male]
 end

>> Bryan.inactive_female_friends.*.name
=> ["Bethany"]
>> Bryan.active_female_friends.*.name
=> []
>> Bryan.inactive_male_friends.*.name
=> []

You can parameterize the scopes:

>>
 class Person
   has_many :y_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => { :name_contains => 'y' }
   has_many :z_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => { :name_contains => 'z' }
 end

>> Bryan.y_friends.*.name
=> ["Bethany"]
>> Bryan.z_friends.*.name
=> []

Chaining

Like named scopes, Hobo scopes can be chained:

>> Bryan.inactive_friends.inactive.*.name
=> ["Bethany"]

Edit this page