activerecord_finder 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +39 -4
- data/lib/activerecord_finder.rb +2 -0
- data/lib/activerecord_finder/comparator.rb +22 -0
- data/lib/activerecord_finder/finder.rb +11 -2
- data/lib/activerecord_finder/set_operations.rb +42 -0
- data/lib/activerecord_finder/where.rb +20 -5
- data/spec/activerecord_finder/comparator_spec.rb +14 -0
- data/spec/activerecord_finder/finder_spec.rb +8 -3
- data/spec/activerecord_finder/set_operations_spec.rb +54 -0
- data/spec/activerecord_finder/where_spec.rb +20 -0
- metadata +5 -2
data/README.md
CHANGED
@@ -29,7 +29,8 @@ so you can use ActiveRecord::Base#restrict with a block to construct your querie
|
|
29
29
|
|
30
30
|
## ActiveRecord's Finders
|
31
31
|
|
32
|
-
Integrating with ActiveRecord, it is possible to use `restrict` to find records on a ActiveRecord object. The syntax is quite simple:
|
32
|
+
Integrating with ActiveRecord, it is possible to use `restrict` to find records on a ActiveRecord object. The syntax is quite simple (Please note that because of operator precedence in Ruby, we must add a parenthesis over the comparission before using `&` or `|`):
|
33
|
+
|
33
34
|
|
34
35
|
```ruby
|
35
36
|
User.restrict { |f| f.age > 18 }
|
@@ -73,7 +74,20 @@ User.restrict do
|
|
73
74
|
end
|
74
75
|
```
|
75
76
|
|
76
|
-
|
77
|
+
Please note that we can't use instance variables if we use the block syntax without a variable. This is a Ruby's limitation, not ActiveRecord Finder's one.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class Foo
|
81
|
+
def initialize
|
82
|
+
@name = "Bar"
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_all
|
86
|
+
User.restrict { name == @name } #Doesn't work - @name is nil
|
87
|
+
User.restrict { |u| u.name == @name } #Works!
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
77
91
|
|
78
92
|
## Constructing Queries
|
79
93
|
|
@@ -90,6 +104,27 @@ end
|
|
90
104
|
|
91
105
|
### Using "joins"
|
92
106
|
|
93
|
-
To be simple, the `Finder` simply has the attributes from the table that it was created. So, to be able to find a record using attributes from one of the joined tables, it is possible to use `ActiveRecord::Base#merge`
|
107
|
+
To be simple, the `Finder` simply has the attributes from the table that it was created. So, to be able to find a record using attributes from one of the joined tables, it is possible to use `ActiveRecord::Base#merge`. If it's absolutely necessary, we could use `restrict_with` or `new_finder_with` to construct queries that uses fields from another tables (or table aliases). For example, consider the following examples:
|
94
108
|
|
95
|
-
|
109
|
+
```ruby
|
110
|
+
#Finding user's roles for specific users
|
111
|
+
admins = User.admins #(or User.where(admin: true) or User.restrict { admin == true }
|
112
|
+
roles = Role.restrict { name != "top secret administrative stuff" }
|
113
|
+
admin_roles = roles.joins(:users).merge(admins)
|
114
|
+
|
115
|
+
#Finding without "merge" (but you cannot use "scopes" in this case,
|
116
|
+
#nor use the block syntax without variables)
|
117
|
+
admin_roles = Role.joins(:users).restrict_with(:users) do |role, user|
|
118
|
+
(role.name != "top secret administrative stuff") & (user.admin == true)
|
119
|
+
end
|
120
|
+
|
121
|
+
#Finding using a "table alias" or some other thing
|
122
|
+
table = User.where(name: "Foo").as('alias')
|
123
|
+
User.from([User.arel_table, table]).restrict_with(:alias) do |user, table|
|
124
|
+
user.id == table.id
|
125
|
+
end
|
126
|
+
#Will produce the query:
|
127
|
+
#SELECT `users`.*
|
128
|
+
#FROM `users`, (SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Foo') alias
|
129
|
+
#WHERE `users`.`id` = `alias`.`id`
|
130
|
+
```
|
data/lib/activerecord_finder.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative "activerecord_finder/finder"
|
2
2
|
require_relative "activerecord_finder/comparator"
|
3
3
|
require_relative "activerecord_finder/where"
|
4
|
+
require_relative "activerecord_finder/set_operations"
|
4
5
|
|
5
6
|
ActiveRecord::Base.extend ActiveRecordFinder::Where
|
7
|
+
ActiveRecord::Relation.send :include, ActiveRecordFinder::SetOperations
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module ActiveRecordFinder
|
2
2
|
class Comparator
|
3
|
+
attr_reader :field
|
4
|
+
|
3
5
|
def initialize(finder, field)
|
4
6
|
@finder = finder
|
5
7
|
@field = field
|
@@ -7,6 +9,7 @@ module ActiveRecordFinder
|
|
7
9
|
|
8
10
|
def self.convert_to_arel(operation, arel_method)
|
9
11
|
define_method(operation) do |other|
|
12
|
+
other = other.field if other.respond_to?(:field)
|
10
13
|
arel_clause = @field.send(arel_method, other)
|
11
14
|
Finder.new(@finder.table, arel_clause)
|
12
15
|
end
|
@@ -22,6 +25,25 @@ module ActiveRecordFinder
|
|
22
25
|
Finder.new(@finder.table, arel_clause)
|
23
26
|
end
|
24
27
|
|
28
|
+
def not_in?(other)
|
29
|
+
other = other.arel if other.respond_to?(:arel)
|
30
|
+
arel_clause = @field.not_in(other)
|
31
|
+
Finder.new(@finder.table, arel_clause)
|
32
|
+
end
|
33
|
+
|
34
|
+
def size
|
35
|
+
Comparator.new(@finder, @field.count)
|
36
|
+
end
|
37
|
+
alias :count :size
|
38
|
+
|
39
|
+
def lower
|
40
|
+
Comparator.new(@finder, @field.lower)
|
41
|
+
end
|
42
|
+
|
43
|
+
def upper
|
44
|
+
Comparator.new(@finder, Arel::Nodes::NamedFunction.new("UPPER", [@field]))
|
45
|
+
end
|
46
|
+
|
25
47
|
convert_to_arel :==, :eq
|
26
48
|
convert_to_arel '!=', :not_eq
|
27
49
|
convert_to_arel :>, :gt
|
@@ -5,12 +5,16 @@ module ActiveRecordFinder
|
|
5
5
|
def initialize(arel_table, operation=nil)
|
6
6
|
@table = arel_table
|
7
7
|
@arel = operation
|
8
|
-
@table.columns.each { |a| define_attribute_method a }
|
8
|
+
@table.columns.each { |a| define_attribute_method a } rescue nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](attribute)
|
12
|
+
Comparator.new(self, @table[attribute])
|
9
13
|
end
|
10
14
|
|
11
15
|
def define_attribute_method(arel_attribute)
|
12
16
|
singleton_class.send :define_method, arel_attribute.name do
|
13
|
-
|
17
|
+
self[arel_attribute.name]
|
14
18
|
end
|
15
19
|
end
|
16
20
|
private :define_attribute_method
|
@@ -30,5 +34,10 @@ module ActiveRecordFinder
|
|
30
34
|
end
|
31
35
|
|
32
36
|
undef_method :==
|
37
|
+
|
38
|
+
def method_missing(method, *args, &b)
|
39
|
+
super if args.size != 0 || b
|
40
|
+
self[method]
|
41
|
+
end
|
33
42
|
end
|
34
43
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ActiveRecordFinder
|
2
|
+
module SetOperations
|
3
|
+
def unite_with(other)
|
4
|
+
if arel_table.name != other.arel_table.name
|
5
|
+
raise ArgumentError, "can't operate on different tables"
|
6
|
+
elsif primary_key.nil?
|
7
|
+
raise ArgumentError, "must have a primary key"
|
8
|
+
end
|
9
|
+
|
10
|
+
first_subselect = unscoped.where(primary_key => other)
|
11
|
+
second_subselect = unscoped.where(primary_key => self)
|
12
|
+
unscoped.restrict(first_subselect.to_finder | second_subselect.to_finder)
|
13
|
+
end
|
14
|
+
|
15
|
+
def intersect_with(other, field_self=nil, field_other=nil)
|
16
|
+
field_other ||= field_self
|
17
|
+
|
18
|
+
if field_self.nil?
|
19
|
+
where(primary_key => other)
|
20
|
+
else
|
21
|
+
if field_other.is_a?(Symbol)
|
22
|
+
field_other = other.scoped.arel_table[field_other]
|
23
|
+
end
|
24
|
+
where(field_self => other.select(field_other))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def subtract(other, field_self=nil, field_other=nil)
|
29
|
+
field_other ||= field_self
|
30
|
+
|
31
|
+
if field_self.nil?
|
32
|
+
subselect = unscoped.where(primary_key => other)
|
33
|
+
else
|
34
|
+
if field_other.is_a?(Symbol)
|
35
|
+
field_other = other.scoped.arel_table[field_other]
|
36
|
+
end
|
37
|
+
subselect = unscoped.where(field_self => other.select(field_other))
|
38
|
+
end
|
39
|
+
restrict(!subselect.to_finder)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -10,22 +10,37 @@ module ActiveRecordFinder
|
|
10
10
|
end
|
11
11
|
alias :condition :restrict
|
12
12
|
|
13
|
+
def restrict_with(*tables, &block)
|
14
|
+
where(create_finder(*tables, &block).arel)
|
15
|
+
end
|
16
|
+
|
13
17
|
def new_finder(&block)
|
18
|
+
new_finder_with(&block)
|
19
|
+
end
|
20
|
+
alias :to_finder :new_finder
|
21
|
+
|
22
|
+
def new_finder_with(*tables, &block)
|
14
23
|
scoped_where = scoped.arel.where_clauses.map { |w| Arel.sql(w) }
|
15
24
|
if block
|
16
|
-
finder = create_finder(&block)
|
25
|
+
finder = create_finder(*tables, &block)
|
17
26
|
scoped_where << finder.arel
|
18
27
|
end
|
19
28
|
arel = Arel::Nodes::And.new(scoped_where)
|
20
29
|
ActiveRecordFinder::Finder.new(arel_table, arel)
|
21
30
|
end
|
31
|
+
alias :to_finder_with :new_finder_with
|
22
32
|
|
23
|
-
|
33
|
+
|
34
|
+
def create_finder(*tables, &block)
|
24
35
|
activerecord_finder = ActiveRecordFinder::Finder.new(arel_table)
|
25
|
-
if block.arity ==
|
26
|
-
block.call(activerecord_finder)
|
27
|
-
else
|
36
|
+
if tables.size == 0 && block.arity == 0
|
28
37
|
activerecord_finder.instance_eval(&block)
|
38
|
+
else
|
39
|
+
additional_finders = tables.map do |table_name|
|
40
|
+
arel_table = Arel::Table.new(table_name)
|
41
|
+
ActiveRecordFinder::Finder.new(arel_table)
|
42
|
+
end
|
43
|
+
block.call(activerecord_finder, *additional_finders)
|
29
44
|
end
|
30
45
|
end
|
31
46
|
private :create_finder
|
@@ -32,6 +32,10 @@ describe ActiveRecordFinder::Comparator do
|
|
32
32
|
subject.in?(['foo']).arel.should be_equivalent_to table[:name].in(['foo'])
|
33
33
|
end
|
34
34
|
|
35
|
+
it 'finds with NOT IN' do
|
36
|
+
subject.not_in?(['foo']).arel.should be_equivalent_to table[:name].not_in(['foo'])
|
37
|
+
end
|
38
|
+
|
35
39
|
it 'finds with subselects' do
|
36
40
|
result = subject.in?(Person.where(age: 10))
|
37
41
|
result.arel.to_sql.should match(/select/i)
|
@@ -41,4 +45,14 @@ describe ActiveRecordFinder::Comparator do
|
|
41
45
|
(subject =~ 'foo').arel.should be_equivalent_to table[:name].matches('foo')
|
42
46
|
(subject !~ 'foo').arel.should be_equivalent_to table[:name].does_not_match('foo')
|
43
47
|
end
|
48
|
+
|
49
|
+
it 'is able to find with COUNT' do
|
50
|
+
(subject.size == 10).arel.should be_equivalent_to table[:name].count.eq(10)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'finds with LOWER or UPPER' do
|
54
|
+
(subject.lower == "foo").arel.should be_equivalent_to table[:name].lower.eq('foo')
|
55
|
+
(subject.upper == "foo").arel.should be_equivalent_to(
|
56
|
+
Arel::Nodes::NamedFunction.new("UPPER", [table[:name]]).eq('foo'))
|
57
|
+
end
|
44
58
|
end
|
@@ -9,8 +9,8 @@ describe ActiveRecordFinder::Finder do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'should not calculate equality' do
|
12
|
-
|
13
|
-
|
12
|
+
-> { subject == subject }.should raise_error(NoMethodError)
|
13
|
+
-> { subject != subject }.should raise_error(NoMethodError)
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'should be able to "or" two conditions' do
|
@@ -22,11 +22,16 @@ describe ActiveRecordFinder::Finder do
|
|
22
22
|
it 'should be able to "and" two conditions' do
|
23
23
|
c1 = table[:id].eq(1)
|
24
24
|
c2 = table[:name].eq(2)
|
25
|
-
((subject
|
25
|
+
((subject[:id] == 1) & (subject[:name] == 2)).arel.should be_equivalent_to c1.and(c2)
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'should be able to negate the find' do
|
29
29
|
result = subject.name == 'foo'
|
30
30
|
(!result).arel.should be_equivalent_to Arel::Nodes::Not.new(table[:name].eq('foo'))
|
31
31
|
end
|
32
|
+
|
33
|
+
it 'finds by custom attributes defined in SELECT clause' do
|
34
|
+
result = subject.custom == "Foo"
|
35
|
+
result.arel.should be_equivalent_to table[:custom].eq("Foo")
|
36
|
+
end
|
32
37
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative "../helper"
|
2
|
+
|
3
|
+
describe ActiveRecordFinder::SetOperations do
|
4
|
+
let!(:seventeen) { Person.create! :name => 'Foo', :age => 17 }
|
5
|
+
let!(:eighteen) { Person.create! :name => 'Foo', :age => 18 }
|
6
|
+
|
7
|
+
it 'UNIONs two relations' do
|
8
|
+
relation1 = Person.where(age: 17)
|
9
|
+
relation2 = Person.where(age: 18)
|
10
|
+
relation1.unite_with(relation2).should include(seventeen, eighteen)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'UNIONs two relations with JOIN' do
|
14
|
+
seventeen_with_join = Person.create!(age: 17)
|
15
|
+
seventeen_with_join.addresses.create!
|
16
|
+
relation1 = Person.where(age: 17).joins(:addresses)
|
17
|
+
relation2 = Person.where(age: 18)
|
18
|
+
|
19
|
+
result = relation1.unite_with(relation2)
|
20
|
+
result.should include(seventeen_with_join, eighteen)
|
21
|
+
result.should_not include(seventeen)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'INTERSECTs two relations' do
|
25
|
+
bar = Person.create!(name: "Bar", age: 17)
|
26
|
+
relation1 = Person.where(age: 17)
|
27
|
+
relation2 = Person.where(name: "Foo")
|
28
|
+
relation1.intersect_with(relation2).should == [seventeen]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'INTERSECTs two relations using another fields and another tables' do
|
32
|
+
seventeen_bar = Person.create!(name: "Bar", age: 17)
|
33
|
+
relation1 = Person.where(age: 17)
|
34
|
+
relation2 = Person.where(name: "Foo")
|
35
|
+
relation1.intersect_with(relation2, :age).should include(seventeen_bar, seventeen)
|
36
|
+
relation1.intersect_with(relation2, :age).should_not include(eighteen)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'SUBTRACTs a relation from this one' do
|
40
|
+
relation1 = Person.where(name: "Foo")
|
41
|
+
relation2 = Person.where(age: 18)
|
42
|
+
relation1.subtract(relation2).should == [seventeen]
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'SUBTRACTs a relation from this one, using another fields' do
|
46
|
+
bar = Person.create! name: "Bar", age: 17
|
47
|
+
Address.create! address: "Foo"
|
48
|
+
|
49
|
+
relation1 = Person.where(age: 17)
|
50
|
+
relation2 = Address.where(address: "Foo")
|
51
|
+
|
52
|
+
relation1.subtract(relation2, :name, :address).should == [bar]
|
53
|
+
end
|
54
|
+
end
|
@@ -39,4 +39,24 @@ describe ActiveRecordFinder::Where do
|
|
39
39
|
new_finder = person.new_finder
|
40
40
|
Person.restrict(new_finder).should == [bar]
|
41
41
|
end
|
42
|
+
|
43
|
+
it 'finds using fields from other tables' do
|
44
|
+
seventeen.addresses.create! address: "Seventeen Road"
|
45
|
+
eighteen.addresses.create! address: "Eighteen Road"
|
46
|
+
|
47
|
+
result = Person.joins(:addresses).restrict(
|
48
|
+
Person.new_finder_with(:addresses) { |_, a| a.address =~ 'Eighteen%' })
|
49
|
+
result.should == [eighteen]
|
50
|
+
|
51
|
+
result = Person.joins(:addresses).restrict_with(:addresses) { |_, a| a.address =~ 'Seventeen%' }
|
52
|
+
result.should == [seventeen]
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'finds by multiple tables or aliases' do
|
56
|
+
finder = Person.new_finder_with(:custom) { |p, c| p.id == c.id }
|
57
|
+
|
58
|
+
people_table = Arel::Table.new(:people)
|
59
|
+
custom_table = Arel::Table.new(:custom)
|
60
|
+
finder.arel.should be_equivalent_to(people_table[:id].eq(custom_table[:id]))
|
61
|
+
end
|
42
62
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord_finder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-07-
|
12
|
+
date: 2013-07-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -34,6 +34,7 @@ extensions: []
|
|
34
34
|
extra_rdoc_files:
|
35
35
|
- README.md
|
36
36
|
files:
|
37
|
+
- lib/activerecord_finder/set_operations.rb
|
37
38
|
- lib/activerecord_finder/where.rb
|
38
39
|
- lib/activerecord_finder/finder.rb
|
39
40
|
- lib/activerecord_finder/comparator.rb
|
@@ -41,6 +42,7 @@ files:
|
|
41
42
|
- README.md
|
42
43
|
- spec/activerecord_finder/finder_spec.rb
|
43
44
|
- spec/activerecord_finder/comparator_spec.rb
|
45
|
+
- spec/activerecord_finder/set_operations_spec.rb
|
44
46
|
- spec/activerecord_finder/where_spec.rb
|
45
47
|
homepage: http://github.com/mauricioszabo/arel_operators
|
46
48
|
licenses: []
|
@@ -69,5 +71,6 @@ summary: Better finder syntax (|, &, >=, <=) for ActiveRecord.
|
|
69
71
|
test_files:
|
70
72
|
- spec/activerecord_finder/finder_spec.rb
|
71
73
|
- spec/activerecord_finder/comparator_spec.rb
|
74
|
+
- spec/activerecord_finder/set_operations_spec.rb
|
72
75
|
- spec/activerecord_finder/where_spec.rb
|
73
76
|
has_rdoc: true
|