dm-ambition 1.0.0 → 1.1.0.rc1
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/Gemfile +77 -0
- data/LICENSE +1 -1
- data/README.rdoc +15 -6
- data/Rakefile +3 -9
- data/TODO +13 -0
- data/dm-ambition.gemspec +70 -56
- data/lib/dm-ambition/collection.rb +18 -48
- data/lib/dm-ambition/model.rb +1 -0
- data/lib/dm-ambition/query/filter_processor.rb +149 -255
- data/lib/dm-ambition/query.rb +4 -5
- data/lib/dm-ambition/version.rb +1 -1
- data/lib/dm-ambition.rb +7 -0
- data/spec/public/collection_spec.rb +102 -68
- data/spec/public/model_spec.rb +8 -11
- data/spec/public/shared/filter_shared_spec.rb +45 -9
- data/spec/semipublic/query_spec.rb +283 -67
- data/spec/spec_helper.rb +9 -41
- data/tasks/local_gemfile.rake +16 -0
- data/tasks/spec.rake +0 -3
- metadata +82 -63
- data/.gitignore +0 -35
@@ -1,184 +1,95 @@
|
|
1
|
-
require 'parse_tree'
|
2
|
-
require 'parse_tree_extensions'
|
3
|
-
require 'ruby2ruby'
|
4
|
-
|
5
1
|
module DataMapper
|
6
2
|
module Ambition
|
7
3
|
module Query
|
8
4
|
class FilterProcessor < SexpProcessor
|
9
5
|
attr_reader :conditions
|
10
6
|
|
11
|
-
def initialize(binding, model
|
7
|
+
def initialize(binding, model)
|
12
8
|
super()
|
13
9
|
|
14
10
|
self.expected = Object # allow anything for now
|
15
11
|
self.auto_shift_type = true
|
16
12
|
self.default_method = :process_error
|
17
13
|
|
18
|
-
@binding
|
19
|
-
@model
|
20
|
-
@receiver = nil
|
21
|
-
|
22
|
-
@container = DataMapper::Query::Conditions::Operation.new(:and)
|
14
|
+
@binding = binding
|
15
|
+
@model = model
|
23
16
|
|
24
|
-
|
25
|
-
@conditions = DataMapper::Query::Conditions::Operation.new(:not, @container)
|
26
|
-
else
|
27
|
-
@conditions = @container
|
28
|
-
end
|
17
|
+
@conditions = @container = DataMapper::Query::Conditions::Operation.new(:and)
|
29
18
|
end
|
30
19
|
|
31
20
|
def process_error(exp)
|
32
|
-
raise "
|
21
|
+
raise ArgumentError, "calling process_#{exp.shift} with #{exp.inspect}"
|
33
22
|
end
|
34
23
|
|
35
24
|
def process_iter(exp)
|
36
|
-
call_argslist = exp.shift
|
37
|
-
raise "DEBUG: invalid: #{call_argslist.inspct}" if call_argslist != s(:call, nil, :proc, s(:arglist))
|
38
|
-
|
39
|
-
# get the reciever
|
40
|
-
@receiver = process(exp.shift)
|
41
|
-
|
42
|
-
# process the Proc body
|
43
|
-
result = process(exp.shift)
|
44
|
-
|
45
|
-
if result.nil?
|
46
|
-
raise 'DEBUG: conditions must be supplied'
|
47
|
-
end
|
48
|
-
|
49
|
-
unless result.equal?(@container)
|
50
|
-
raise "DEBUG: invalid result processing body: #{result.inspect} (expected #{@container.inspect})"
|
51
|
-
end
|
52
|
-
|
53
|
-
result
|
54
|
-
end
|
55
|
-
|
56
|
-
def process_lasgn(exp)
|
57
25
|
exp.shift
|
26
|
+
@receiver = process(exp.shift)
|
27
|
+
process(exp.shift)
|
58
28
|
end
|
59
29
|
|
60
30
|
def process_call(exp)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
rhs = process(exp.shift)
|
65
|
-
|
66
|
-
if operator == :send
|
67
|
-
operator = rhs.shift
|
68
|
-
end
|
31
|
+
lhs = process(exp.shift)
|
32
|
+
operator = exp.shift
|
33
|
+
rhs = process(exp.shift)
|
69
34
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
rhs = rhs.first
|
74
|
-
end
|
35
|
+
while [ :send, :__send__ ].include?(operator)
|
36
|
+
operator = rhs.shift
|
37
|
+
end
|
75
38
|
|
76
|
-
|
77
|
-
|
78
|
-
else
|
79
|
-
evaluate_operator(operator, lhs, rhs)
|
80
|
-
end
|
39
|
+
if lhs.nil? && operator != :==
|
40
|
+
call_method(operator, *rhs)
|
81
41
|
else
|
82
|
-
|
42
|
+
evaluate_operator(operator, lhs, rhs.shift)
|
83
43
|
end
|
84
44
|
end
|
85
45
|
|
86
46
|
def process_and(exp)
|
87
|
-
|
88
|
-
|
89
|
-
begin
|
90
|
-
unless @container.kind_of?(DataMapper::Query::Conditions::AndOperation)
|
91
|
-
parent << @container = DataMapper::Query::Conditions::Operation.new(:and)
|
92
|
-
end
|
93
|
-
|
94
|
-
while sexp = exp.shift
|
95
|
-
process(sexp)
|
96
|
-
end
|
97
|
-
ensure
|
98
|
-
@container = parent
|
99
|
-
end
|
100
|
-
|
101
|
-
@container
|
47
|
+
process_connective(exp, :and)
|
102
48
|
end
|
103
49
|
|
104
50
|
def process_or(exp)
|
105
|
-
|
106
|
-
|
107
|
-
begin
|
108
|
-
unless @container.kind_of?(DataMapper::Query::Conditions::OrOperation)
|
109
|
-
parent << @container = DataMapper::Query::Conditions::Operation.new(:or)
|
110
|
-
end
|
111
|
-
|
112
|
-
while sexp = exp.shift
|
113
|
-
process(sexp)
|
114
|
-
end
|
115
|
-
ensure
|
116
|
-
@container = parent
|
117
|
-
end
|
118
|
-
|
119
|
-
@container
|
51
|
+
process_connective(exp, :or)
|
120
52
|
end
|
121
53
|
|
122
54
|
def process_not(exp)
|
123
|
-
|
124
|
-
|
125
|
-
begin
|
126
|
-
parent << @container = DataMapper::Query::Conditions::Operation.new(:not)
|
127
|
-
process(exp.shift)
|
128
|
-
ensure
|
129
|
-
@container = parent
|
130
|
-
end
|
131
|
-
|
132
|
-
@container
|
55
|
+
process_connective(exp, :not)
|
133
56
|
end
|
134
57
|
|
135
58
|
def process_lvar(exp)
|
136
|
-
|
137
|
-
|
59
|
+
lvar = exp.shift
|
60
|
+
lvar.equal?(@receiver) ? @receiver : eval(lvar)
|
138
61
|
end
|
139
62
|
|
140
63
|
def process_arglist(exp)
|
141
|
-
|
142
|
-
while sexp = exp.shift
|
143
|
-
arglist << process(sexp)
|
144
|
-
end
|
145
|
-
arglist
|
146
|
-
end
|
147
|
-
|
148
|
-
def process_colon2(exp)
|
149
|
-
const = process(exp.shift)
|
150
|
-
|
151
|
-
const.const_get(exp.shift)
|
64
|
+
process_array(exp)
|
152
65
|
end
|
153
66
|
|
154
|
-
def
|
155
|
-
|
67
|
+
def process_block(exp)
|
68
|
+
process_array(exp).last
|
156
69
|
end
|
157
70
|
|
158
71
|
def process_match3(exp)
|
159
|
-
|
160
|
-
|
72
|
+
evaluate_operator(:=~, process(exp.shift), process(exp.shift))
|
73
|
+
end
|
161
74
|
|
162
|
-
|
75
|
+
def process_colon2(exp)
|
76
|
+
process(exp.shift).const_get(exp.shift)
|
163
77
|
end
|
164
78
|
|
165
|
-
def
|
166
|
-
|
167
|
-
while sexp = exp.shift
|
168
|
-
array << process(sexp)
|
169
|
-
end
|
170
|
-
array
|
79
|
+
def process_const(exp)
|
80
|
+
Object.const_get(exp.shift)
|
171
81
|
end
|
172
82
|
|
173
|
-
def
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
83
|
+
def process_masgn(exp)
|
84
|
+
vars, values = process(exp.shift), process(exp.shift)
|
85
|
+
vars.zip(values) { |var, value| assign_value(var, value) }
|
86
|
+
values
|
87
|
+
end
|
178
88
|
|
179
|
-
|
180
|
-
|
181
|
-
|
89
|
+
def process_lasgn(exp)
|
90
|
+
var = exp.shift
|
91
|
+
return var if exp.empty?
|
92
|
+
assign_value(var, process(exp.shift))
|
182
93
|
end
|
183
94
|
|
184
95
|
def process_str(exp)
|
@@ -186,9 +97,15 @@ module DataMapper
|
|
186
97
|
end
|
187
98
|
|
188
99
|
def process_lit(exp)
|
189
|
-
|
190
|
-
|
191
|
-
|
100
|
+
exp.shift
|
101
|
+
end
|
102
|
+
|
103
|
+
def process_ivar(exp)
|
104
|
+
eval(exp.shift)
|
105
|
+
end
|
106
|
+
|
107
|
+
def process_gvar(exp)
|
108
|
+
eval(exp.shift)
|
192
109
|
end
|
193
110
|
|
194
111
|
def process_true(exp)
|
@@ -203,158 +120,135 @@ module DataMapper
|
|
203
120
|
nil
|
204
121
|
end
|
205
122
|
|
206
|
-
def
|
207
|
-
|
123
|
+
def process_array(exp)
|
124
|
+
array = []
|
125
|
+
array << process(exp.shift) until exp.empty?
|
126
|
+
array
|
208
127
|
end
|
209
128
|
|
210
|
-
def
|
211
|
-
|
129
|
+
def process_hash(exp)
|
130
|
+
hash = {}
|
131
|
+
hash[process(exp.shift)] = process(exp.shift) until exp.empty?
|
132
|
+
hash
|
212
133
|
end
|
213
134
|
|
214
|
-
|
215
|
-
if operator == :=~ && !lhs.kind_of?(Regexp) && !rhs.kind_of?(Regexp)
|
216
|
-
raise "DEBUG: when using =~ operator one side should be a Regexp"
|
217
|
-
end
|
135
|
+
private
|
218
136
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
137
|
+
def process_connective(exp, operation)
|
138
|
+
parent, @container = @container, DataMapper::Query::Conditions::Operation.new(operation)
|
139
|
+
process(exp.shift) until exp.empty?
|
140
|
+
parent << @container
|
141
|
+
ensure
|
142
|
+
@container = parent
|
143
|
+
end
|
224
144
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
145
|
+
def evaluate_operator(operator, lhs, rhs)
|
146
|
+
if lhs.equal?(@receiver) then evaluate_receiver_source(operator, lhs, rhs)
|
147
|
+
elsif rhs.equal?(@receiver) then evaluate_receiver_target(operator, lhs, rhs)
|
148
|
+
elsif lhs.kind_of?(DataMapper::Property) then evaluate_property_source(operator, lhs, rhs)
|
149
|
+
elsif rhs.kind_of?(DataMapper::Property) then evaluate_property_target(operator, lhs, rhs)
|
150
|
+
else
|
151
|
+
lhs.send(operator, *Array(rhs))
|
152
|
+
end
|
153
|
+
end
|
229
154
|
|
230
|
-
|
231
|
-
|
232
|
-
|
155
|
+
def evaluate_receiver_source(operator, lhs, rhs)
|
156
|
+
if rhs.nil? && @model.properties.named?(operator)
|
157
|
+
@model.properties[operator]
|
158
|
+
else
|
159
|
+
resources = operator == :== ? [ rhs ] : []
|
160
|
+
key = @model.key
|
161
|
+
add_condition(DataMapper::Query.target_conditions(resources, key, key))
|
162
|
+
end
|
163
|
+
end
|
233
164
|
|
234
|
-
|
165
|
+
def evaluate_receiver_target(operator, lhs, rhs)
|
166
|
+
resources = case lhs
|
167
|
+
when Hash
|
168
|
+
case operator
|
169
|
+
when :key?, :has_key?, :include?, :member? then lhs.keys.sort
|
170
|
+
when :value?, :has_value? then lhs.values.sort
|
171
|
+
end
|
172
|
+
when Enumerable
|
173
|
+
case operator
|
174
|
+
when :include?, :member? then lhs
|
175
|
+
end
|
176
|
+
when Resource
|
177
|
+
case operator
|
178
|
+
when :==, :eql? then lhs
|
235
179
|
end
|
236
180
|
else
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
elsif rhs == @model
|
241
|
-
if @model.key.size > 1
|
242
|
-
raise 'Until OR conditions are added can only match resources with single keys'
|
243
|
-
end
|
244
|
-
|
245
|
-
resources = case lhs
|
246
|
-
when Array
|
247
|
-
case operator
|
248
|
-
when :include?, :member? then lhs
|
249
|
-
end
|
250
|
-
|
251
|
-
when Hash
|
252
|
-
case operator
|
253
|
-
when :key?, :has_key?, :include?, :member? then lhs.keys
|
254
|
-
when :value?, :has_value? then lhs.values
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
unless resources
|
259
|
-
raise "DEBUG: cannot call #{lhs.class}##{operator} with #{rhs.inspect}"
|
260
|
-
end
|
261
|
-
|
262
|
-
unless resources.all? { |r| r.kind_of?(DataMapper::Resource) }
|
263
|
-
raise 'cannot compare against a non-resource'
|
264
|
-
end
|
265
|
-
|
266
|
-
property = @model.key.first
|
267
|
-
bind_value = resources.map { |r| r.key.first }.sort
|
268
|
-
|
269
|
-
evaluate_operator(:include?, bind_value, property)
|
270
|
-
|
271
|
-
elsif lhs.kind_of?(DataMapper::Property)
|
272
|
-
property = lhs
|
273
|
-
bind_value = rhs
|
274
|
-
|
275
|
-
# TODO: throw an exception if the operator is :== and the value is an Array
|
276
|
-
# - this prevents conditions like { |u| u.val == [ 1, 2, 3 ] }
|
277
|
-
|
278
|
-
if operator == :nil? && bind_value.nil?
|
279
|
-
operator = :==
|
280
|
-
bind_value = nil
|
281
|
-
end
|
282
|
-
|
283
|
-
operator = remap_operator(operator)
|
284
|
-
|
285
|
-
@container << DataMapper::Query::Conditions::Comparison.new(operator, property, bind_value)
|
286
|
-
@container
|
287
|
-
|
288
|
-
elsif rhs.kind_of?(DataMapper::Property)
|
289
|
-
property = rhs
|
290
|
-
bind_value = lhs
|
291
|
-
|
292
|
-
# TODO: throw an exception if the operator is :== and the bind value is an Array
|
293
|
-
# - this prevents conditions like { |u| [ 1, 2, 3 ] == u.val }
|
294
|
-
|
295
|
-
case bind_value
|
296
|
-
when Array
|
297
|
-
case operator
|
298
|
-
when :include?, :member?
|
299
|
-
operator = :in
|
300
|
-
else
|
301
|
-
raise "DEBUG: cannot call Array##{operator} with #{bind_value.inspect}"
|
302
|
-
end
|
303
|
-
|
304
|
-
when Range
|
305
|
-
case operator
|
306
|
-
when :include?, :member?, :===
|
307
|
-
operator = :in
|
308
|
-
else
|
309
|
-
raise "DEBUG: cannot call Range##{operator} with #{bind_value.inspect}"
|
310
|
-
end
|
311
|
-
|
312
|
-
when Hash
|
313
|
-
case operator
|
314
|
-
when :key?, :has_key?, :include?, :member?
|
315
|
-
operator = :in
|
316
|
-
bind_value = bind_value.keys
|
317
|
-
when :value?, :has_value?
|
318
|
-
operator = :in
|
319
|
-
bind_value = bind_value.values
|
320
|
-
else
|
321
|
-
raise "DEBUG: cannot call Hash##{operator} with #{bind_value.inspect}"
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
|
-
operator = remap_operator(operator)
|
326
|
-
|
327
|
-
@container << DataMapper::Query::Conditions::Comparison.new(operator, property, bind_value)
|
328
|
-
@container
|
329
|
-
|
330
|
-
elsif lhs.respond_to?(operator)
|
331
|
-
lhs.send(operator, *[ rhs ].compact)
|
181
|
+
[]
|
182
|
+
end
|
332
183
|
|
333
|
-
|
334
|
-
|
184
|
+
key = @model.key
|
185
|
+
add_condition(DataMapper::Query.target_conditions(resources, key, key))
|
186
|
+
end
|
187
|
+
|
188
|
+
def evaluate_property_source(operator, lhs, rhs)
|
189
|
+
operator = operator == :nil? ? :eql : remap_operator(operator)
|
190
|
+
add_condition(DataMapper::Query::Conditions::Comparison.new(operator, lhs, rhs))
|
191
|
+
end
|
192
|
+
|
193
|
+
def evaluate_property_target(operator, lhs, rhs)
|
194
|
+
bind_value = lhs
|
335
195
|
|
196
|
+
operator = case bind_value
|
197
|
+
when Hash
|
198
|
+
case operator
|
199
|
+
when :key?, :has_key?, :include?, :member?
|
200
|
+
bind_value = bind_value.keys.sort
|
201
|
+
:in
|
202
|
+
when :value?, :has_value?
|
203
|
+
bind_value = bind_value.values.sort
|
204
|
+
:in
|
205
|
+
end
|
206
|
+
when Range
|
207
|
+
case operator
|
208
|
+
when :include?, :member?, :===
|
209
|
+
:in
|
210
|
+
end
|
211
|
+
when Enumerable
|
212
|
+
case operator
|
213
|
+
when :include?, :member?
|
214
|
+
bind_value = bind_value.sort
|
215
|
+
:in
|
216
|
+
end
|
217
|
+
else
|
218
|
+
remap_operator(operator)
|
336
219
|
end
|
220
|
+
|
221
|
+
add_condition(DataMapper::Query::Conditions::Comparison.new(operator, rhs, bind_value))
|
337
222
|
end
|
338
223
|
|
339
|
-
# TODO: update dm-core internals to use the Ruby operators
|
340
|
-
# insted of the DM specific ones
|
341
224
|
def remap_operator(operator)
|
342
225
|
# remap Ruby to DM operators
|
343
226
|
case operator
|
344
|
-
when :in then :in
|
345
227
|
when :== then :eql
|
346
228
|
when :=~ then :regexp
|
347
229
|
when :> then :gt
|
348
230
|
when :>= then :gte
|
349
231
|
when :< then :lt
|
350
232
|
when :<= then :lte
|
351
|
-
else raise "DEBUG: unknown operator #{operator}"
|
352
233
|
end
|
353
234
|
end
|
354
235
|
|
355
|
-
def
|
356
|
-
|
236
|
+
def eval(value, binding = @binding)
|
237
|
+
super(value.to_s, binding)
|
238
|
+
end
|
239
|
+
|
240
|
+
def call_method(name, *args)
|
241
|
+
eval("method(#{name.inspect})").call(*args)
|
357
242
|
end
|
243
|
+
|
244
|
+
def assign_value(var, value)
|
245
|
+
eval("#{var} = #{value.inspect}")
|
246
|
+
end
|
247
|
+
|
248
|
+
def add_condition(condition)
|
249
|
+
@container << condition
|
250
|
+
end
|
251
|
+
|
358
252
|
end # class FilterProcessor
|
359
253
|
end # module Query
|
360
254
|
end # module Ambition
|
data/lib/dm-ambition/query.rb
CHANGED
@@ -5,19 +5,18 @@ module DataMapper
|
|
5
5
|
|
6
6
|
# TODO: spec and document this
|
7
7
|
# @api semipublic
|
8
|
-
def filter(
|
8
|
+
def filter(&block)
|
9
9
|
# TODO: benchmark Marshal versus just building the sexp on demand
|
10
10
|
|
11
11
|
# deep clone the sexp for multiple re-use
|
12
12
|
sexp = Marshal.load(@@sexps[block.to_s] ||= Marshal.dump(block.to_sexp))
|
13
13
|
|
14
|
-
processor = FilterProcessor.new(block.binding, model
|
14
|
+
processor = FilterProcessor.new(block.binding, model)
|
15
15
|
processor.process(sexp)
|
16
16
|
|
17
|
-
merge(:conditions => processor.conditions)
|
17
|
+
self.class.new(repository, model, options.merge(:conditions => conditions & processor.conditions))
|
18
18
|
end
|
19
|
+
|
19
20
|
end # module Query
|
20
21
|
end # module Ambition
|
21
22
|
end # module DataMapper
|
22
|
-
|
23
|
-
require Pathname(__FILE__).dirname.expand_path / 'query' / 'filter_processor'
|
data/lib/dm-ambition/version.rb
CHANGED
data/lib/dm-ambition.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'sourcify'
|
3
|
+
require 'ruby2ruby'
|
4
|
+
|
1
5
|
require 'dm-ambition/collection'
|
2
6
|
require 'dm-ambition/model'
|
7
|
+
|
3
8
|
require 'dm-ambition/query'
|
9
|
+
require 'dm-ambition/query/filter_processor'
|
10
|
+
|
4
11
|
require 'dm-ambition/version'
|
5
12
|
|
6
13
|
module DataMapper
|