activerecord_finder 0.1.2 → 0.1.4
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.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
|