Hobo Scopes
{: .document-title}

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}

To test Hobo scopes, we're going to need a connection to the database
and some sample data.  This takes a few lines of code to set up when
starting from a blank irb session.
{.hidden}

First off we need to configure ActiveSupport for auto-loading
{.hidden}

    >> require 'rubygems'
    >> require 'active_support'
    >> require 'active_record'
    >> require 'action_pack'
    >> require 'action_view'
    >> require 'action_controller'
{.hidden}

We also need to get ActiveRecord set up with a database connection
{.hidden}

    >> mysql_adapter = defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql'
    >> mysql_user = 'root'; mysql_password = ''
    >> mysql_login = "-u #{mysql_user} --password='#{mysql_password}'"
    >> mysql_database = "hobofields_doctest"
    >> system "mysqladmin #{mysql_login} --force drop #{mysql_database} 2> /dev/null"
    >> system("mysqladmin #{mysql_login} create #{mysql_database}") or raise "could not create database"
    >> ActiveRecord::Base.establish_connection(:adapter => mysql_adapter,
                                               :database => mysql_database,
                                               :host => "localhost", 
                                               :username => mysql_user, 
                                               :password => mysql_password)
{.hidden}

Some load path manipulation:
{.hidden}

    >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobofields/lib')
    >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobosupport/lib')
    >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobo/lib')
{.hidden}

And we'll require hobo:
{.hidden}

    >> require 'will_paginate'
    >> require 'will_paginate/finder'
    >> require 'hobosupport'
    >> require 'hobofields'
    >> require 'hobo'
    >> Hobo::Model.enable
    >> HoboFields.enable
{.hidden}

Let's set up a few models for our testing:
 
    >>
     class Person < ActiveRecord::Base
       hobo_model

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

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

       has_many :friendships
       has_many :friends, :through => :friendships
     end
   
    >>
     class Friendship < ActiveRecord::Base
       hobo_model
       fields
       belongs_to :person
       belongs_to :friend, :class_name => "Person"
     end

Generate a migration and run it:
{.hidden}

    >> ActiveRecord::Migration.class_eval(HoboFields::MigrationGenerator.run[0])
    >> 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

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

## \_does\_not\_contain

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

## \_starts

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

## \_does\_not\_start

    >> 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

Adding the include function to your query chain has the same effect as
the `:include` option to the `find` method.  

    >> Person.include(: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"]

## 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"]

Clean up our test database:
{.hidden}

    >> system  "mysqladmin --force drop #{mysql_database} 2> /dev/null"
{.hidden}