dm-ambition 0.10.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/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/Manifest.txt +23 -0
- data/README.rdoc +118 -0
- data/Rakefile +35 -0
- data/TODO +0 -0
- data/dm-ambition.gemspec +89 -0
- data/lib/dm-ambition/collection.rb +73 -0
- data/lib/dm-ambition/model.rb +28 -0
- data/lib/dm-ambition/query/filter_processor.rb +361 -0
- data/lib/dm-ambition/query.rb +23 -0
- data/lib/dm-ambition/version.rb +5 -0
- data/lib/dm-ambition.rb +50 -0
- data/spec/public/collection_spec.rb +138 -0
- data/spec/public/model_spec.rb +34 -0
- data/spec/public/shared/filter_shared_spec.rb +221 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/query_spec.rb +718 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +49 -0
- data/tasks/ci.rake +1 -0
- data/tasks/clean.rake +6 -0
- data/tasks/heckle.rake +52 -0
- data/tasks/metrics.rake +37 -0
- data/tasks/spec.rake +41 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +20 -0
- metadata +137 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Dan Kubb
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
.gitignore
|
2
|
+
History.txt
|
3
|
+
LICENSE
|
4
|
+
Manifest.txt
|
5
|
+
README.txt
|
6
|
+
Rakefile
|
7
|
+
TODO
|
8
|
+
dm-ambition.gemspec
|
9
|
+
lib/dm-ambition.rb
|
10
|
+
lib/dm-ambition/collection.rb
|
11
|
+
lib/dm-ambition/model.rb
|
12
|
+
lib/dm-ambition/query.rb
|
13
|
+
lib/dm-ambition/query/filter_processor.rb
|
14
|
+
lib/dm-ambition/version.rb
|
15
|
+
spec/public/collection_spec.rb
|
16
|
+
spec/public/model_spec.rb
|
17
|
+
spec/public/shared/filter_shared_spec.rb
|
18
|
+
spec/semipublic/query_spec.rb
|
19
|
+
spec/spec.opts
|
20
|
+
spec/spec_helper.rb
|
21
|
+
tasks/hoe.rb
|
22
|
+
tasks/install.rb
|
23
|
+
tasks/spec.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
= dm-ambition
|
2
|
+
|
3
|
+
DataMapper::Ambition is a plugin that provides an Ambition-like API for
|
4
|
+
accessing DataMapper models.
|
5
|
+
|
6
|
+
== Installation
|
7
|
+
|
8
|
+
With Rubygems:
|
9
|
+
|
10
|
+
$ sudo gem install dm-ambition
|
11
|
+
$ irb -rubygems
|
12
|
+
>> require 'dm-core'
|
13
|
+
>> require 'dm-ambition'
|
14
|
+
=> true
|
15
|
+
|
16
|
+
With git and local working copy:
|
17
|
+
|
18
|
+
$ git clone git://github.com/dkubb/dm-ambition.git
|
19
|
+
$ cd dm-ambition
|
20
|
+
$ rake build && sudo rake install
|
21
|
+
$ irb -rubygems
|
22
|
+
>> require 'dm-core'
|
23
|
+
>> require 'dm-ambition'
|
24
|
+
=> true
|
25
|
+
|
26
|
+
== Examples
|
27
|
+
|
28
|
+
# with == operator
|
29
|
+
User.select { |u| u.name == 'Dan Kubb' }
|
30
|
+
|
31
|
+
# with =~ operator
|
32
|
+
User.select { |u| u.name =~ /Dan Kubb/ }
|
33
|
+
|
34
|
+
# with > operator
|
35
|
+
User.select { |u| u.id > 1 }
|
36
|
+
|
37
|
+
# with >= operator
|
38
|
+
User.select { |u| u.id >= 1 }
|
39
|
+
|
40
|
+
# with < operator
|
41
|
+
User.select { |u| u.id < 1 }
|
42
|
+
|
43
|
+
# with <= operator
|
44
|
+
User.select { |u| u.id <= 1 }
|
45
|
+
|
46
|
+
# with < operator
|
47
|
+
User.select { |u| u.id < 1 }
|
48
|
+
|
49
|
+
# with receiver.attribute.nil?
|
50
|
+
User.select { |u| u.id.nil? }
|
51
|
+
|
52
|
+
# with nil bind value
|
53
|
+
User.select { |u| u.id == nil }
|
54
|
+
|
55
|
+
# with true bind value
|
56
|
+
User.select { |u| u.admin == true }
|
57
|
+
|
58
|
+
# with false bind value
|
59
|
+
User.select { |u| u.admin == false }
|
60
|
+
|
61
|
+
# with AND conditions
|
62
|
+
User.select { |u| u.id == 1 && u.name == 'Dan Kubb' }
|
63
|
+
|
64
|
+
# with negated conditions
|
65
|
+
User.select { |u| !(u.id == 1) }
|
66
|
+
|
67
|
+
# with double-negated conditions
|
68
|
+
User.select { |u| !(!(u.id == 1)) }
|
69
|
+
|
70
|
+
# with receiver matching
|
71
|
+
User.select { |u| u == user }
|
72
|
+
|
73
|
+
# using send on receiver
|
74
|
+
User.select { |u| u.send(:name) == 'Dan Kubb' }
|
75
|
+
|
76
|
+
# with Array#include?
|
77
|
+
User.select { |u| user_ids.include?(u.id) }
|
78
|
+
|
79
|
+
# with Range#include?
|
80
|
+
User.select { |u| (1..10).include?(u.id) }
|
81
|
+
|
82
|
+
# with Hash#key?
|
83
|
+
User.select { |u| { 1 => 'Dan Kubb' }.key?(u.id) }
|
84
|
+
|
85
|
+
# with Hash#value?
|
86
|
+
User.select { |u| { 'Dan Kubb' => 1 }.value?(u.id) }
|
87
|
+
|
88
|
+
# with receiver and Array#include?
|
89
|
+
User.select { |u| users.include?(u) }
|
90
|
+
|
91
|
+
# with receiver and Hash#key?
|
92
|
+
User.select { |u| { user => 'Dan Kubb' }.key?(u) }
|
93
|
+
|
94
|
+
# with receiver and Hash#value?
|
95
|
+
User.select { |u| { 'Dan Kubb' => user }.value?(u) }
|
96
|
+
|
97
|
+
== License
|
98
|
+
|
99
|
+
Copyright (c) 2009 Dan Kubb
|
100
|
+
|
101
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
102
|
+
a copy of this software and associated documentation files (the
|
103
|
+
"Software"), to deal in the Software without restriction, including
|
104
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
105
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
106
|
+
permit persons to whom the Software is furnished to do so, subject to
|
107
|
+
the following conditions:
|
108
|
+
|
109
|
+
The above copyright notice and this permission notice shall be
|
110
|
+
included in all copies or substantial portions of the Software.
|
111
|
+
|
112
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
113
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
114
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
115
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
116
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
117
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
118
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
require File.expand_path('../lib/dm-ambition/version', __FILE__)
|
5
|
+
|
6
|
+
begin
|
7
|
+
gem 'jeweler', '~> 1.4'
|
8
|
+
require 'jeweler'
|
9
|
+
|
10
|
+
Jeweler::Tasks.new do |gem|
|
11
|
+
gem.name = 'dm-ambition'
|
12
|
+
gem.summary = 'DataMapper plugin providing an Ambition-like API'
|
13
|
+
gem.description = gem.summary
|
14
|
+
gem.email = 'dan.kubb@gmail.com'
|
15
|
+
gem.homepage = 'http://github.com/dkubb/%s' % gem.name
|
16
|
+
gem.authors = [ 'Dan Kubb' ]
|
17
|
+
|
18
|
+
gem.version = DataMapper::Ambition::VERSION
|
19
|
+
|
20
|
+
gem.rubyforge_project = 'dm-ambition'
|
21
|
+
|
22
|
+
gem.add_dependency 'dm-core', '~> 0.10.2'
|
23
|
+
gem.add_dependency 'ParseTree', '~> 3.0.4'
|
24
|
+
gem.add_dependency 'ruby2ruby', '~> 1.2.4'
|
25
|
+
|
26
|
+
gem.add_development_dependency 'rspec', '~> 1.2.9'
|
27
|
+
gem.add_development_dependency 'yard', '~> 0.4.0'
|
28
|
+
end
|
29
|
+
|
30
|
+
Jeweler::GemcutterTasks.new
|
31
|
+
|
32
|
+
FileList['tasks/**/*.rake'].each { |task| import task }
|
33
|
+
rescue LoadError
|
34
|
+
puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
|
35
|
+
end
|
data/TODO
ADDED
File without changes
|
data/dm-ambition.gemspec
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dm-ambition}
|
8
|
+
s.version = "0.10.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Dan Kubb"]
|
12
|
+
s.date = %q{2009-12-14}
|
13
|
+
s.description = %q{DataMapper plugin providing an Ambition-like API}
|
14
|
+
s.email = %q{dan.kubb@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc",
|
18
|
+
"TODO"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
".gitignore",
|
23
|
+
"LICENSE",
|
24
|
+
"Manifest.txt",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"TODO",
|
28
|
+
"dm-ambition.gemspec",
|
29
|
+
"lib/dm-ambition.rb",
|
30
|
+
"lib/dm-ambition/collection.rb",
|
31
|
+
"lib/dm-ambition/model.rb",
|
32
|
+
"lib/dm-ambition/query.rb",
|
33
|
+
"lib/dm-ambition/query/filter_processor.rb",
|
34
|
+
"lib/dm-ambition/version.rb",
|
35
|
+
"spec/public/collection_spec.rb",
|
36
|
+
"spec/public/model_spec.rb",
|
37
|
+
"spec/public/shared/filter_shared_spec.rb",
|
38
|
+
"spec/rcov.opts",
|
39
|
+
"spec/semipublic/query_spec.rb",
|
40
|
+
"spec/spec.opts",
|
41
|
+
"spec/spec_helper.rb",
|
42
|
+
"tasks/ci.rake",
|
43
|
+
"tasks/clean.rake",
|
44
|
+
"tasks/heckle.rake",
|
45
|
+
"tasks/metrics.rake",
|
46
|
+
"tasks/spec.rake",
|
47
|
+
"tasks/yard.rake",
|
48
|
+
"tasks/yardstick.rake"
|
49
|
+
]
|
50
|
+
s.homepage = %q{http://github.com/dkubb/dm-ambition}
|
51
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubyforge_project = %q{dm-ambition}
|
54
|
+
s.rubygems_version = %q{1.3.5}
|
55
|
+
s.summary = %q{DataMapper plugin providing an Ambition-like API}
|
56
|
+
s.test_files = [
|
57
|
+
"spec/public/collection_spec.rb",
|
58
|
+
"spec/public/model_spec.rb",
|
59
|
+
"spec/public/shared/filter_shared_spec.rb",
|
60
|
+
"spec/semipublic/query_spec.rb",
|
61
|
+
"spec/spec_helper.rb"
|
62
|
+
]
|
63
|
+
|
64
|
+
if s.respond_to? :specification_version then
|
65
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
66
|
+
s.specification_version = 3
|
67
|
+
|
68
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
69
|
+
s.add_runtime_dependency(%q<dm-core>, ["~> 0.10.2"])
|
70
|
+
s.add_runtime_dependency(%q<ParseTree>, ["~> 3.0.4"])
|
71
|
+
s.add_runtime_dependency(%q<ruby2ruby>, ["~> 1.2.4"])
|
72
|
+
s.add_development_dependency(%q<rspec>, ["~> 1.2.9"])
|
73
|
+
s.add_development_dependency(%q<yard>, ["~> 0.4.0"])
|
74
|
+
else
|
75
|
+
s.add_dependency(%q<dm-core>, ["~> 0.10.2"])
|
76
|
+
s.add_dependency(%q<ParseTree>, ["~> 3.0.4"])
|
77
|
+
s.add_dependency(%q<ruby2ruby>, ["~> 1.2.4"])
|
78
|
+
s.add_dependency(%q<rspec>, ["~> 1.2.9"])
|
79
|
+
s.add_dependency(%q<yard>, ["~> 0.4.0"])
|
80
|
+
end
|
81
|
+
else
|
82
|
+
s.add_dependency(%q<dm-core>, ["~> 0.10.2"])
|
83
|
+
s.add_dependency(%q<ParseTree>, ["~> 3.0.4"])
|
84
|
+
s.add_dependency(%q<ruby2ruby>, ["~> 1.2.4"])
|
85
|
+
s.add_dependency(%q<rspec>, ["~> 1.2.9"])
|
86
|
+
s.add_dependency(%q<yard>, ["~> 0.4.0"])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# TODO: update Collection#select in dm-core to return a Collection
|
2
|
+
# TODO: update Collection#reject in dm-core to return a Collection
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Ambition
|
6
|
+
module Collection
|
7
|
+
def self.included(base)
|
8
|
+
base.send(:alias_method, :find_all, :select)
|
9
|
+
base.send(:alias_method, :find, :detect)
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO: add empty?
|
13
|
+
# TODO: add any? (handle with and without block)
|
14
|
+
# TODO: add all? (handle with and without block)
|
15
|
+
# TODO: add none? (handle with and without block) (add to LazyArray for < 1.9)
|
16
|
+
# TODO: add one? (handle with and without block) (add to LazyArray for < 1.9)
|
17
|
+
|
18
|
+
# TODO: document this
|
19
|
+
# @api public
|
20
|
+
def select(&block)
|
21
|
+
query = self.query.filter(&block)
|
22
|
+
|
23
|
+
if loaded?
|
24
|
+
new_collection(query, super)
|
25
|
+
else
|
26
|
+
collection = all(query)
|
27
|
+
|
28
|
+
if head.any?
|
29
|
+
collection.unshift(*head.select(&block))
|
30
|
+
end
|
31
|
+
|
32
|
+
if tail.any?
|
33
|
+
collection.concat(tail.select(&block))
|
34
|
+
end
|
35
|
+
|
36
|
+
collection
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: document this
|
41
|
+
# @api public
|
42
|
+
def detect(&block)
|
43
|
+
if loaded?
|
44
|
+
super
|
45
|
+
else
|
46
|
+
head.detect(&block) || first(query.filter(&block))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# TODO: document this
|
51
|
+
# @api public
|
52
|
+
def reject(&block)
|
53
|
+
query = self.query.filter(true, &block)
|
54
|
+
|
55
|
+
if loaded?
|
56
|
+
new_collection(query, super)
|
57
|
+
else
|
58
|
+
collection = all(query)
|
59
|
+
|
60
|
+
if head.any?
|
61
|
+
collection.unshift(*head.reject(&block))
|
62
|
+
end
|
63
|
+
|
64
|
+
if tail.any?
|
65
|
+
collection.concat(tail.reject(&block))
|
66
|
+
end
|
67
|
+
|
68
|
+
collection
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end # module Collection
|
72
|
+
end # module Ambition
|
73
|
+
end # module DataMapper
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Ambition
|
3
|
+
module Model
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:alias_method, :find_all, :select)
|
6
|
+
base.send(:alias_method, :find, :detect)
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO: document this
|
10
|
+
# @api public
|
11
|
+
def select(&block)
|
12
|
+
all.select(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# TODO: document this
|
16
|
+
# @api public
|
17
|
+
def detect(&block)
|
18
|
+
all.detect(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# TODO: document this
|
22
|
+
# @api public
|
23
|
+
def reject(&block)
|
24
|
+
all.reject(&block)
|
25
|
+
end
|
26
|
+
end # module Model
|
27
|
+
end # module Ambition
|
28
|
+
end # module DataMapper
|
@@ -0,0 +1,361 @@
|
|
1
|
+
require 'parse_tree'
|
2
|
+
require 'parse_tree_extensions'
|
3
|
+
require 'ruby2ruby'
|
4
|
+
|
5
|
+
module DataMapper
|
6
|
+
module Ambition
|
7
|
+
module Query
|
8
|
+
class FilterProcessor < SexpProcessor
|
9
|
+
attr_reader :conditions
|
10
|
+
|
11
|
+
def initialize(binding, model, negated = false)
|
12
|
+
super()
|
13
|
+
|
14
|
+
self.expected = Object # allow anything for now
|
15
|
+
self.auto_shift_type = true
|
16
|
+
self.default_method = :process_error
|
17
|
+
|
18
|
+
@binding = binding
|
19
|
+
@model = model
|
20
|
+
@receiver = nil
|
21
|
+
|
22
|
+
@container = DataMapper::Query::Conditions::Operation.new(:and)
|
23
|
+
|
24
|
+
if negated
|
25
|
+
@conditions = DataMapper::Query::Conditions::Operation.new(:not, @container)
|
26
|
+
else
|
27
|
+
@conditions = @container
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_error(exp)
|
32
|
+
raise "DEBUG: calling process_#{exp.shift} with #{exp.inspect} (in process_error)"
|
33
|
+
end
|
34
|
+
|
35
|
+
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
|
+
exp.shift
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_call(exp)
|
61
|
+
if exp.size == 3
|
62
|
+
lhs = process(exp.shift)
|
63
|
+
operator = exp.shift
|
64
|
+
rhs = process(exp.shift)
|
65
|
+
|
66
|
+
if operator == :send
|
67
|
+
operator = rhs.shift
|
68
|
+
end
|
69
|
+
|
70
|
+
if rhs.size > 1
|
71
|
+
raise "DEBUG: rhs.size should not be larger than 1, but was #{rhs.size}: #{rhs.inspect}"
|
72
|
+
else
|
73
|
+
rhs = rhs.first
|
74
|
+
end
|
75
|
+
|
76
|
+
if lhs.nil?
|
77
|
+
nil
|
78
|
+
else
|
79
|
+
evaluate_operator(operator, lhs, rhs)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
raise "DEBUG: unhandled call: #{exp.inspect}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def process_and(exp)
|
87
|
+
parent = @container
|
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
|
102
|
+
end
|
103
|
+
|
104
|
+
def process_or(exp)
|
105
|
+
parent = @container
|
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
|
120
|
+
end
|
121
|
+
|
122
|
+
def process_not(exp)
|
123
|
+
parent = @container
|
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
|
133
|
+
end
|
134
|
+
|
135
|
+
def process_lvar(exp)
|
136
|
+
var = exp.shift
|
137
|
+
var == @receiver ? @model : value(var)
|
138
|
+
end
|
139
|
+
|
140
|
+
def process_arglist(exp)
|
141
|
+
arglist = []
|
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)
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_const(exp)
|
155
|
+
Object.const_get(exp.shift)
|
156
|
+
end
|
157
|
+
|
158
|
+
def process_match3(exp)
|
159
|
+
rhs = process(exp.shift)
|
160
|
+
lhs = process(exp.shift)
|
161
|
+
|
162
|
+
evaluate_operator(:=~, lhs, rhs)
|
163
|
+
end
|
164
|
+
|
165
|
+
def process_array(exp)
|
166
|
+
array = []
|
167
|
+
while sexp = exp.shift
|
168
|
+
array << process(sexp)
|
169
|
+
end
|
170
|
+
array
|
171
|
+
end
|
172
|
+
|
173
|
+
def process_hash(exp)
|
174
|
+
hash = {}
|
175
|
+
until exp.empty?
|
176
|
+
key = process(exp.shift)
|
177
|
+
value = process(exp.shift)
|
178
|
+
|
179
|
+
hash[key] = value
|
180
|
+
end
|
181
|
+
hash
|
182
|
+
end
|
183
|
+
|
184
|
+
def process_str(exp)
|
185
|
+
exp.shift
|
186
|
+
end
|
187
|
+
|
188
|
+
def process_lit(exp)
|
189
|
+
literal = exp.shift
|
190
|
+
exp.shift # FIXME: workaround for bug in ParseTree or SexpProcessor
|
191
|
+
literal
|
192
|
+
end
|
193
|
+
|
194
|
+
def process_true(exp)
|
195
|
+
true
|
196
|
+
end
|
197
|
+
|
198
|
+
def process_false(exp)
|
199
|
+
false
|
200
|
+
end
|
201
|
+
|
202
|
+
def process_nil(exp)
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
|
206
|
+
def process_ivar(exp)
|
207
|
+
value(exp.shift)
|
208
|
+
end
|
209
|
+
|
210
|
+
def process_gvar(exp)
|
211
|
+
value(exp.shift)
|
212
|
+
end
|
213
|
+
|
214
|
+
def evaluate_operator(operator, lhs, rhs)
|
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
|
218
|
+
|
219
|
+
if lhs == @model
|
220
|
+
if rhs.nil?
|
221
|
+
@model.properties[operator]
|
222
|
+
elsif rhs.kind_of?(DataMapper::Resource) && operator == :==
|
223
|
+
resource = rhs
|
224
|
+
|
225
|
+
if resource.repository == DataMapper.repository &&
|
226
|
+
resource.saved? &&
|
227
|
+
!resource.dirty? &&
|
228
|
+
(key = resource.key).all?
|
229
|
+
|
230
|
+
@model.key.zip(key) do |property, bind_value|
|
231
|
+
@container << DataMapper::Query::Conditions::Comparison.new(:eql, property, bind_value)
|
232
|
+
end
|
233
|
+
|
234
|
+
@container
|
235
|
+
end
|
236
|
+
else
|
237
|
+
raise "DEBUG: cannot call #{@model.name}.#{operator} with #{rhs.inspect}"
|
238
|
+
end
|
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)
|
332
|
+
|
333
|
+
else
|
334
|
+
raise "DEBUG: not handled: #{lhs.inspect} #{operator} #{rhs.inspect}"
|
335
|
+
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# TODO: update dm-core internals to use the Ruby operators
|
340
|
+
# insted of the DM specific ones
|
341
|
+
def remap_operator(operator)
|
342
|
+
# remap Ruby to DM operators
|
343
|
+
case operator
|
344
|
+
when :in then :in
|
345
|
+
when :== then :eql
|
346
|
+
when :=~ then :regexp
|
347
|
+
when :> then :gt
|
348
|
+
when :>= then :gte
|
349
|
+
when :< then :lt
|
350
|
+
when :<= then :lte
|
351
|
+
else raise "DEBUG: unknown operator #{operator}"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def value(value)
|
356
|
+
eval(value.to_s, @binding)
|
357
|
+
end
|
358
|
+
end # class FilterProcessor
|
359
|
+
end # module Query
|
360
|
+
end # module Ambition
|
361
|
+
end # module DataMapper
|