johnbender-rquery 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +58 -11
- data/examples/user.rb +49 -0
- data/lib/rquery/active_record/base.rb +23 -21
- data/lib/rquery/adapters/sql.rb +8 -2
- data/lib/rquery/attribute.rb +14 -0
- data/lib/rquery/attribute_collection.rb +35 -0
- data/lib/rquery/declarations.rb +30 -35
- data/lib/rquery/serializers.rb +173 -99
- data/lib/rquery.rb +14 -6
- data/spec/active_record_base_spec_attribute_collection.rb +216 -0
- data/spec/active_record_base_spec_symbols.rb +214 -0
- data/spec/declarations_spec.rb +1 -25
- data/spec/mock_active_record.rb +13 -0
- data/spec/or_and_operations_spec.rb +54 -0
- data/spec/serializers_spec.rb +26 -22
- metadata +11 -5
- data/spec/active_record_base_spec.rb +0 -199
data/README.markdown
CHANGED
@@ -3,14 +3,28 @@ RQuery
|
|
3
3
|
|
4
4
|
RQuery is a small DSL inspired by RSpec for building text queries in languages like SQL. It is meant to be concise, easy to read, and expressive about what the query will be asking for.
|
5
5
|
|
6
|
-
Currently only the ActiveRecord extension is implemented with a Sqlite adapter. Mysql should
|
6
|
+
Currently only the ActiveRecord extension is implemented with a Sqlite adapter. Mysql should be trivial to implement, and I'm hoping to get to supporting my own project Grove.
|
7
7
|
|
8
8
|
ActiveRecord
|
9
9
|
------------
|
10
10
|
|
11
|
-
|
11
|
+
###Setup/Config and Symbols vs Block parameters
|
12
12
|
|
13
|
-
|
13
|
+
In you're rails environment file simply require rquery. The default adapter is the included Sqlite adapter but you can created and set your own with
|
14
|
+
|
15
|
+
RQuery.adapter = class
|
16
|
+
|
17
|
+
You can view both Sql and Sqlite in the adapters directory if you are interested in writing your own (mysql?). As a side note it would be nice at some point to decide the adapter based on the db chosen for a given environment.
|
18
|
+
|
19
|
+
Also, you can choose to use symbols as the attribute names in the block or you can use an option block argument to represent an ActiveRecord object. To use the symbols make sure to add the following to your environment.rb
|
20
|
+
|
21
|
+
RQuery.use_symbols
|
22
|
+
|
23
|
+
Using the block parameter instead has two benefits. 1: you won't be poluting the Symbol objects with my hackery, and 2: RQuery will tell you when you are attempting use an attribute for the object that doesn't exist. Examples of both below.
|
24
|
+
|
25
|
+
###Examples
|
26
|
+
|
27
|
+
RQuery extend ActiveRecord to provide the `where` method. `where` accepts a single optional argument and a block that represents the query statements
|
14
28
|
|
15
29
|
In a given UsersController your `show` action might find the User like so:
|
16
30
|
|
@@ -20,6 +34,9 @@ Using RQuery:
|
|
20
34
|
|
21
35
|
@user = User.where { :id.is == params[:id] }
|
22
36
|
|
37
|
+
Or
|
38
|
+
|
39
|
+
@user = User.where { |user| user.id.is == params[:id] }
|
23
40
|
|
24
41
|
In the above case, RQuery doesn't provide much of an improvement over the traditional `find` method, but as the query becomes more complex its expressiveness begins to shine through:
|
25
42
|
|
@@ -33,23 +50,23 @@ RQuery:
|
|
33
50
|
|
34
51
|
Or:
|
35
52
|
|
36
|
-
@users = User.where do
|
37
|
-
|
53
|
+
@users = User.where do |user|
|
54
|
+
user.age.between 10..20
|
38
55
|
end
|
39
56
|
|
40
57
|
Both the `from` and `between` methods accept argument lists `10,20` or an array `[10,20]`.
|
41
58
|
|
42
|
-
|
43
59
|
###Other Examples
|
44
60
|
|
45
61
|
RQuery supports most of the common SQL operations: =, <>, >, <, >=, <= as well as in, like (see below for specifics), and between. `:column.is_not` works for `.in`, `.between`, `.contains`, and `==`. All operations are anded as of the current version.
|
46
62
|
|
47
63
|
Operators:
|
48
64
|
|
49
|
-
@obj = ActiveRecordObject.where do
|
50
|
-
|
51
|
-
|
65
|
+
@obj = ActiveRecordObject.where do |obj|
|
66
|
+
obj.foo.is > 2
|
67
|
+
obj.foo.is_not == 4
|
52
68
|
end
|
69
|
+
|
53
70
|
#=> conditions array: ["foo > ? and foo <> ?", 2, 4]
|
54
71
|
|
55
72
|
Contains:
|
@@ -62,8 +79,8 @@ Contains:
|
|
62
79
|
|
63
80
|
In:
|
64
81
|
|
65
|
-
@obj = ActiveRecordObject.where do
|
66
|
-
|
82
|
+
@obj = ActiveRecordObject.where do |obj|
|
83
|
+
obj.foo.in "bar", "baz", "bak"
|
67
84
|
end
|
68
85
|
#=> conditions array: ["foo in (?)", ["bar", "baz", "bak"]]
|
69
86
|
#using the default sqlite adapter
|
@@ -81,6 +98,36 @@ is equivalent to the find call:
|
|
81
98
|
|
82
99
|
@obj = ActiveRecordObject.find(:first, conditions => ["foo = ?", "bar"])
|
83
100
|
|
101
|
+
###Complex queries
|
102
|
+
|
103
|
+
RQuery supports relatively complex queries including | and & operation groupings. All operations need to be on the same line and in parens and either the | operator or the & operator can be used on a singel line
|
104
|
+
|
105
|
+
User.where do |user|
|
106
|
+
(mock.age.is > 20) | (mock.age.in 16,18)
|
107
|
+
end
|
108
|
+
|
109
|
+
In the following example the & takes precedence and will be grouped with the contains "Alice" which will be or'd with the contains "George"
|
110
|
+
|
111
|
+
#=> name contains "George" or (name contains "Alice and age from 20 to 30)
|
112
|
+
User.where do |user|
|
113
|
+
(user.name.contains "George") | (user.name.contains "Alice") & (use.age.from 20..30)
|
114
|
+
end
|
115
|
+
|
116
|
+
To correct the above to the more intuitive version add parens to force precedence of the contains operations
|
117
|
+
|
118
|
+
#=> (name contains "George" or name contains "Alice) and age from 20 to 30
|
119
|
+
User.where do |user|
|
120
|
+
((user.name.contains "George") | (user.name.contains "Alice")) & (use.age.from 20..30)
|
121
|
+
end
|
122
|
+
|
123
|
+
In this sutation it would be cleaner and easier to just move the and'd statement down a line as all seperate lines are and'd and lines have precedence from top to bottom
|
124
|
+
|
125
|
+
User.where do |user|
|
126
|
+
(user.name.contains "George") | (user.name.contains "Alice")
|
127
|
+
use.age.from 20..30
|
128
|
+
end
|
129
|
+
|
130
|
+
|
84
131
|
|
85
132
|
|
86
133
|
|
data/examples/user.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#All operations need to be on the same line and in parens
|
2
|
+
#either the | operator or the & operator can be used on a singel line
|
3
|
+
User.where do |user|
|
4
|
+
(mock.age.is > 20) | (mock.age.in 16,18)
|
5
|
+
end
|
6
|
+
|
7
|
+
#the & takes precedence here and will be grouped with the contains "Alice" which will be
|
8
|
+
#or'd with the contains "George"
|
9
|
+
#=> name contains "George" or (name contains "Alice and age from 20 to 30)
|
10
|
+
User.where do |user|
|
11
|
+
(user.name.contains "George") | (user.name.contains "Alice") & (use.age.from 20..30)
|
12
|
+
end
|
13
|
+
|
14
|
+
#to correct the above to the more intuitive version add parens to force precedence of the
|
15
|
+
#contains operations
|
16
|
+
#=> (name contains "George" or name contains "Alice) and age from 20 to 30
|
17
|
+
User.where do |user|
|
18
|
+
((user.name.contains "George") | (user.name.contains "Alice")) & (use.age.from 20..30)
|
19
|
+
end
|
20
|
+
|
21
|
+
#in this sutation it would be cleaner and easier to just move the and'd statement down
|
22
|
+
#a line as all seperate lines are and'd and lines have precedence from top to bottom
|
23
|
+
#additionaly operations on seperate lines don't need parens
|
24
|
+
User.where do |user|
|
25
|
+
(user.name.contains "George") | (user.name.contains "Alice")
|
26
|
+
use.age.from 20..30
|
27
|
+
end
|
28
|
+
|
29
|
+
#should you attempt to use and attribute that doesn't exist for a given model
|
30
|
+
#rquery will tell you before it's sent to the db
|
31
|
+
User.where do |user|
|
32
|
+
user.ssn.is == 123-45-6789
|
33
|
+
end
|
34
|
+
# RQuery::AttributeNotFoundError: The field 'ssn' doesn't exist for this object
|
35
|
+
# from /Users/johnbender/Projects/rquery/lib/rquery/attribute_collection.rb:28:in `method_missing'
|
36
|
+
# from (irb):24
|
37
|
+
# from /Users/johnbender/Projects/rquery/lib/rquery/active_record/base.rb:16:in `where'
|
38
|
+
# from /Users/johnbender/Projects/rquery/lib/rquery/active_record/base.rb:11:in `synchronize'
|
39
|
+
# from /Users/johnbender/Projects/rquery/lib/rquery/active_record/base.rb:11:in `where'
|
40
|
+
# from (irb):23
|
41
|
+
|
42
|
+
#environment config
|
43
|
+
RQuery.use_symbols
|
44
|
+
|
45
|
+
#example of using symbols, you can see more at the RQuery page on my site.
|
46
|
+
User.where do |user|
|
47
|
+
(:name.contains "George") | (:name.contains "Alice")
|
48
|
+
:age.from 20..30
|
49
|
+
end
|
@@ -1,35 +1,37 @@
|
|
1
1
|
module RQuery
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
module ActiveRecord
|
3
|
+
@@where_mutex = Mutex.new
|
4
|
+
def where(*args, &block)
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
#establish scope for conditions
|
7
|
+
conditions = nil
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
#make sure only one thread at a time is altering the class
|
10
|
+
#variables inside RQuery::Serializers::Operations
|
11
|
+
@@where_mutex.synchronize do
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
#Passes a new AttributeCollection object to the block
|
14
|
+
#if RQuery.use_symbols has been called it may not be used
|
15
|
+
#but otherwise will take the form attr_coll_object.attribute.is ...
|
16
|
+
yield(RQuery::AttributeCollection.new(self.new.attribute_names))
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
+
#record altered conditions and values
|
19
|
+
conditions = ::RQuery::Serializers::Operations.conditions
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
#clear the alterations
|
22
|
+
RQuery::Serializers::Operations.clear
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
#limit the records returned (:first, :all, :last)
|
26
|
+
limit = args.first ? args.first : :all
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
28
|
+
#call find with the conditions and values provided by the block
|
29
|
+
find(limit, :conditions => conditions)
|
29
30
|
end
|
31
|
+
end
|
30
32
|
end
|
31
33
|
|
32
34
|
#extend ActiveRecord::Base with the where method
|
33
35
|
if Object.const_defined?(:ActiveRecord)
|
34
|
-
|
36
|
+
ActiveRecord::Base.send :extend, RQuery::ActiveRecord
|
35
37
|
end
|
data/lib/rquery/adapters/sql.rb
CHANGED
@@ -31,9 +31,13 @@ module RQuery
|
|
31
31
|
def contains
|
32
32
|
"like '%' + ? + '%'"
|
33
33
|
end
|
34
|
+
|
35
|
+
def and_group(ops)
|
36
|
+
"(#{ops.join(" and ")})"
|
37
|
+
end
|
34
38
|
|
35
|
-
def
|
36
|
-
|
39
|
+
def or_group(ops)
|
40
|
+
"(#{ops.join(" or ")})"
|
37
41
|
end
|
38
42
|
|
39
43
|
[:>, :>=, :<, :<=].each do |operator|
|
@@ -41,6 +45,8 @@ module RQuery
|
|
41
45
|
"#{operator} ?"
|
42
46
|
end
|
43
47
|
end
|
48
|
+
|
49
|
+
alias :join :and_group
|
44
50
|
end
|
45
51
|
end
|
46
52
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RQuery
|
2
|
+
class Attribute
|
3
|
+
#adds is, is_not, between, in, contains, and from (alias)
|
4
|
+
include RQuery::Declarations
|
5
|
+
|
6
|
+
#define name for the Declarations included methods
|
7
|
+
#as it needs to be different than the default
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def initialize(field_name)
|
11
|
+
@name = field_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RQuery
|
2
|
+
class AttributeCollection
|
3
|
+
|
4
|
+
def initialize(fields)
|
5
|
+
@fields = fields.map{ |x| x.to_s }
|
6
|
+
end
|
7
|
+
|
8
|
+
#if the field was added upon initialization its a valid call
|
9
|
+
#otherwise report to the user it is invalid. Reports errors at the ruby level
|
10
|
+
#instead of the data store level with something like "column doesn't exist"
|
11
|
+
#
|
12
|
+
#example
|
13
|
+
#
|
14
|
+
# >> user = RQuery::FieldCollection.new([:age, :name])
|
15
|
+
# => #<RQuery::FieldCollection:0x1a2c21c @fields=[:age, :name]>
|
16
|
+
# >> user.age
|
17
|
+
# => #<RQuery::Field:0x1a2b240 @name=:age>
|
18
|
+
# >> user.name
|
19
|
+
# => #<RQuery::Field:0x1a2a390 @name=:name>
|
20
|
+
# >> user.weight
|
21
|
+
# ArgumentError: The field 'weight' doesn't exist for this object
|
22
|
+
# from /Users/johnbender/Projects/rquery/lib/rquery/where_clause.rb:20:in `method_missing'
|
23
|
+
# from (irb):5
|
24
|
+
def method_missing(symbol, *args)
|
25
|
+
if @fields.include?(symbol.to_s)
|
26
|
+
Attribute.new(symbol)
|
27
|
+
else
|
28
|
+
raise AttributeNotFoundError, "The field '#{symbol.to_s}' doesn't exist for this object"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class AttributeNotFoundError < ArgumentError; end
|
35
|
+
end
|
data/lib/rquery/declarations.rb
CHANGED
@@ -1,39 +1,34 @@
|
|
1
|
-
|
2
1
|
module RQuery
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def between(*args)
|
23
|
-
::RQuery::Serializers::IsOperations.add_operation(self)
|
24
|
-
::RQuery::Serializers::IsOperations.prefix = nil
|
25
|
-
::RQuery::Serializers::IsOperations.between(*args)
|
26
|
-
end
|
27
|
-
|
28
|
-
def contains(val)
|
29
|
-
::RQuery::Serializers::IsOperations.add_operation(self)
|
30
|
-
::RQuery::Serializers::IsOperations.prefix = nil
|
31
|
-
::RQuery::Serializers::IsOperations.contains(val)
|
32
|
-
end
|
33
|
-
|
34
|
-
alias :from :between
|
2
|
+
module Declarations
|
3
|
+
#Allows the methods below to be included in
|
4
|
+
#the Field class and the Symbol class but remain
|
5
|
+
#the same in implementation
|
6
|
+
#!name is redefined as an attr_accessor in the Field class!
|
7
|
+
def name
|
8
|
+
self.to_s if @name == nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def is
|
12
|
+
Serializers::IsOperations.add_operation(name)
|
13
|
+
Serializers::IsOperations.prefix = nil
|
14
|
+
Serializers::IsOperations
|
15
|
+
end
|
16
|
+
|
17
|
+
def is_not
|
18
|
+
Serializers::IsNotOperations.add_operation(name)
|
19
|
+
Serializers::IsNotOperations.prefix = "not_"
|
20
|
+
Serializers::IsNotOperations
|
35
21
|
end
|
22
|
+
|
23
|
+
[:in, :between, :contains].each do |m|
|
24
|
+
define_method(m) do |*args|
|
25
|
+
Serializers::IsOperations.add_operation(name)
|
26
|
+
Serializers::IsOperations.prefix = nil
|
27
|
+
Serializers::IsOperations.send(m, *args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :from :between
|
32
|
+
end
|
36
33
|
end
|
37
34
|
|
38
|
-
Symbol.send :include, RQuery::Declarations
|
39
|
-
|
data/lib/rquery/serializers.rb
CHANGED
@@ -1,110 +1,184 @@
|
|
1
1
|
module RQuery
|
2
|
-
|
3
|
-
|
2
|
+
module Serializers
|
3
|
+
#The Operations serializer, handles the limiting factors imposed
|
4
|
+
#by a given query. The methods in this class apply to both is and is_not
|
5
|
+
#operations. An example in sql would look like:
|
6
|
+
#
|
7
|
+
#where foo = bar and foo > 2
|
8
|
+
#
|
9
|
+
#This class is a gateway to the selected RQuery.adapter methods.
|
10
|
+
#Calls to methods here, will be passed on to the selected adapter
|
11
|
+
class Operations
|
12
|
+
|
13
|
+
@@prefix = nil
|
14
|
+
@@ops = []
|
15
|
+
@@values = []
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def prefix=(val)
|
19
|
+
@@prefix = val
|
20
|
+
end
|
4
21
|
|
5
|
-
|
6
|
-
|
7
|
-
@@values = []
|
8
|
-
|
9
|
-
class << self
|
10
|
-
def prefix=(val)
|
11
|
-
@@prefix = val
|
12
|
-
end
|
13
|
-
|
14
|
-
def to_s
|
15
|
-
RQuery.adapter.join(@@ops)
|
16
|
-
end
|
17
|
-
|
18
|
-
def conditions
|
19
|
-
[to_s] + @@values
|
20
|
-
end
|
21
|
-
|
22
|
-
def add_operation(val)
|
23
|
-
@@ops << val.to_s
|
24
|
-
end
|
25
|
-
|
26
|
-
def clear
|
27
|
-
@@ops.clear
|
28
|
-
@@values.clear
|
29
|
-
end
|
30
|
-
|
31
|
-
def in(*args)
|
32
|
-
#flatten our args to prevent having to check for an array first arg
|
33
|
-
args.flatten!
|
34
|
-
|
35
|
-
#if a range (or anything else that responds to :first) is passed as the first argument
|
36
|
-
#use it alone, otherwise use the args array
|
37
|
-
#examples:
|
38
|
-
#ruby => args.flatten! => stored values
|
39
|
-
#:id.between 1..100 => [1..100] => 1..100
|
40
|
-
#:id.between [1, 2, 3] => [1, 2, 3] => [1, 2, 3]
|
41
|
-
#:id.between 1, 2 => [1, 2] => [1, 2]
|
42
|
-
#
|
43
|
-
@@values << (args.first.respond_to?(:first) ? args.first : args)
|
44
|
-
@@ops[@@ops.length-1] += " #{RQuery.adapter.send("#{@@prefix}in")}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def between(*args)
|
48
|
-
#flatten our args to prevent having to check for an array first arg
|
49
|
-
args.flatten!
|
50
|
-
|
51
|
-
#if a range is passed use its first/last element
|
52
|
-
#otherwise use the first and last element of the flattened args array
|
53
|
-
#examples:
|
54
|
-
#ruby => args.flatten! => stored values
|
55
|
-
#:id.between 1..100 => [1..100] => 1 100
|
56
|
-
#:id.between [1, 2, 3] => [1, 2, 3] => 1 3
|
57
|
-
#:id.between 1, 2 => [1, 2] => 1 2
|
58
|
-
#
|
59
|
-
@@values += args.first.respond_to?(:first) ? [args.first.first, args.first.last] : [args.first, args.last]
|
60
|
-
|
61
|
-
#store the operation string
|
62
|
-
#example:
|
63
|
-
#:id.between 1..100 "id between ? and ?"
|
64
|
-
#
|
65
|
-
@@ops[@@ops.length-1] += " #{RQuery.adapter.send("#{@@prefix}between")}"
|
66
|
-
end
|
67
|
-
|
68
|
-
def contains(str)
|
69
|
-
@@values << str
|
70
|
-
@@ops[@@ops.length-1] += " #{RQuery.adapter.send("#{@@prefix}contains")}"
|
71
|
-
end
|
72
|
-
|
73
|
-
#allows for is.from
|
74
|
-
#examples:
|
75
|
-
#
|
76
|
-
#:id.is.from 1,2
|
77
|
-
#:is.is.from 2..10
|
78
|
-
alias :from :between
|
79
|
-
|
80
|
-
end
|
81
|
-
|
22
|
+
def to_s
|
23
|
+
RQuery.adapter.join(@@ops)
|
82
24
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
end
|
25
|
+
|
26
|
+
#return a conditions array for use with ActiveRecord.find
|
27
|
+
def conditions
|
28
|
+
[to_s] + @@values
|
29
|
+
end
|
30
|
+
|
31
|
+
#add and operation to the @@ops array which will be popped
|
32
|
+
#and pushed depending on the operations sequence and arrangement
|
33
|
+
def add_operation(val)
|
34
|
+
@@ops << val.to_s
|
95
35
|
end
|
96
36
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
37
|
+
#clean out the Operations singleton class variables
|
38
|
+
def clear
|
39
|
+
@@ops.clear
|
40
|
+
@@values.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
#grouping is done by using the | and & operators between multiple operations
|
44
|
+
#objects on a single line.
|
45
|
+
#
|
46
|
+
#This works because each operation ie (user.age.is == 2) is
|
47
|
+
#evaluated before these two operators thus pushing
|
48
|
+
#the equivelant operation string onto the @@ops array (ie 'age = ?').
|
49
|
+
#When an operation is evaluated it returns the Operations class which can be compared
|
50
|
+
#using the aforementioned operators. Those operators call the group method
|
51
|
+
#popping the last two arguments off the stack and dealing with them in one of two ways
|
52
|
+
#
|
53
|
+
#1. if the second object popped is a string both objects should be
|
54
|
+
# added to a new OperationGroup which is then put back onto the stack
|
55
|
+
#
|
56
|
+
#2. if the second object popped is an OperationGroup the firest belongs to this group as
|
57
|
+
# well (it was on the same line). It is added to the OperationGroup and put back on the
|
58
|
+
# stack
|
59
|
+
def group(type)
|
60
|
+
second_op, first_op = @@ops.pop, @@ops.pop
|
61
|
+
|
62
|
+
#if the previous operation on the stack is an Operation Group we need to add to it
|
63
|
+
if first_op.class == RQuery::Serializers::OperationsGroup
|
64
|
+
if first_op.type == type
|
65
|
+
first_op.ops << second_op
|
66
|
+
@@ops << first_op
|
67
|
+
else
|
68
|
+
@@ops << OperationsGroup.new(first_op.to_s, second_op, type)
|
105
69
|
end
|
70
|
+
else
|
71
|
+
@@ops << OperationsGroup.new(first_op, second_op, type)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
#used to group operations for anding on a single line
|
76
|
+
#example with sqlite adapter
|
77
|
+
#(user.age.in [1,2,3]) | (user.name.contains "foo")
|
78
|
+
#=>(age in (?) and name like '%' || 'foo' || '%')
|
79
|
+
def &(second)
|
80
|
+
self.group(:and)
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def |(second)
|
85
|
+
self.group(:or)
|
86
|
+
self
|
87
|
+
end
|
106
88
|
|
89
|
+
def in(*args)
|
90
|
+
#flatten our args to prevent having to check for an array first arg
|
91
|
+
args.flatten!
|
92
|
+
|
93
|
+
#if a range is passed as the first argument
|
94
|
+
#use it alone, otherwise use the args array
|
95
|
+
#examples:
|
96
|
+
#ruby => args.flatten! => stored values
|
97
|
+
#:id.between 1..100 => [1..100] => 1..100
|
98
|
+
#:id.between [1, 2, 3] => [1, 2, 3] => [1, 2, 3]
|
99
|
+
#:id.between 1, 2 => [1, 2] => [1, 2]
|
100
|
+
@@values << (args.first.class == Range ? args.first : args)
|
101
|
+
@@ops[@@ops.length-1] += " #{RQuery.adapter.send("#{@@prefix}in")}"
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def between(*args)
|
106
|
+
#flatten our args to prevent having to check for an array first arg
|
107
|
+
args.flatten!
|
108
|
+
|
109
|
+
#if a range is passed use its first/last element
|
110
|
+
#otherwise use the first and last element of the flattened args array
|
111
|
+
#examples:
|
112
|
+
#ruby => args.flatten! => stored values
|
113
|
+
#:id.between 1..100 => [1..100] => 1 100
|
114
|
+
#:id.between [1, 2, 3] => [1, 2, 3] => 1 3
|
115
|
+
#:id.between 1, 2 => [1, 2] => 1 2
|
116
|
+
#
|
117
|
+
@@values += (args.first.class == Range ? [args.first.first, args.first.last] : [args.first, args.last])
|
118
|
+
@@ops[@@ops.length-1] += " #{RQuery.adapter.send("#{@@prefix}between")}"
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def contains(str)
|
123
|
+
@@values << str
|
124
|
+
@@ops[@@ops.length-1] += " #{RQuery.adapter.send("#{@@prefix}contains")}"
|
125
|
+
self
|
107
126
|
end
|
127
|
+
|
128
|
+
#allows for is.from
|
129
|
+
#examples:
|
130
|
+
#
|
131
|
+
#:id.is.from 1,2
|
132
|
+
#:is.is.from 2..10
|
133
|
+
alias :from :between
|
134
|
+
end
|
135
|
+
end
|
108
136
|
|
137
|
+
class OperationsGroup
|
138
|
+
attr_accessor :ops, :type
|
139
|
+
|
140
|
+
def initialize(left, right, type)
|
141
|
+
@ops = Array.new
|
142
|
+
@ops << left
|
143
|
+
@ops << right
|
144
|
+
@type = type
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_s
|
148
|
+
RQuery.adapter.send("#{type.to_s}_group", @ops)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
#The IsOpertaions serializer defines only methods that apply to the .is operator
|
153
|
+
#that is added to the Symbol class in the Declarations module.
|
154
|
+
class IsOperations < Operations
|
155
|
+
#define the normal operators for is to call the adapter for the equivelant sql
|
156
|
+
class << self
|
157
|
+
[:==, :>, :>=, :<, :<=].each do |operator|
|
158
|
+
define_method(operator) do |val|
|
159
|
+
@@values << val
|
160
|
+
@@ops[@@ops.length-1] += " #{RQuery.adapter.send(operator)}"
|
161
|
+
self
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
109
166
|
end
|
167
|
+
|
168
|
+
#The IsNotOperations serializer defines only methods that apply to the .is_not operator
|
169
|
+
#that is added to the symbol class in the declarations module. Specifically, == as
|
170
|
+
#ruby does not allow for the overloading of !=
|
171
|
+
class IsNotOperations < Operations
|
172
|
+
#define the == value for is_not to call the adapter for the equivelant sql
|
173
|
+
class << self
|
174
|
+
def ==(val)
|
175
|
+
@@values << val
|
176
|
+
@@ops[@@ops.length-1] += " #{RQuery.adapter.send(:neq)}"
|
177
|
+
self
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
110
184
|
end
|