arel_operators 0.0.2

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 ADDED
@@ -0,0 +1,65 @@
1
+ AR Operators
2
+
3
+ Don't let me wrong, it's not as I hate SQL. I just hate to create complex queries when ActiveRecord
4
+ could do this for me, for free.
5
+
6
+ Enter AR Operators.
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:
10
+
11
+ Person.all :conditions => [
12
+ '(name LIKE ? OR name LIKE ?) AND age >= 18', '%John%', '%Smith%'
13
+ ]
14
+
15
+ But this is tedious. One of the great advantages of ActiveRecord 3 is use of scopes, so you could:
16
+
17
+ Person.where(['(name LIKE ? OR name LIKE ?)', '%John%', '%Smith%']).where('age >= 18')
18
+
19
+ Better. But why not:
20
+
21
+ johns = Person.where(['name like ?', '%John%'])
22
+ smiths = Person.where(['name like ?', '%Smith%'])
23
+ underageds = Person.where('age < 18')
24
+ (johns | smiths) - underageds
25
+
26
+ ENTER AR Operators
27
+
28
+ This library brings operators to ActiveRecord 3. So, all you have to do is:
29
+ require 'ar_operators'
30
+ class Person < ActiveRecord::Base
31
+ extend AROperators
32
+ end
33
+
34
+ And you're ready to go. Right now, the following operators are implemented:
35
+ | (OR)
36
+ & (AND)
37
+ - (AND (NOT ...))
38
+ -@ (negates the query. Use like: -Person.where(:name => 'foo'), to find all people where name is not 'foo')
39
+
40
+ There is also the following constructions:
41
+ p1 = Person.where :name => 'Foo'
42
+ p2 = Person.where :age => 18
43
+ p1.where(p2) #Generates something like: SELECT * FROM people WHERE ((name = 'Foo') AND (id in SELECT id FROM people WHERE age = 18))
44
+
45
+ Design Decision:
46
+ To not monkey patch ActiveRecord::Relation, I decided to include these operators only when you
47
+ use "where" or "scoped" to find objects. If you decide that ALL ActiveRecord operations should
48
+ have this kind of behaviour, you can:
49
+
50
+ class ActiveRecord::Relation
51
+ include ActiveRecord::Operators
52
+ end
53
+
54
+ Known issues:
55
+ As ActiveRecord::Relation doesn't only include "where" clauses, there can be a strange behaviour if trying
56
+ to combinate more behaviours. For instance, this kind of query:
57
+ p1 = Person.where(:name => 'Foo').limit(10)
58
+ p2 = Person.where(:age => 18).order('name').limit(20)
59
+ (p1 | p2).to_sql
60
+
61
+ Will generate:
62
+ SELECT "people".* FROM "people" WHERE ((("people"."name" = 'Foo') OR ("people"."age" = 18))) LIMIT 10
63
+
64
+ So, if you don't want to fall in undefined behaviours, please use:
65
+ (p1 | p2).limit(20).order('name')
@@ -0,0 +1,53 @@
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
@@ -0,0 +1,17 @@
1
+ require "active_record/operators"
2
+ module ArelOperators
3
+ def where(args, *opts)
4
+ include_operators_on super
5
+ end
6
+
7
+ def scoped(*args)
8
+ include_operators_on super(*args)
9
+ end
10
+
11
+ def include_operators_on(relation)
12
+ metaclass = class << relation; self; end
13
+ metaclass.send :include, ActiveRecord::Operators
14
+ return relation
15
+ end
16
+ private :include_operators_on
17
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../helper')
2
+ require "ruby_debug"
3
+
4
+ describe ActiveRecord::Operators do
5
+ it 'Should "or" two conditions' do
6
+ arel = Person.where(:id => 190) | Person.where(:id => 210)
7
+ result = arel.to_sql
8
+ result.should match(/or/i)
9
+ result.should match(/210/i)
10
+ result.should match(/190/i)
11
+ end
12
+
13
+ it 'Should "and" two conditions' do
14
+ arel = Person.where(:id => 190) & Person.where(:id => 210)
15
+ result = arel.to_sql
16
+ result.should match(/and/i)
17
+ result.should match(/210/i)
18
+ result.should match(/190/i)
19
+ end
20
+
21
+ it 'should negate the condition' do
22
+ arel = -Person.where(:id => 190)
23
+ result = arel.to_sql
24
+ result.should match(/not/i)
25
+ result.should match(/190/i)
26
+ end
27
+
28
+ it 'should subtract two conditions' do
29
+ arel = Person.where(:id => 190) - Person.where(:id => 210)
30
+ result = arel.to_sql
31
+ result.should match(/and.*not/i)
32
+ result.should match(/210/i)
33
+ result.should match(/190/i)
34
+ end
35
+
36
+ it 'should create subselects' do
37
+ foo = Person.create! :name => 'Foo'
38
+ bar = Person.create! :name => 'Bar'
39
+ baz = Person.create! :name => 'Baz'
40
+ select = Person.where :name => ['Foo', 'Baz']
41
+ subselect = Person.where :name => ['Baz', 'Bar']
42
+ result = select.where(subselect)
43
+ result.should include(baz)
44
+ result.should_not include(foo)
45
+ result.should_not include(bar)
46
+ sql = result.to_sql
47
+ sql.scan(/select/i).should have(2).matches
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arel_operators
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - "Maur\xC3\xADcio Szabo"
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-01 00:00:00 -03:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 3
30
+ - 0
31
+ - 0
32
+ version: 3.0.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description:
36
+ email: mauricio.szabo@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ files:
44
+ - lib/arel_operators.rb
45
+ - lib/active_record/operators.rb
46
+ - README
47
+ - spec/active_record/operators_spec.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/mauricioszabo/arel_operators
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.7
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Operators (|, &, -) for ActiveRecord.
80
+ test_files:
81
+ - spec/active_record/operators_spec.rb