0.0
No commit activity in last 3 years
No release in over 3 years
Operators (|, &, -) for ActiveRecord.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

>= 3.0.0
 Project Readme
= AR Operators =

Don't let me wrong, it's not as I hate SQL. I just hate to create complex queries when ActiveRecord
could do this for me, for free.

For example, imagine you're finding people. You want everybody named "John" OR "Smith", 
BUT don't want anyone who is underaged. So, in ActiveRecord, you could do something like:

Person.all :conditions => [
    '(name LIKE ? OR name LIKE ?) AND age >= 18', '%John%', '%Smith%'
]

But this is tedious. One of the great advantages of ActiveRecord 3 is use of scopes, so you could:

Person.where(['(name LIKE ? OR name LIKE ?)', '%John%', '%Smith%']).where('age >= 18')

Better. But why not:

johns = Person.where(['name like ?', '%John%'])
smiths = Person.where(['name like ?', '%Smith%'])
underaged = Person.where('age < 18')
(johns | smiths) - underaged

== ENTER AR Operators ==

This library brings operators to ActiveRecord 3. So, all you have to do is:
require 'ar_operators'
class Person < ActiveRecord::Base
  extend ArelOperators
end

And you're ready to go. Right now, the following operators are implemented:
.or, |, + (OR)
.and (AND)
- (AND (NOT ...))
-@ (negates the query. Use like: -Person.where(:name => 'foo'), to find all people where name is not 'foo')

I cannot use & (amperstand operator) for AND, because this behaviour is already defined for ActiveRecord::Relation.

There is also the following constructions:
p1 = Person.where :name => 'Foo'
p2 = Person.where :age => 18
p1.where(p2) #Generates something like: SELECT * FROM people WHERE ((name = 'Foo') AND (id in SELECT id FROM people WHERE age = 18))

== Design Decision: ==
To not monkey patch ActiveRecord::Relation, I decided to include these operators only when you
use "where" or "scoped" to find objects. If you decide that ALL ActiveRecord operations should
have this kind of behaviour, you can:

class ActiveRecord::Relation
  include ArelOperators::Operators
end

== Finders ==
Ok, so we don't have to write tedious SQL operations by hand. What about trying a little further,
and not write ANY SQL at all? After all, we have blocks in Ruby, so what about:

Person.where { |p| p.name >= 10 }

ArelOperators, right now, permits this kind of behaviour only for where clauses (no "having" support
right now). The following operations are supported:
== (Test for equality)
!= (Test for inequality, Ruby 1.9 only)
>, >= (Test for Greater or Greather or Equal)
<, <= (Test for Lower, or Lower or equal)
in? (SQL IN clause, "WHERE name IN(1, 2, 3)" )
like?, matches?, =~ (SQL LIKE clause, "WHERE name LIKE 'something'")

So, as an example:

Person.where { |p| p.name.like?("F%") }
Person.where { |p| p.name =~ "F%" } #Same as above
Person.where { |p| p.age.in?([10, 20, 30]) } #SQL IN operator
Person.where { |p| p.age.in?(0..17) } #SQL BETWEEN operator

Furthermore, you can OR or AND your clauses with | and &. Ruby 1.9 "!" and  is also supported, and for
Ruby 1.8, you can use the negative ("-@") operator. For example:

Person.where { |p| (p.name != "Fred") | (p.age > 17) } #Ruby 1.9 version
Person.where { |p| !(p.name == "Fred") | (p.age > 17) } #Another Ruby 1.9 version
Person.where { |p| -(p.name == "Fred") | (p.age > 17) } #All ruby versions

The block argument is optional, too, so:

Person.where { name == "Foo" }
Person.where { name.like "F%" }
Person.where { (age <= 10) & (name =~ "F%) }

Please notice, that as & and | operators have a high precedence, don't forget the parenthesis,
otherwise undesired behaviours can occur.

== NOTICE ==
Regexp operations are NOT supported. Right now, I'm only delegating these operators
to Arel, and Arel still has no support for regexp.

== Known issues: ==
As ActiveRecord::Relation doesn't only include "where" clauses, there can be a strange behaviour if trying
to combinate more behaviours. For instance, this kind of query:
p1 = Person.where(:name => 'Foo').limit(10)
p2 = Person.where(:age => 18).order('name').limit(20)
(p1 | p2).to_sql

Will generate:
SELECT "people".* FROM "people"  WHERE ((("people"."name" = 'Foo') OR ("people"."age" = 18))) LIMIT 10

So, if you don't want to fall in undefined behaviours, please use:
(p1 | p2).limit(20).order('name')

Furthermore, beware of "OR" operator. Don't try this at home:
p1 = Person.where(:name => "Foo")
a1 = Address.where(:road => "Something")
(p1 | a1) #This will generate something like: 
SELECT     "people".* FROM       "people"  WHERE     ((("people"."name" = 'Foo') OR ("addresses"."road" = 'Something')))
So, as with & operator, you must join relations so your query would be correct.