arel_operators 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +65 -0
- data/lib/active_record/operators.rb +53 -0
- data/lib/arel_operators.rb +17 -0
- data/spec/active_record/operators_spec.rb +49 -0
- metadata +81 -0
data/README
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
AR Operators
|
2
|
+
|
3
|
+
Don't let me wrong, it's not as I hate SQL. I just hate to create complex queries when ActiveRecord
|
4
|
+
could do this for me, for free.
|
5
|
+
|
6
|
+
Enter AR Operators.
|
7
|
+
|
8
|
+
Imagine you're finding people. You want everybody named "John" OR "Smith", BUT don't want anyone who is underaged.
|
9
|
+
So, in ActiveRecord, you could do something like:
|
10
|
+
|
11
|
+
Person.all :conditions => [
|
12
|
+
'(name LIKE ? OR name LIKE ?) AND age >= 18', '%John%', '%Smith%'
|
13
|
+
]
|
14
|
+
|
15
|
+
But this is tedious. One of the great advantages of ActiveRecord 3 is use of scopes, so you could:
|
16
|
+
|
17
|
+
Person.where(['(name LIKE ? OR name LIKE ?)', '%John%', '%Smith%']).where('age >= 18')
|
18
|
+
|
19
|
+
Better. But why not:
|
20
|
+
|
21
|
+
johns = Person.where(['name like ?', '%John%'])
|
22
|
+
smiths = Person.where(['name like ?', '%Smith%'])
|
23
|
+
underageds = Person.where('age < 18')
|
24
|
+
(johns | smiths) - underageds
|
25
|
+
|
26
|
+
ENTER AR Operators
|
27
|
+
|
28
|
+
This library brings operators to ActiveRecord 3. So, all you have to do is:
|
29
|
+
require 'ar_operators'
|
30
|
+
class Person < ActiveRecord::Base
|
31
|
+
extend AROperators
|
32
|
+
end
|
33
|
+
|
34
|
+
And you're ready to go. Right now, the following operators are implemented:
|
35
|
+
| (OR)
|
36
|
+
& (AND)
|
37
|
+
- (AND (NOT ...))
|
38
|
+
-@ (negates the query. Use like: -Person.where(:name => 'foo'), to find all people where name is not 'foo')
|
39
|
+
|
40
|
+
There is also the following constructions:
|
41
|
+
p1 = Person.where :name => 'Foo'
|
42
|
+
p2 = Person.where :age => 18
|
43
|
+
p1.where(p2) #Generates something like: SELECT * FROM people WHERE ((name = 'Foo') AND (id in SELECT id FROM people WHERE age = 18))
|
44
|
+
|
45
|
+
Design Decision:
|
46
|
+
To not monkey patch ActiveRecord::Relation, I decided to include these operators only when you
|
47
|
+
use "where" or "scoped" to find objects. If you decide that ALL ActiveRecord operations should
|
48
|
+
have this kind of behaviour, you can:
|
49
|
+
|
50
|
+
class ActiveRecord::Relation
|
51
|
+
include ActiveRecord::Operators
|
52
|
+
end
|
53
|
+
|
54
|
+
Known issues:
|
55
|
+
As ActiveRecord::Relation doesn't only include "where" clauses, there can be a strange behaviour if trying
|
56
|
+
to combinate more behaviours. For instance, this kind of query:
|
57
|
+
p1 = Person.where(:name => 'Foo').limit(10)
|
58
|
+
p2 = Person.where(:age => 18).order('name').limit(20)
|
59
|
+
(p1 | p2).to_sql
|
60
|
+
|
61
|
+
Will generate:
|
62
|
+
SELECT "people".* FROM "people" WHERE ((("people"."name" = 'Foo') OR ("people"."age" = 18))) LIMIT 10
|
63
|
+
|
64
|
+
So, if you don't want to fall in undefined behaviours, please use:
|
65
|
+
(p1 | p2).limit(20).order('name')
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Operators
|
3
|
+
def |(other)
|
4
|
+
build_arel_conditions build_predicate(Arel::Predicates::Or, where_values, other.where_values)
|
5
|
+
end
|
6
|
+
|
7
|
+
def &(other)
|
8
|
+
build_arel_conditions build_predicate(Arel::Predicates::And, where_values, other.where_values)
|
9
|
+
end
|
10
|
+
|
11
|
+
def -(other)
|
12
|
+
self & (-other)
|
13
|
+
end
|
14
|
+
|
15
|
+
def -@
|
16
|
+
build_arel_conditions build_predicate(Arel::Predicates::Not, where_values)
|
17
|
+
end
|
18
|
+
|
19
|
+
def where(obj, *args)
|
20
|
+
if obj.respond_to?(:build_where)
|
21
|
+
relation = obj.select(:id)
|
22
|
+
self & build_arel_conditions(Arel::Predicates::In.new(relation.primary_key, relation.arel))
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def build_arel_conditions(condition)
|
30
|
+
clone.tap do |c|
|
31
|
+
cond = build_where(condition)
|
32
|
+
c.where_values = Array.wrap(cond)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_predicate(predicate, *values)
|
37
|
+
values = values.collect do |v|
|
38
|
+
wrap_predicate_value(v)
|
39
|
+
end
|
40
|
+
predicate.new(*values)
|
41
|
+
end
|
42
|
+
|
43
|
+
def wrap_predicate_value(value)
|
44
|
+
value.collect do |v|
|
45
|
+
if v.is_a?(String)
|
46
|
+
Arel::SqlLiteral.new(v)
|
47
|
+
else
|
48
|
+
v
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "active_record/operators"
|
2
|
+
module ArelOperators
|
3
|
+
def where(args, *opts)
|
4
|
+
include_operators_on super
|
5
|
+
end
|
6
|
+
|
7
|
+
def scoped(*args)
|
8
|
+
include_operators_on super(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def include_operators_on(relation)
|
12
|
+
metaclass = class << relation; self; end
|
13
|
+
metaclass.send :include, ActiveRecord::Operators
|
14
|
+
return relation
|
15
|
+
end
|
16
|
+
private :include_operators_on
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../helper')
|
2
|
+
require "ruby_debug"
|
3
|
+
|
4
|
+
describe ActiveRecord::Operators do
|
5
|
+
it 'Should "or" two conditions' do
|
6
|
+
arel = Person.where(:id => 190) | Person.where(:id => 210)
|
7
|
+
result = arel.to_sql
|
8
|
+
result.should match(/or/i)
|
9
|
+
result.should match(/210/i)
|
10
|
+
result.should match(/190/i)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'Should "and" two conditions' do
|
14
|
+
arel = Person.where(:id => 190) & Person.where(:id => 210)
|
15
|
+
result = arel.to_sql
|
16
|
+
result.should match(/and/i)
|
17
|
+
result.should match(/210/i)
|
18
|
+
result.should match(/190/i)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should negate the condition' do
|
22
|
+
arel = -Person.where(:id => 190)
|
23
|
+
result = arel.to_sql
|
24
|
+
result.should match(/not/i)
|
25
|
+
result.should match(/190/i)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should subtract two conditions' do
|
29
|
+
arel = Person.where(:id => 190) - Person.where(:id => 210)
|
30
|
+
result = arel.to_sql
|
31
|
+
result.should match(/and.*not/i)
|
32
|
+
result.should match(/210/i)
|
33
|
+
result.should match(/190/i)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should create subselects' do
|
37
|
+
foo = Person.create! :name => 'Foo'
|
38
|
+
bar = Person.create! :name => 'Bar'
|
39
|
+
baz = Person.create! :name => 'Baz'
|
40
|
+
select = Person.where :name => ['Foo', 'Baz']
|
41
|
+
subselect = Person.where :name => ['Baz', 'Bar']
|
42
|
+
result = select.where(subselect)
|
43
|
+
result.should include(baz)
|
44
|
+
result.should_not include(foo)
|
45
|
+
result.should_not include(bar)
|
46
|
+
sql = result.to_sql
|
47
|
+
sql.scan(/select/i).should have(2).matches
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: arel_operators
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- "Maur\xC3\xADcio Szabo"
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-01 00:00:00 -03:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activerecord
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 3
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
version: 3.0.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description:
|
36
|
+
email: mauricio.szabo@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README
|
43
|
+
files:
|
44
|
+
- lib/arel_operators.rb
|
45
|
+
- lib/active_record/operators.rb
|
46
|
+
- README
|
47
|
+
- spec/active_record/operators_spec.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/mauricioszabo/arel_operators
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.7
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Operators (|, &, -) for ActiveRecord.
|
80
|
+
test_files:
|
81
|
+
- spec/active_record/operators_spec.rb
|