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 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
- Plase note that because of operator precedence in Ruby, we must add a parenthesis over the comparission before using `&` or `|`.
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
+ ```
@@ -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
- Comparator.new(self, arel_attribute)
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
- def create_finder(&block)
33
+
34
+ def create_finder(*tables, &block)
24
35
  activerecord_finder = ActiveRecordFinder::Finder.new(arel_table)
25
- if block.arity == 1
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
- proc { subject == subject }.should raise_error(NoMethodError)
13
- proc { subject != subject }.should raise_error(NoMethodError)
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.id == 1) & (subject.name == 2)).arel.should be_equivalent_to c1.and(c2)
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.2
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-03 00:00:00.000000000 Z
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