arel_operators 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +73 -14
- data/lib/arel_operators.rb +19 -3
- data/lib/arel_operators/finder.rb +45 -0
- data/lib/arel_operators/finder/comparators.rb +32 -0
- data/lib/arel_operators/operators.rb +65 -0
- data/spec/arel_operators/finder/comparators_spec.rb +48 -0
- data/spec/arel_operators/finder_spec.rb +87 -0
- data/spec/arel_operators/joins_spec.rb +38 -0
- data/spec/{active_record → arel_operators}/operators_spec.rb +4 -5
- metadata +14 -6
- data/lib/active_record/operators.rb +0 -53
data/README
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
AR Operators
|
1
|
+
= AR Operators =
|
2
2
|
|
3
3
|
Don't let me wrong, it's not as I hate SQL. I just hate to create complex queries when ActiveRecord
|
4
4
|
could do this for me, for free.
|
5
5
|
|
6
|
-
|
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:
|
6
|
+
For example, imagine you're finding people. You want everybody named "John" OR "Smith",
|
7
|
+
BUT don't want anyone who is underaged. So, in ActiveRecord, you could do something like:
|
10
8
|
|
11
9
|
Person.all :conditions => [
|
12
10
|
'(name LIKE ? OR name LIKE ?) AND age >= 18', '%John%', '%Smith%'
|
@@ -20,38 +18,92 @@ Better. But why not:
|
|
20
18
|
|
21
19
|
johns = Person.where(['name like ?', '%John%'])
|
22
20
|
smiths = Person.where(['name like ?', '%Smith%'])
|
23
|
-
|
24
|
-
(johns | smiths) -
|
21
|
+
underaged = Person.where('age < 18')
|
22
|
+
(johns | smiths) - underaged
|
25
23
|
|
26
|
-
ENTER AR Operators
|
24
|
+
== ENTER AR Operators ==
|
27
25
|
|
28
26
|
This library brings operators to ActiveRecord 3. So, all you have to do is:
|
29
27
|
require 'ar_operators'
|
30
28
|
class Person < ActiveRecord::Base
|
31
|
-
extend
|
29
|
+
extend ArelOperators
|
32
30
|
end
|
33
31
|
|
34
32
|
And you're ready to go. Right now, the following operators are implemented:
|
35
|
-
|
36
|
-
|
33
|
+
.or, |, + (OR)
|
34
|
+
.and (AND)
|
37
35
|
- (AND (NOT ...))
|
38
36
|
-@ (negates the query. Use like: -Person.where(:name => 'foo'), to find all people where name is not 'foo')
|
39
37
|
|
38
|
+
I cannot use & (amperstand operator) for AND, because this behaviour is already defined for ActiveRecord::Relation.
|
39
|
+
|
40
40
|
There is also the following constructions:
|
41
41
|
p1 = Person.where :name => 'Foo'
|
42
42
|
p2 = Person.where :age => 18
|
43
43
|
p1.where(p2) #Generates something like: SELECT * FROM people WHERE ((name = 'Foo') AND (id in SELECT id FROM people WHERE age = 18))
|
44
44
|
|
45
|
-
Design Decision:
|
45
|
+
== Design Decision: ==
|
46
46
|
To not monkey patch ActiveRecord::Relation, I decided to include these operators only when you
|
47
47
|
use "where" or "scoped" to find objects. If you decide that ALL ActiveRecord operations should
|
48
48
|
have this kind of behaviour, you can:
|
49
49
|
|
50
50
|
class ActiveRecord::Relation
|
51
|
-
include
|
51
|
+
include ArelOperators::Operators
|
52
52
|
end
|
53
53
|
|
54
|
-
|
54
|
+
== Finders ==
|
55
|
+
Ok, so we don't have to write tedious SQL operations by hand. What about trying a little further,
|
56
|
+
and not write ANY SQL at all? After all, we have blocks in Ruby, so what about:
|
57
|
+
|
58
|
+
Person.where { |p| p.name >= 10 }
|
59
|
+
|
60
|
+
ArelOperators, right now, permits this kind of behaviour only for where clauses (no "having" support
|
61
|
+
right now). The following operations are supported:
|
62
|
+
== (Test for equality)
|
63
|
+
!= (Test for inequality, Ruby 1.9 only)
|
64
|
+
>, >= (Test for Greater or Greather or Equal)
|
65
|
+
<, <= (Test for Lower, or Lower or equal)
|
66
|
+
in? (SQL IN clause, "WHERE name IN(1, 2, 3)" )
|
67
|
+
like?, matches?, =~ (SQL LIKE clause, "WHERE name LIKE 'something'")
|
68
|
+
|
69
|
+
So, as an example:
|
70
|
+
|
71
|
+
Person.where { |p| p.name.like?("F%") }
|
72
|
+
Person.where { |p| p.name =~ "F%" } #Same as above
|
73
|
+
Person.where { |p| p.age.in?([10, 20, 30]) } #SQL IN operator
|
74
|
+
Person.where { |p| p.age.in?(0..17) } #SQL BETWEEN operator
|
75
|
+
|
76
|
+
Furthermore, you can OR or AND your clauses with | and &. Ruby 1.9 "!" and is also supported, and for
|
77
|
+
Ruby 1.8, you can use the negative ("-@") operator. For example:
|
78
|
+
|
79
|
+
Person.where { |p| (p.name != "Fred") | (p.age > 17) } #Ruby 1.9 version
|
80
|
+
Person.where { |p| !(p.name == "Fred") | (p.age > 17) } #Another Ruby 1.9 version
|
81
|
+
Person.where { |p| -(p.name == "Fred") | (p.age > 17) } #All ruby versions
|
82
|
+
|
83
|
+
The block argument is optional, too, so:
|
84
|
+
|
85
|
+
Person.where { name == "Foo" }
|
86
|
+
Person.where { name.like "F%" }
|
87
|
+
Person.where { (age <= 10) & (name =~ "F%) }
|
88
|
+
|
89
|
+
Please notice, that as & and | operators have a high precedence, don't forget the parenthesis,
|
90
|
+
otherwise undesired behaviours can occur.
|
91
|
+
|
92
|
+
This kind of syntax can be combined with regular "where" expressions too. So, the following code:
|
93
|
+
|
94
|
+
Person.where(:name => "Foo") { age > 10 }
|
95
|
+
|
96
|
+
Is the same as:
|
97
|
+
|
98
|
+
Person.where(:name => "Foo").where { age > 10 }
|
99
|
+
|
100
|
+
(this will "AND" the two conditions)
|
101
|
+
|
102
|
+
== NOTICE ==
|
103
|
+
Regexp operations are NOT supported. Right now, I'm only delegating these operators
|
104
|
+
to Arel, and Arel still has no support for regexp.
|
105
|
+
|
106
|
+
== Known issues: ==
|
55
107
|
As ActiveRecord::Relation doesn't only include "where" clauses, there can be a strange behaviour if trying
|
56
108
|
to combinate more behaviours. For instance, this kind of query:
|
57
109
|
p1 = Person.where(:name => 'Foo').limit(10)
|
@@ -63,3 +115,10 @@ SELECT "people".* FROM "people" WHERE ((("people"."name" = 'Foo') OR ("people".
|
|
63
115
|
|
64
116
|
So, if you don't want to fall in undefined behaviours, please use:
|
65
117
|
(p1 | p2).limit(20).order('name')
|
118
|
+
|
119
|
+
Furthermore, beware of "OR" operator. Don't try this at home:
|
120
|
+
p1 = Person.where(:name => "Foo")
|
121
|
+
a1 = Address.where(:road => "Something")
|
122
|
+
(p1 | a1) #This will generate something like:
|
123
|
+
SELECT "people".* FROM "people" WHERE ((("people"."name" = 'Foo') OR ("addresses"."road" = 'Something')))
|
124
|
+
So, as with & operator, you must join relations so your query would be correct.
|
data/lib/arel_operators.rb
CHANGED
@@ -1,16 +1,32 @@
|
|
1
|
-
require "
|
1
|
+
require "arel_operators/operators"
|
2
|
+
require 'arel_operators/finder'
|
2
3
|
module ArelOperators
|
3
|
-
def where(args,
|
4
|
+
def where(*args, &b)
|
5
|
+
if b
|
6
|
+
arel = arel_from_block &b
|
7
|
+
result = where(arel)
|
8
|
+
return result if args.empty?
|
9
|
+
return result.where(*args)
|
10
|
+
end
|
4
11
|
include_operators_on super
|
5
12
|
end
|
6
13
|
|
14
|
+
def arel_from_block(&b)
|
15
|
+
if b.arity == -1 || b.arity == 0
|
16
|
+
ArelOperators::Finder.new(arel_table).instance_eval(&b).arel
|
17
|
+
else
|
18
|
+
b.call(ArelOperators::Finder.new(arel_table)).arel
|
19
|
+
end
|
20
|
+
end
|
21
|
+
private :arel_from_block
|
22
|
+
|
7
23
|
def scoped(*args)
|
8
24
|
include_operators_on super(*args)
|
9
25
|
end
|
10
26
|
|
11
27
|
def include_operators_on(relation)
|
12
28
|
metaclass = class << relation; self; end
|
13
|
-
metaclass.send :include,
|
29
|
+
metaclass.send :include, ArelOperators::Operators
|
14
30
|
return relation
|
15
31
|
end
|
16
32
|
private :include_operators_on
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "arel_operators/finder/comparators"
|
2
|
+
module ArelOperators
|
3
|
+
class Finder
|
4
|
+
attr_reader :table, :arel
|
5
|
+
|
6
|
+
def initialize(arel_table, operation=nil)
|
7
|
+
@table = arel_table
|
8
|
+
@arel = operation
|
9
|
+
@table.attributes.each { |a| define_attribute_method a.name }
|
10
|
+
end
|
11
|
+
|
12
|
+
def define_attribute_method(attribute)
|
13
|
+
singleton_class.send :define_method, attribute do
|
14
|
+
ArelOperators::Finder::Comparators.new(self, @table[attribute])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
private :define_attribute_method
|
18
|
+
|
19
|
+
def |(other)
|
20
|
+
other = other.arel if other.respond_to?(:arel)
|
21
|
+
Finder.new(table, arel.or(other))
|
22
|
+
end
|
23
|
+
|
24
|
+
def &(other)
|
25
|
+
other = other.arel if other.respond_to?(:arel)
|
26
|
+
Finder.new(table, arel.and(other))
|
27
|
+
end
|
28
|
+
|
29
|
+
if RUBY_VERSION >= '1.9.0'
|
30
|
+
eval '
|
31
|
+
def !@
|
32
|
+
-self
|
33
|
+
end
|
34
|
+
'
|
35
|
+
end
|
36
|
+
|
37
|
+
def -@
|
38
|
+
Finder.new(table, Arel::Predicates::Not.new(arel))
|
39
|
+
end
|
40
|
+
|
41
|
+
undef_method :==
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
__END__
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ArelOperators
|
2
|
+
class Finder
|
3
|
+
class Comparators
|
4
|
+
def initialize(finder, field)
|
5
|
+
@finder = finder
|
6
|
+
@field = finder.table[field]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.convert_to_arel(operation, arel_method)
|
10
|
+
define_method(operation) do |other|
|
11
|
+
arel_clause = @field.send(arel_method, other)
|
12
|
+
ArelOperators::Finder.new(@finder.table, arel_clause)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def nil?
|
17
|
+
self == nil
|
18
|
+
end
|
19
|
+
|
20
|
+
convert_to_arel :==, :eq
|
21
|
+
convert_to_arel '!=', :not_eq
|
22
|
+
convert_to_arel :>, :gt
|
23
|
+
convert_to_arel :>=, :gteq
|
24
|
+
convert_to_arel :<, :lt
|
25
|
+
convert_to_arel :<=, :lteq
|
26
|
+
convert_to_arel :in?, :in
|
27
|
+
convert_to_arel :like?, :matches
|
28
|
+
alias :matches? :like?
|
29
|
+
alias :=~ :like? #TODO: Is this really a good idea?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ArelOperators
|
2
|
+
module Operators
|
3
|
+
def or(other)
|
4
|
+
build_arel_predicate(Arel::Predicates::Or, self, other)
|
5
|
+
end
|
6
|
+
alias :| :or
|
7
|
+
alias :+ :or
|
8
|
+
|
9
|
+
def and(other)
|
10
|
+
build_arel_predicate(Arel::Predicates::And, self, other)
|
11
|
+
end
|
12
|
+
|
13
|
+
def -(other)
|
14
|
+
self.and(-other)
|
15
|
+
end
|
16
|
+
|
17
|
+
def -@
|
18
|
+
build_arel_predicate(Arel::Predicates::Not, self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def where(obj, *args)
|
22
|
+
return super unless obj.respond_to?(:build_where)
|
23
|
+
relation = obj.select(:id)
|
24
|
+
other = clone.tap do |c|
|
25
|
+
cond = build_where(Arel::Predicates::In.new(relation.primary_key, relation.arel))
|
26
|
+
c.where_values = Array.wrap(cond)
|
27
|
+
end
|
28
|
+
self.and(other)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def build_arel_predicate(predicate, *args)
|
33
|
+
clone.tap do |c|
|
34
|
+
condition = build_predicate(predicate, *args)
|
35
|
+
cond = build_where(condition)
|
36
|
+
c.where_values = Array.wrap(cond)
|
37
|
+
possible_joins = args.select { |x| x.object_id != object_id && x.respond_to?(:joins_values) }
|
38
|
+
c.joins_values += possible_joins.collect { |x| x.joins_values }
|
39
|
+
#possible_eager = args.select { |x| x.object_id != object_id }
|
40
|
+
#c.eager_load_values += possible_eager.collect { |x| x.eager_load_values }.flatten
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_predicate(predicate, *relations)
|
45
|
+
values = relations.collect do |relation|
|
46
|
+
value = relation.is_a?(ActiveRecord::Relation) ? relation.where_values : relation
|
47
|
+
wrap_predicate_value(value)
|
48
|
+
end
|
49
|
+
predicate.new(*values)
|
50
|
+
end
|
51
|
+
|
52
|
+
def wrap_predicate_value(value)
|
53
|
+
value.collect do |v|
|
54
|
+
next v unless v.is_a?(String)
|
55
|
+
Arel::SqlLiteral.new(v)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class RelationMismatch < StandardError
|
61
|
+
def initialize(expected_class)
|
62
|
+
super("Invalid class for operation. Expected #{expected_class}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "spec/helper"
|
2
|
+
|
3
|
+
describe ArelOperators::Finder::Comparators do
|
4
|
+
before do
|
5
|
+
@table = Person.arel_table
|
6
|
+
@finder = ArelOperators::Finder.new Person.arel_table
|
7
|
+
@comparator = ArelOperators::Finder::Comparators.new @finder, :name
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should be able to find by equality' do
|
11
|
+
(@comparator == 'foo').should be_a_kind_of(ArelOperators::Finder)
|
12
|
+
(@comparator == 'foo').arel.should ==
|
13
|
+
@table[:name].eq('foo')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should be able to find by inequality (Ruby1.9 only)' do
|
17
|
+
next if RUBY_VERSION < '1.9.0'
|
18
|
+
(@comparator != 'foo').arel.should ==
|
19
|
+
@table[:name].not_eq('foo')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should be able to verify if an attribute is nil' do
|
23
|
+
(@comparator.nil?).arel.should ==
|
24
|
+
@table[:name].eq(nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should be able to find by greater than' do
|
28
|
+
(@comparator > 'foo').arel.should == @table[:name].gt('foo')
|
29
|
+
(@comparator >= 'foo').arel.should == @table[:name].gteq('foo')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should be able to find by lower than' do
|
33
|
+
(@comparator < 'foo').arel.should == @table[:name].lt('foo')
|
34
|
+
(@comparator <= 'foo').arel.should == @table[:name].lteq('foo')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should be able to find IN' do
|
38
|
+
@comparator.in?(['foo']).arel.should == @table[:name].in(['foo'])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should be able to find with LIKE' do
|
42
|
+
@comparator.like?('foo').arel.should == @table[:name].matches('foo')
|
43
|
+
@comparator.matches?('foo').arel.should == @table[:name].matches('foo')
|
44
|
+
(@comparator =~ 'foo').arel.should == @table[:name].matches('foo')
|
45
|
+
end
|
46
|
+
|
47
|
+
#it 'should be able to find with Regex'
|
48
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "spec/helper"
|
2
|
+
|
3
|
+
describe ArelOperators::Finder do
|
4
|
+
before do
|
5
|
+
@table = Person.arel_table
|
6
|
+
@finder = ArelOperators::Finder.new Person.arel_table
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should be able to convert to a arel' do
|
10
|
+
arel = mock("Arel")
|
11
|
+
@finder = ArelOperators::Finder.new Person.arel_table, arel
|
12
|
+
@finder.arel.should == arel
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should return a empty arel if not being able to convert' do
|
16
|
+
@finder.arel.should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should not calculate equality' do
|
20
|
+
proc { @finder == @finder }.should raise_error(NoMethodError)
|
21
|
+
proc { @finder != @finder }.should raise_error(NoMethodError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should be able to expose the class attributes' do
|
25
|
+
@finder.name.should be_a_kind_of(ArelOperators::Finder::Comparators)
|
26
|
+
@finder.id.should be_a_kind_of(ArelOperators::Finder::Comparators)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should be able to "or" two conditions' do
|
30
|
+
c1 = @table[:id].eq(1)
|
31
|
+
c2 = @table[:name].eq(2)
|
32
|
+
((@finder.id == 1) | (@finder.name == 2)).arel.should ==
|
33
|
+
c1.or(c2)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should be able to "and" two conditions' do
|
37
|
+
c1 = @table[:id].eq(1)
|
38
|
+
c2 = @table[:name].eq(2)
|
39
|
+
((@finder.id == 1) & (@finder.name == 2)).arel.should ==
|
40
|
+
c1.and(c2)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should be able to negate the find (in Ruby1.9)' do
|
44
|
+
next if RUBY_VERSION < '1.9.0'
|
45
|
+
@finder = ArelOperators::Finder.new Person.arel_table, @table[:name].eq('foo')
|
46
|
+
(!@finder).arel.should ==
|
47
|
+
Arel::Predicates::Not.new(@table[:name].eq('foo'))
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should be able to negate the find with -@' do
|
51
|
+
@finder = ArelOperators::Finder.new Person.arel_table, @table[:name].eq('foo')
|
52
|
+
(-@finder).arel.should ==
|
53
|
+
Arel::Predicates::Not.new(@table[:name].eq('foo'))
|
54
|
+
end
|
55
|
+
|
56
|
+
#it 'should be able to find in a subselect'
|
57
|
+
|
58
|
+
context 'when finding using WHERE and a block' do
|
59
|
+
before do
|
60
|
+
@p1 = Person.create! :name => 'Foo', :age => 17
|
61
|
+
@p2 = Person.create! :name => 'Foo', :age => 18
|
62
|
+
end
|
63
|
+
|
64
|
+
after do
|
65
|
+
Person.delete_all
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should be able to find using a block with one parameter' do
|
69
|
+
result = Person.where { |p| (p.name == 'Foo') & (p.age > 17) }
|
70
|
+
result.should == [@p2]
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should be able to find using a block with no parameters' do
|
74
|
+
result = Person.where { (name == 'Foo') & (age > 17) }
|
75
|
+
result.should == [@p2]
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should be able to find using a condition too' do
|
79
|
+
result = Person.where(:age => 17) { name == 'Foo' }
|
80
|
+
result.should == [@p1]
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should be able to find on a HAVING condition' do
|
84
|
+
pending
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../helper')
|
2
|
+
|
3
|
+
describe ArelOperators::Operators do
|
4
|
+
before do
|
5
|
+
@p1 = Person.create! :name => 'Foo'
|
6
|
+
@p2 = Person.create! :name => 'Bar'
|
7
|
+
@p3 = Person.create! :name => 'Baz'
|
8
|
+
Address.create! :address => "Avenue", :person_id => @p2.id
|
9
|
+
Address.create! :address => "Road", :person_id => @p3.id
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
Person.delete_all; Address.delete_all
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should obey joins' do
|
17
|
+
Address.create! :address => "Village", :person_id => @p1.id
|
18
|
+
q1 = Person.where :name => 'Foo'
|
19
|
+
q2 = Person.joins(:addresses).where('addresses.address = ?', 'Road')
|
20
|
+
result = q2 | q1
|
21
|
+
result.to_sql.should match(/inner join/i)
|
22
|
+
result.should include(@p1)
|
23
|
+
result.should include(@p3)
|
24
|
+
result.should_not include(@p2)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should obey left joins' do
|
28
|
+
pending 'Left Join in Rails?'
|
29
|
+
q1 = Person.where :name => 'Foo'
|
30
|
+
q2 = Person.eager_load(:addresses).where('addresses.address = ?', 'Road')
|
31
|
+
puts q2.to_sql
|
32
|
+
result = q1 | q2
|
33
|
+
result.to_sql.should match(/join/i)
|
34
|
+
result.should include(@p1)
|
35
|
+
result.should include(@p3)
|
36
|
+
result.should_not include(@p2)
|
37
|
+
end
|
38
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../helper')
|
2
|
-
require "ruby_debug"
|
3
2
|
|
4
|
-
describe
|
3
|
+
describe ArelOperators::Operators do
|
5
4
|
it 'Should "or" two conditions' do
|
6
5
|
arel = Person.where(:id => 190) | Person.where(:id => 210)
|
7
6
|
result = arel.to_sql
|
@@ -11,7 +10,7 @@ describe ActiveRecord::Operators do
|
|
11
10
|
end
|
12
11
|
|
13
12
|
it 'Should "and" two conditions' do
|
14
|
-
arel = Person.where(:id => 190)
|
13
|
+
arel = Person.where(:id => 190).and Person.where(:id => 210)
|
15
14
|
result = arel.to_sql
|
16
15
|
result.should match(/and/i)
|
17
16
|
result.should match(/210/i)
|
@@ -28,6 +27,7 @@ describe ActiveRecord::Operators do
|
|
28
27
|
it 'should subtract two conditions' do
|
29
28
|
arel = Person.where(:id => 190) - Person.where(:id => 210)
|
30
29
|
result = arel.to_sql
|
30
|
+
result.should have(1).select_clause
|
31
31
|
result.should match(/and.*not/i)
|
32
32
|
result.should match(/210/i)
|
33
33
|
result.should match(/190/i)
|
@@ -43,7 +43,6 @@ describe ActiveRecord::Operators do
|
|
43
43
|
result.should include(baz)
|
44
44
|
result.should_not include(foo)
|
45
45
|
result.should_not include(bar)
|
46
|
-
|
47
|
-
sql.scan(/select/i).should have(2).matches
|
46
|
+
result.to_sql.should have(2).select_clauses
|
48
47
|
end
|
49
48
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.2
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- "Maur\xC3\xADcio Szabo"
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-19 00:00:00 -03:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -42,9 +42,14 @@ extra_rdoc_files:
|
|
42
42
|
- README
|
43
43
|
files:
|
44
44
|
- lib/arel_operators.rb
|
45
|
-
- lib/
|
45
|
+
- lib/arel_operators/finder.rb
|
46
|
+
- lib/arel_operators/finder/comparators.rb
|
47
|
+
- lib/arel_operators/operators.rb
|
46
48
|
- README
|
47
|
-
- spec/
|
49
|
+
- spec/arel_operators/joins_spec.rb
|
50
|
+
- spec/arel_operators/operators_spec.rb
|
51
|
+
- spec/arel_operators/finder/comparators_spec.rb
|
52
|
+
- spec/arel_operators/finder_spec.rb
|
48
53
|
has_rdoc: true
|
49
54
|
homepage: http://github.com/mauricioszabo/arel_operators
|
50
55
|
licenses: []
|
@@ -78,4 +83,7 @@ signing_key:
|
|
78
83
|
specification_version: 3
|
79
84
|
summary: Operators (|, &, -) for ActiveRecord.
|
80
85
|
test_files:
|
81
|
-
- spec/
|
86
|
+
- spec/arel_operators/joins_spec.rb
|
87
|
+
- spec/arel_operators/operators_spec.rb
|
88
|
+
- spec/arel_operators/finder/comparators_spec.rb
|
89
|
+
- spec/arel_operators/finder_spec.rb
|
@@ -1,53 +0,0 @@
|
|
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
|