Browse the Ruby on Rails Community.

You are here: Browse Railsplugins Criteriaquery

Criteriaquery

=CriteriaQuery

CriteriaQuery is an extension to the ActiveRecord find mechanism. It allows object-oriented construction of queries.

For bug reports, questions and suggestions please contact max_at_muermann_dot_org

The CriteriaQuery homepage is at http://www.muermann.org/ruby/criteria_query

The rubyforge project page can be found at http://rubyforge.org/projects/criteriaquery/

=Installation

script/plugin install criteria_query
From SVN

Unix: .script/plugin install svn://rubyforge.org/var/svn/criteriaquery

Windows: ruby script\plugin install svn://rubyforge.org/var/svn/criteriaquery

=Why is this useful? For simple queries, ActiveRecord’s sql-based finder mechanism is great. If all you are using in your application are statements like Person.find(1) or Person.find_by_name(‘some name’), you won’t need CriteriaQuery.

If you frequently face the problem of constructing complex dynamic queries, you will get some benefit out of this.

Consider the following search form (taken from a real application):

Name (first or last): [               ]
Customer Category:     Please Select 
Last call between:    [         ] and [         ]
Active Contacts only: [x]
Address
State:                [               ]
City:                 [               ]          
Street Addres:        [               ]

Or, even worse, dynamically constructed filters with a variable number of search criteria:

 Equals    -           
[+] Add condition
[Save Filter]

The code that generates the appropriate sql clauses for these kinds of search operation is often ugly and normally prone to fencepost errors, as you have to write special case code when concatenating optional OR or AND statements. For the first example:

conditions = "" 
par = []
includes = []
if params[:name]
  conditions << "(firstName=? OR lastName=?)" 
  par << params[:name]
  par << params[:name]
end
if params[:category]
  conditions << " AND " unless conditions.empty?
  conditions << "category_id=?" 
  par << params[:category]
end
...
if params[:address]
  includes << ["address"]
  if params[:address[:state]] 
    conditions << " AND " unless conditions.empty?
    conditions << "addresses.state=?" 
  par << params[:address[:state]]
  end
end
...

CriteriaQuery allows you to write the above as:

pq = Person.query
pq.disjunction.first_name_eq(params[:name]).last_name_eq(params[:name]) if params[:name]
pq.category_id_eq(params[:category]) if params[:category]
...
address = pq.join("address")
address.state_eq(params[:address[:state]]) if params[:address[:state]]
...
end
Query Expression Trees

When a sql statement is executed by a database server, the server will first parse the statement into a tree structure. Each node in the tree represents a SQL expression (AND, OR, +, =, LIKE, IN, etc..), and each leaf represents a field or a constant. The following where clause:

WHERE (a=1 AND (b=2 OR c<3>< c 3

If you think of the operands (=,<) and expressions like OR, AND as functions, the execution of the query looks like this:

AND, OR(=(b,2), <(c,3)) )

In the criteria_query plugin, compound expressions like AND, OR are specified in a similar prefixed manner:

pq = Person.query pq.or << pq.b_eq(2) << pq.c_lt(3)

Queries

A Query represents a tree of query conditions and joins and allows execution of the query via the ActiveRecord find method:

Person.query.find

will execute Person.find(:all).

You can pass parameters to the Query.find method similar to ActiveRecord.find:

Person.query.find(:limit=>10, :offset:20).

Note that if you pass :conditions and :include parameters, they will be overwritten by those generated by Query.

= Bring your own model

If you do not wish to use the find() method available on a query object, the conditions and parameters generated by the query can be used separately:

pq = Person.query
pq.name_like 'name'
pq.conditions => '(people.name LIKE ?)'
pq.parameters => ['name']
pq.find_options => ['(people.name LIKE ?)', 'name']

You can pass the output of find_options directly to any ActiveRecord finder method.

Expressions

A query is hardly useful unless there are some conditions restricting the results. You can add restricting expressions to a query by using magic meta methods:

Person.query.name_eq('some name').find

will execute Person.find(:all, :conditions=>[‘name=?’], ‘some_name’)

The expression methods normally return their parent restriction, so they can be chained. Expressions are combined with AND by default: Person.query.first_name_eq(‘firstname’).last_name_eq(‘lastname’).find

will execute Person.find(:all, c:conditions=>[‘first_name=? AND last_name=?’], ‘firstname’, ‘lastname’)

If you really want to, you can also construct and add the expression objects manually:

pq = Person.query
pq &lt;&lt; Eq.new('name', 'somename')
pq &lt;&lt; Gt.new('created_on', 'somedate')
pq.find

All expressions are included on each object which includes the Filterable module – which are all elements that can be added to the query tree:

pq = Person.query
pq.eq(name,value)
pq.gt('created_on', 'somedate')
pq.find

As both of these variations swap the verb and the subject (equals name ‘some_name’) instead of the more natural way (name equals ‘some_name’), using the magic meta methods is recommended. It also requires less typing.

=Available Expressions

Equals:: attribute_eq(value) Not equal:: attribute_ne(value) Like:: attribute_like(value) Case insensitive Like (Ilike):: attribute_ilike(value) Greater than:: attribute_gt(value) Greater than or equal:: attribute_gte(value) Less than:: attribute_lt(value) Less than or equal:: attribute_lte(value) Is null:: attribute_is_null Not null:: attribute_is_not_null In:: attribute_in([value, value, value,...])

Subrestrictions

There are some restrictions which can contain other expressions.

= Disjunction (OR) A disjunction is an “OR” clause in sql. All child expressions of a disjunction are combined with OR: Person.query.disjunction.first_name_eq(‘name’).last_name_eq(‘name’)

will execute Person.find(:all, :conditions=>[‘first_name=? OR last_name=?’, ‘name’, ‘name’])

The disjunction method is aliased to “or”: Person.query.or.first_name_eq(‘name’).last_name_eq(‘name’)

=== Conjunction (AND) A conjunction is an “AND” clause in sql. This expression only needs to be used in cases where nested AND clauses are required within an OR clause (disjunction):

pq = Person.disjunction
pq.conjunction.date_lt('some date').date_gt('some other date')
pq.active_eq(1)

will execute: Person.find(:all, :conditions=>[‘(date>? AND date<?) OR active=?’,’some date’, ‘some other date’, 1])

The conjunction method is aliased to “and”: pq.and.date_lt(‘some date’).date_gt(‘some other date’)

=== Negation The negation expression will wrap its children in a NOT:

Person.query.not.name_eq('name').active_eq(0)

will execute Person.find(:all, :conditions=>[‘NOT’, ‘name’, 0 ])

Joins You can use joins in your queries:

Person.query.name_like('name').join('address').city_like('city')

will execute: Person.find(:all, :conditions=>[‘people.name LIKE ? AND addresses.city LIKE ?’, ‘name’, ‘city’], :include=>[:city])

Joins across multiple relationship levels are also supported:

Person.query.name_like('name').join('address').city_like('city').join('state').name_eq('state')

will execute: Person.find(:all, :conditions=>[‘people.name LIKE ? AND addresses.city LIKE ? AND states.name=?’, ‘name’, ‘city’, ‘state’], :include=>[:city=>[:state]])

If a join is defined for a query, a method is created on the parent expression. The method name defaults to the name of the association, but can be overidden by passing an :as option to join():

pq = Person.query
pq.join :address
pq.address.street_eq('some_street')

And

pq = Person.query
pq.join :address, :as=>:lives_in
pq.lives_in.street_eq('some_street')

Via the magic method_missing method, all TableAlias objects (joins and queries) will attempt to create a join when an undefined method is called:

pq = Person.query
pq.city.name_eq('some_name')

Note that the object returned by Person.query.city.name_eq is the join on city, NOT the query object! This is necessary to enable chaining of expression methods.

= Compound Expression across multiple tables

If you need to construct a query that combines expressions on different joins in the same AND or OR clause, you can use the following:

pq = Person.query
pq.join(:lives_in)
pq.join(:works_in)
pq.or &lt;&lt; pq.lives_in.street_eq('some_street') &lt;&lt; pq.works_in.street_eq('some_street')

Will execute:

Person.find(:all, :conditions=>['(adresses.street=? OR people_addresses=?', 'some_street', 'some_street'], :include=>['lives_in','works_in'])

=== Multiple joins on the same table

CriteriaQueries support joining the same table multiple times. Consider the following models:

class Person &lt; ActiveRecord::Base
  belongs_to :city
  belongs_to :works_in, :class_name=>'City', :foreign_key=>'works_in_id'
end
class City &lt; ActiveRecord::Base
  belongs_to :state
  has_many :people
end
class State  &lt; ActiveRecord::Base
  has_many :cities
end

This will find all people who live in Sydney and work in Melbourne:

pq = Person.query
pq.join('city').name_eq('Sydney')
pq.join('works_in').name_eq('Melbourne')
pq.find

The following (contrived) example finds all people who live in the state that Sydney belongs to:

pq = Person.query
pq.join('city').join('state').join('cities').name_eq('Sydney')
pq.find

=== Polymorphic joins

Polymorphic joins are supported on the “has_one” or “has_many” side of the association. For the following models

class Address &lt; ActiveRecord::Base
  belongs_to :addressable, :polymorphic=>true
end
class Company &lt; ActiveRecord::Base
  has_many :addresses, :as=>:addressable
end

this query will work:

Company.query.join(:addresses).street_eq('some_street')

This, however, will not:

Address.query.join(:addressable).name_eq('some_name')

This is not a limitation of CriteriaQuery, but a fundamental design decision of ActiveRecord. It is also impossible to execute this query via a sql-based finder method. A slightly more verbose, but structurally nicer way of specifying subexpressions is through the use of blocks.

Block Notation

The following

pq = Person.query
pq.name_like('name')
address = pq.join('address')
address.or.street_1_like('street).street_2_like('street)

Can be written as:

pq = Person.query
pq.name_like('name')
pq.join('address') do |address|
  address.or do |streets|
    streets.street_1_like('street).street_2_like('street)
  end
end
Saving Queries Because a Query is just a tree structure of Ruby objects, it can be marshalled and unmarshalled. pq = Person.query.name_like(‘name’) marshalled = Marshal.dump(pq) restored = Marshal.restore(marshalled) restored.find

NOTE: This description has been extracted from the Plugin README and so the formatting may need updating to make browser friendly

Users


See all details


Membership

+ Join this railsplugin

Record Maintainer

'None'