Hobo Scopes
doctest: prepare testapp environment
doctest_require: '../prepare_testapp'
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
- Simple Scopes
- Boolean scopes
- Date scopes
- Lifecycle scopes
- Key scopes
- Static scopes
- Association Scopes
- Scoping Associations
- Chaining
This document was created using the tool rubydoctest. This means that this file serves as both documentation and test. As a side effect, it also ensures that errors do not creep into the sample code in this documentation.
The idea behind rubydoctest is that you should be able to recreate everything in this document from an irb console. It is recommended that you skip down to the fixture definitions. Nobody but the computer needs to read the rest of this section.
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
Generate a migration and run it:
>> system("rails g hobo:migration -m -n")
>> ActionDispatch::Reloader.cleanup!
>> ActionDispatch::Reloader.prepare!
>> Person.connection.clear_cache!
>> Person.connection.schema_cache.clear!
>> Person.reset_column_information
>> Person.columns.*.name
=> ["id", "name", "born_at", "code", "male", "created_at", "updated_at", "state"]
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
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