arel_operators 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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