Hobo Scopes {: .document-title} doctest: prepare testapp environment doctest_require: '../prepare_testapp' {.hidden} 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 {: .contents-heading} - contents {:toc} This document was created using the tool [rubydoctest](http://github.com/tablatom/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. {.hidden} 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](#fixture_definition). Nobody but the computer needs to read the rest of this section. {.hidden} 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: {.hidden} >> 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"] {.hidden} 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](/manual/lifecycles) 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"] {.hidden}