arel_operators 0.0.2 → 0.1.0
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 +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
|