dm-mongo-adapter 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +130 -0
- data/Rakefile +36 -0
- data/TODO +33 -0
- data/VERSION.yml +5 -0
- data/bin/console +31 -0
- data/dm-mongo-adapter.gemspec +154 -0
- data/lib/mongo_adapter.rb +71 -0
- data/lib/mongo_adapter/adapter.rb +255 -0
- data/lib/mongo_adapter/aggregates.rb +21 -0
- data/lib/mongo_adapter/conditions.rb +100 -0
- data/lib/mongo_adapter/embedded_model.rb +187 -0
- data/lib/mongo_adapter/embedded_resource.rb +134 -0
- data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
- data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
- data/lib/mongo_adapter/embedments/relationship.rb +258 -0
- data/lib/mongo_adapter/migrations.rb +45 -0
- data/lib/mongo_adapter/model.rb +89 -0
- data/lib/mongo_adapter/model/embedment.rb +215 -0
- data/lib/mongo_adapter/modifier.rb +85 -0
- data/lib/mongo_adapter/query.rb +252 -0
- data/lib/mongo_adapter/query/java_script.rb +145 -0
- data/lib/mongo_adapter/resource.rb +147 -0
- data/lib/mongo_adapter/types/date.rb +28 -0
- data/lib/mongo_adapter/types/date_time.rb +28 -0
- data/lib/mongo_adapter/types/db_ref.rb +65 -0
- data/lib/mongo_adapter/types/discriminator.rb +32 -0
- data/lib/mongo_adapter/types/object_id.rb +72 -0
- data/lib/mongo_adapter/types/objects.rb +31 -0
- data/script/performance.rb +260 -0
- data/spec/legacy/README +6 -0
- data/spec/legacy/adapter_shared_spec.rb +299 -0
- data/spec/legacy/adapter_spec.rb +174 -0
- data/spec/legacy/associations_spec.rb +140 -0
- data/spec/legacy/embedded_resource_spec.rb +157 -0
- data/spec/legacy/embedments_spec.rb +177 -0
- data/spec/legacy/modifier_spec.rb +81 -0
- data/spec/legacy/property_spec.rb +51 -0
- data/spec/legacy/spec_helper.rb +3 -0
- data/spec/legacy/sti_spec.rb +53 -0
- data/spec/lib/cleanup_models.rb +32 -0
- data/spec/lib/raw_connections.rb +11 -0
- data/spec/public/embedded_collection_spec.rb +61 -0
- data/spec/public/embedded_resource_spec.rb +220 -0
- data/spec/public/model/embedment_spec.rb +186 -0
- data/spec/public/model_spec.rb +37 -0
- data/spec/public/resource_spec.rb +564 -0
- data/spec/public/shared/model_embedments_spec.rb +338 -0
- data/spec/public/shared/object_id_shared_spec.rb +56 -0
- data/spec/public/types/df_ref_spec.rb +6 -0
- data/spec/public/types/discriminator_spec.rb +118 -0
- data/spec/public/types/embedded_array_spec.rb +55 -0
- data/spec/public/types/embedded_hash_spec.rb +83 -0
- data/spec/public/types/object_id_spec.rb +6 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/embedded_model_spec.rb +43 -0
- data/spec/semipublic/model/embedment_spec.rb +42 -0
- data/spec/semipublic/resource_spec.rb +70 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +45 -0
- data/tasks/spec.rake +37 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +21 -0
- metadata +215 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Migrations
|
3
|
+
module SingletonMethods
|
4
|
+
private
|
5
|
+
|
6
|
+
def repository_execute(method, repository_name)
|
7
|
+
DataMapper::Model.descendants.each do |model|
|
8
|
+
model.send(method, repository_name || model.default_repository_name) unless model == DataMapper::Mongo::EmbeddedResource
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Mongo
|
15
|
+
module Migrations
|
16
|
+
def self.included(base)
|
17
|
+
DataMapper.extend(DataMapper::Migrations::SingletonMethods)
|
18
|
+
|
19
|
+
[ :Repository, :Model ].each do |name|
|
20
|
+
DataMapper.const_get(name).send(:include, DataMapper::Migrations.const_get(name))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def storage_exists?(storage_name)
|
25
|
+
database.collections.map(&:name).include?(storage_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_model_storage(model)
|
29
|
+
return false if storage_exists?(model.storage_name)
|
30
|
+
database.create_collection(model.storage_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def upgrade_model_storage(model)
|
34
|
+
create_model_storage(model)
|
35
|
+
end
|
36
|
+
|
37
|
+
def destroy_model_storage(model)
|
38
|
+
database.drop_collection(model.storage_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
DataMapper::Mongo.send(:include, DataMapper::Mongo::Migrations)
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Model
|
4
|
+
def self.extended(model)
|
5
|
+
model.extend Embedment
|
6
|
+
end
|
7
|
+
|
8
|
+
# Defines a Property on the Resource
|
9
|
+
#
|
10
|
+
# Overrides the property method in dm-core so as to automatically map
|
11
|
+
# Array and Hash types to EmbeddedArray and EmbeddedHash respectively.
|
12
|
+
#
|
13
|
+
# @param [Symbol] name
|
14
|
+
# the name for which to call this property
|
15
|
+
# @param [Type] type
|
16
|
+
# the type to define this property ass
|
17
|
+
# @param [Hash(Symbol => String)] options
|
18
|
+
# a hash of available options
|
19
|
+
#
|
20
|
+
# @return [Property]
|
21
|
+
# the created Property
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def property(name, type, options = {})
|
25
|
+
if Array == type
|
26
|
+
type = DataMapper::Mongo::Types::EmbeddedArray
|
27
|
+
elsif Hash == type
|
28
|
+
type = DataMapper::Mongo::Types::EmbeddedHash
|
29
|
+
elsif DateTime == type
|
30
|
+
type = DataMapper::Mongo::Types::DateTime
|
31
|
+
elsif Date == type
|
32
|
+
type = DataMapper::Mongo::Types::Date
|
33
|
+
end
|
34
|
+
|
35
|
+
super(name, type, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Loads an instance of this Model, taking into account IdentityMap
|
39
|
+
# lookup, inheritance columns(s) and Property typecasting. Also loads
|
40
|
+
# the embedments on the Resource.
|
41
|
+
#
|
42
|
+
# @param [Enumerable<Object>] records
|
43
|
+
# An Array of Resource or Hashes to load a Resource with
|
44
|
+
# @param [DataMapper::Query] query
|
45
|
+
# The query used to load the Resource
|
46
|
+
#
|
47
|
+
# @return [Resource]
|
48
|
+
# The loaded Resource instance
|
49
|
+
#
|
50
|
+
# @overrides DataMapper::Model#load
|
51
|
+
#
|
52
|
+
# @api semipublic
|
53
|
+
def load(records, query)
|
54
|
+
if discriminator = properties(query.repository.name).discriminator
|
55
|
+
records.each do |record|
|
56
|
+
discriminator_key = discriminator.name.to_s
|
57
|
+
discriminator_value = discriminator.type.load(record[discriminator_key], discriminator)
|
58
|
+
|
59
|
+
record[discriminator_key] = discriminator_value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
resources = super
|
64
|
+
|
65
|
+
# Load embedded resources
|
66
|
+
resources.each_with_index do |resource, index|
|
67
|
+
resource.model.embedments.each do |name, relationship|
|
68
|
+
unless (targets = records[index][name.to_s]).blank?
|
69
|
+
relationship.set(resource, targets, true)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
resources
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# @api private
|
80
|
+
def const_missing(name)
|
81
|
+
if DataMapper::Mongo::Types.const_defined?(name)
|
82
|
+
DataMapper::Mongo::Types.const_get(name)
|
83
|
+
else
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end # Model
|
88
|
+
end # Mongo
|
89
|
+
end # DataMapper
|
@@ -0,0 +1,215 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Model
|
4
|
+
# Embedment extends Mongo-based resources to allow resources to be
|
5
|
+
# embedded within a document, while providing relationship-like `has 1`
|
6
|
+
# and `has n` functionality.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class User
|
10
|
+
# include DataMapper::Mongo::Resource
|
11
|
+
#
|
12
|
+
# property :id, ObjectID
|
13
|
+
# property :name, String
|
14
|
+
#
|
15
|
+
# embeds n, :addresses, :model => Address
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# class Address
|
19
|
+
# include DataMapper::Mongo::EmbeddedResource
|
20
|
+
#
|
21
|
+
# property :street, String
|
22
|
+
# property :post_code, String
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
module Embedment
|
26
|
+
extend Chainable
|
27
|
+
|
28
|
+
# Options which cannot be used on Embedments.
|
29
|
+
ILLEGAL_OPTS = [:through, :remote_name, :via, :inverse, :parent_key]
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
def self.extended(model)
|
33
|
+
model.instance_variable_set(:@embedments, {})
|
34
|
+
end
|
35
|
+
|
36
|
+
chainable do
|
37
|
+
# @api private
|
38
|
+
def inherited(model)
|
39
|
+
model.instance_variable_set(:@embedments, {})
|
40
|
+
|
41
|
+
@embedments.each { |name, embedment| model.embedments[name] ||= embedment }
|
42
|
+
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
chainable do
|
48
|
+
# @api public
|
49
|
+
def method_missing(method, *args, &block)
|
50
|
+
if respond_to?(:embedments) && embedment = embedments[method]
|
51
|
+
return embedment
|
52
|
+
end
|
53
|
+
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the embedments on this model
|
59
|
+
#
|
60
|
+
# @return [Hash]
|
61
|
+
# Embedments on this model, where each hash key is the embedment
|
62
|
+
# name, and each value is the Embedments::Relationship instance.
|
63
|
+
#
|
64
|
+
# @api semipublic
|
65
|
+
def embedments
|
66
|
+
@embedments ||= {}
|
67
|
+
end
|
68
|
+
|
69
|
+
# A short-hand, clear syntax for defining one-to-one and one-to-many
|
70
|
+
# embedments -- where an embedded resource is held within the parent
|
71
|
+
# document in the database.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# embed 1, :friend # one friend
|
75
|
+
# embed n, :friends # many friends
|
76
|
+
# embed 1..3, :friends # many friends (at least 1, at most 3)
|
77
|
+
# embed 3, :friends # many friends (exactly 3)
|
78
|
+
# embed 1, :friend, 'User' # one friend with the class User
|
79
|
+
#
|
80
|
+
# @param cardinality [Integer, Range, Infinity]
|
81
|
+
# Cardinality that defines the embedment type and constraints
|
82
|
+
# @param name [Symbol]
|
83
|
+
# The name that the embedment will be referenced by
|
84
|
+
# @param model [Model, #to_str]
|
85
|
+
# The target model of the embedment
|
86
|
+
# @param opts [Hash]
|
87
|
+
# An options hash
|
88
|
+
#
|
89
|
+
# @option :model[Model, String] The name of the class to associate
|
90
|
+
# with, if omitted then the model class name is assumed to match the
|
91
|
+
# (optional) third parameter, or the embedment name.
|
92
|
+
#
|
93
|
+
# @return [Embedment::Relationship]
|
94
|
+
# The embedment that was created to reflect either a one-to-one or
|
95
|
+
# one-to-many embedment.
|
96
|
+
#
|
97
|
+
# @raise [ArgumentError]
|
98
|
+
# If the cardinality was not understood. Should be a Integer, Range
|
99
|
+
# or Infinity(n)
|
100
|
+
#
|
101
|
+
# @api public
|
102
|
+
def embeds(cardinality, name, *args)
|
103
|
+
assert_kind_of 'cardinality', cardinality, Integer, Range, Infinity.class
|
104
|
+
assert_kind_of 'name', name, Symbol
|
105
|
+
|
106
|
+
model = extract_model(args)
|
107
|
+
options = extract_options(args)
|
108
|
+
|
109
|
+
min, max = extract_min_max(cardinality)
|
110
|
+
options.update(:min => min, :max => max)
|
111
|
+
|
112
|
+
assert_valid_options(options)
|
113
|
+
|
114
|
+
if options.key?(:model) && model
|
115
|
+
raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
|
116
|
+
end
|
117
|
+
|
118
|
+
model ||= options.delete(:model)
|
119
|
+
|
120
|
+
klass = if max > 1
|
121
|
+
Embedments::OneToMany::Relationship
|
122
|
+
else
|
123
|
+
Embedments::OneToOne::Relationship
|
124
|
+
end
|
125
|
+
|
126
|
+
embedment = embedments[name] = klass.new(name, model, self, options)
|
127
|
+
|
128
|
+
descendants.each do |descendant|
|
129
|
+
descendant.embedments[name] ||= embedment
|
130
|
+
end
|
131
|
+
|
132
|
+
create_embedment_reader(embedment)
|
133
|
+
create_embedment_writer(embedment)
|
134
|
+
|
135
|
+
embedment
|
136
|
+
end
|
137
|
+
|
138
|
+
# @todo Investigate as a candidate for removal.
|
139
|
+
# Added 26ae98e1 as an equivelent of belongs_to but _probably_ isn't
|
140
|
+
# of much use in embedded resources (since it would be perfectly
|
141
|
+
# acceptable for an embedment to be used in multiple models). My
|
142
|
+
# opinion is that embedments should always be declared from the
|
143
|
+
# parent side (DM::M::Resource), rather the child side
|
144
|
+
# (DM::M::EmbeddedResource).
|
145
|
+
#
|
146
|
+
# ~antw
|
147
|
+
#
|
148
|
+
# @api public
|
149
|
+
def embedded_in(name, *args)
|
150
|
+
return NotImplementedError
|
151
|
+
end
|
152
|
+
|
153
|
+
def assert_valid_options(options)
|
154
|
+
ILLEGAL_OPTS.each do |option|
|
155
|
+
raise ArgumentError, "+options[:#{option}]+ should not be " \
|
156
|
+
"specified on a relationship" if options.key?(option)
|
157
|
+
end
|
158
|
+
|
159
|
+
super
|
160
|
+
end
|
161
|
+
|
162
|
+
# Dynamically defines a reader method
|
163
|
+
#
|
164
|
+
# Creates a public method matching the name of the embedment which can
|
165
|
+
# be used to access the embedded resource(s).
|
166
|
+
#
|
167
|
+
# @param [Embedment::Relationship] embedment
|
168
|
+
# The embedment for which a reader should be created
|
169
|
+
#
|
170
|
+
# @api private
|
171
|
+
def create_embedment_reader(embedment)
|
172
|
+
name = embedment.name
|
173
|
+
reader_name = name.to_s
|
174
|
+
|
175
|
+
return if resource_method_defined?(reader_name)
|
176
|
+
|
177
|
+
reader_visibility = embedment.reader_visibility
|
178
|
+
|
179
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
180
|
+
#{reader_visibility} # public
|
181
|
+
def #{reader_name}(query = nil) # def author(query = nil)
|
182
|
+
embedments[#{name.inspect}].get(self, query) # embedment[:author].get(self, query)
|
183
|
+
end # end
|
184
|
+
RUBY
|
185
|
+
end
|
186
|
+
|
187
|
+
# Dynamically defines a writer method
|
188
|
+
#
|
189
|
+
# Creates a public method matching the name of the embedment which can
|
190
|
+
# be used to set the embedded resource(s).
|
191
|
+
#
|
192
|
+
# @param [Embedment::Relationship] embedment
|
193
|
+
# The embedment for which a writer should be created
|
194
|
+
#
|
195
|
+
# @api private
|
196
|
+
def create_embedment_writer(embedment)
|
197
|
+
name = embedment.name
|
198
|
+
writer_name = "#{name}="
|
199
|
+
|
200
|
+
return if resource_method_defined?(writer_name)
|
201
|
+
|
202
|
+
writer_visibility = embedment.writer_visibility
|
203
|
+
|
204
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
205
|
+
#{writer_visibility} # public
|
206
|
+
def #{writer_name}(target) # def author=(target)
|
207
|
+
embedments[#{name.inspect}].set(self, target) # embedment[:author].set(self, target)
|
208
|
+
end # end
|
209
|
+
RUBY
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end # Model
|
214
|
+
end # Mongo
|
215
|
+
end # DataMapper
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Modifier
|
4
|
+
# TODO: document
|
5
|
+
# @api public
|
6
|
+
def increment(property, value)
|
7
|
+
attribute_set(property, attribute_get(property) + value)
|
8
|
+
|
9
|
+
if modifier(:inc, property => value)
|
10
|
+
original_attributes.clear
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# TODO: document
|
15
|
+
# @api public
|
16
|
+
def decrement(property, value)
|
17
|
+
attribute_set(property, attribute_get(property) - value)
|
18
|
+
|
19
|
+
if modifier(:inc, property => -value.abs)
|
20
|
+
original_attributes.clear
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# TODO: document
|
25
|
+
# @api public
|
26
|
+
def set(args)
|
27
|
+
modifier(:set, args)
|
28
|
+
|
29
|
+
args.keys.each do |key|
|
30
|
+
attribute_set(key, args[key])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO: document
|
35
|
+
# @api public
|
36
|
+
def unset(*args)
|
37
|
+
new_args = {}
|
38
|
+
|
39
|
+
args.each do |arg|
|
40
|
+
new_args[arg] = 1
|
41
|
+
end
|
42
|
+
|
43
|
+
modifier(:unset, new_args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# TODO: document
|
47
|
+
# @api public
|
48
|
+
def push
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO: document
|
53
|
+
# @api public
|
54
|
+
def push_all
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# TODO: document
|
59
|
+
# @api public
|
60
|
+
def pop
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: document
|
65
|
+
# @api public
|
66
|
+
def pull
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# TODO: document
|
71
|
+
# @api public
|
72
|
+
def pull_all
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# TODO: document
|
79
|
+
# @api private
|
80
|
+
def modifier(operation, properties)
|
81
|
+
repository.adapter.execute([self], "$#{operation}" => properties)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
# This class is responsible for taking Query instances from DataMapper and
|
4
|
+
# formatting the query such that it can be performed by the Mongo library.
|
5
|
+
class Query
|
6
|
+
include Extlib::Assertions
|
7
|
+
include DataMapper::Query::Conditions
|
8
|
+
|
9
|
+
# Creates a new Query instance
|
10
|
+
#
|
11
|
+
# @param [Mongo::Collection] collection
|
12
|
+
# @param [DataMapper::Query] query
|
13
|
+
#
|
14
|
+
# @api semipublic
|
15
|
+
def initialize(collection, query)
|
16
|
+
assert_kind_of 'collection', collection, ::Mongo::Collection
|
17
|
+
assert_kind_of 'query', query, DataMapper::Query
|
18
|
+
|
19
|
+
@collection = collection
|
20
|
+
@query = query
|
21
|
+
@statements = {}
|
22
|
+
@conditions = Conditions.new(query.conditions)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Applies the query to the collection
|
26
|
+
#
|
27
|
+
# Reads the query options, fetches the resources matching the query
|
28
|
+
# statements and filters according to the conditions.
|
29
|
+
#
|
30
|
+
# @return [Mongo::Collection]
|
31
|
+
#
|
32
|
+
# @api semipublic
|
33
|
+
def read
|
34
|
+
setup_conditions_and_options
|
35
|
+
find
|
36
|
+
end
|
37
|
+
|
38
|
+
# TODO: document
|
39
|
+
# @api semipublic
|
40
|
+
def count
|
41
|
+
setup_conditions_and_options
|
42
|
+
|
43
|
+
# TODO: atm ruby driver doesn't support count with statements,
|
44
|
+
# that's why we use find and size here as a tmp workaround
|
45
|
+
if @statements.blank?
|
46
|
+
[@collection.count]
|
47
|
+
else
|
48
|
+
[find.size]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO: document
|
53
|
+
# @api semipublic
|
54
|
+
def group
|
55
|
+
setup_conditions_and_options
|
56
|
+
|
57
|
+
property_names = []
|
58
|
+
operators = []
|
59
|
+
keys = []
|
60
|
+
|
61
|
+
@query.fields.each do |field|
|
62
|
+
if field.kind_of?(DataMapper::Query::Operator)
|
63
|
+
operators << field
|
64
|
+
property_names << field.target.name
|
65
|
+
else
|
66
|
+
keys << field.name
|
67
|
+
property_names << field.name
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if operators.blank?
|
72
|
+
initial = {}
|
73
|
+
reduce = JavaScript::Reduce.new.to_s
|
74
|
+
finalize = nil
|
75
|
+
else
|
76
|
+
js_operation = JavaScript::Operation.new(operators)
|
77
|
+
|
78
|
+
initial = js_operation.initial
|
79
|
+
reduce = js_operation.reduce
|
80
|
+
finalize = js_operation.finalize
|
81
|
+
|
82
|
+
keys = keys - initial.keys
|
83
|
+
end
|
84
|
+
|
85
|
+
@collection.group(keys, @statements, initial, reduce, true, finalize).map do |records|
|
86
|
+
records.to_mash.symbolize_keys.only(*property_names)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# TODO: document
|
93
|
+
# @api private
|
94
|
+
def setup_conditions_and_options
|
95
|
+
@options = {}
|
96
|
+
|
97
|
+
@options[:limit] = @query.limit if @query.limit
|
98
|
+
@options[:skip] = @query.offset if @query.offset
|
99
|
+
@options[:sort] = sort_statement(@query.order) unless @query.order.empty?
|
100
|
+
|
101
|
+
conditions_statement(@query.conditions)
|
102
|
+
end
|
103
|
+
|
104
|
+
# TODO: document
|
105
|
+
# @api private
|
106
|
+
def find
|
107
|
+
@conditions.filter_collection!(@collection.find(@statements, @options).to_a)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Takes a condition and returns a Mongo-compatible hash
|
111
|
+
#
|
112
|
+
# @param [DataMapper::Query::Conditions::AbstractOperation, DataMapper::Query::Conditions::AbstractComparison] operation
|
113
|
+
# An operation to be made suitable for use with Mongo
|
114
|
+
# @param [Boolean] affirmative
|
115
|
+
# Are we looking for resources which match the condition (true), or
|
116
|
+
# those which do not match (false)
|
117
|
+
#
|
118
|
+
# @return [Hash]
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
def conditions_statement(conditions, affirmative = true)
|
122
|
+
case conditions
|
123
|
+
when AbstractOperation then operation_statement(conditions, affirmative)
|
124
|
+
when AbstractComparison then comparison_statement(conditions, affirmative)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Takes a Operation condition and returns a Mongo-compatible hash
|
129
|
+
#
|
130
|
+
# @param [DataMapper::Query::Conditions::Operation] operation
|
131
|
+
# An operation to be made suitable for use with Mongo
|
132
|
+
# @param [Boolean] affirmative
|
133
|
+
# Are we looking for resources which match the condition (true), or
|
134
|
+
# those which do not match (false)
|
135
|
+
#
|
136
|
+
# @return [Hash]
|
137
|
+
#
|
138
|
+
# @api private
|
139
|
+
def operation_statement(operation, affirmative = true)
|
140
|
+
case operation
|
141
|
+
when NotOperation then conditions_statement(operation.first, !affirmative)
|
142
|
+
when AndOperation then operation.each{|op| conditions_statement(op, affirmative)}
|
143
|
+
when OrOperation then operation.each{|op| conditions_statement(op, affirmative)}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# TODO: document
|
148
|
+
# @api private
|
149
|
+
def comparison_statement_for_embedment(comparison, affirmative = true)
|
150
|
+
embedment = @query.model.embedments.values.detect { |e| e.target_model == comparison.subject.model }
|
151
|
+
|
152
|
+
field = "#{embedment.name}.#{comparison.subject.field}"
|
153
|
+
|
154
|
+
update_statements(comparison, field, affirmative)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Takes a Comparison condition and returns a Mongo-compatible hash
|
158
|
+
#
|
159
|
+
# @param [DataMapper::Query::Conditions::Comparison] comparison
|
160
|
+
# An comparison to be made suitable for use with Mongo
|
161
|
+
# @param [Boolean] affirmative
|
162
|
+
# Are we looking for resources which match the condition (true), or
|
163
|
+
# those which do not match (false)
|
164
|
+
#
|
165
|
+
# @return [Hash]
|
166
|
+
#
|
167
|
+
# @api private
|
168
|
+
def comparison_statement(comparison, affirmative = true)
|
169
|
+
if comparison.relationship?
|
170
|
+
return conditions_statement(comparison.foreign_key_mapping, affirmative)
|
171
|
+
end
|
172
|
+
|
173
|
+
if comparison.subject.model && comparison.subject.model < EmbeddedResource
|
174
|
+
return comparison_statement_for_embedment(comparison, affirmative)
|
175
|
+
end
|
176
|
+
|
177
|
+
update_statements(comparison, comparison.subject.field, affirmative)
|
178
|
+
end
|
179
|
+
|
180
|
+
# TODO: document
|
181
|
+
# @api private
|
182
|
+
def update_statements(comparison, field, affirmative = true)
|
183
|
+
value = comparison.value
|
184
|
+
|
185
|
+
operator = if affirmative
|
186
|
+
case comparison
|
187
|
+
when EqualToComparison then value
|
188
|
+
when GreaterThanComparison then {'$gt' => value}
|
189
|
+
when LessThanComparison then {'$lt' => value}
|
190
|
+
when GreaterThanOrEqualToComparison then {'$gte' => value}
|
191
|
+
when LessThanOrEqualToComparison then {'$lte' => value}
|
192
|
+
when InclusionComparison then inclusion_comparison_operator(comparison, value)
|
193
|
+
when RegexpComparison then value
|
194
|
+
when LikeComparison then comparison.send(:expected)
|
195
|
+
else
|
196
|
+
raise NotImplementedError
|
197
|
+
end
|
198
|
+
else
|
199
|
+
case comparison
|
200
|
+
when EqualToComparison then {'$ne' => value}
|
201
|
+
when InclusionComparison then {'$nin' => value}
|
202
|
+
else
|
203
|
+
raise NotImplementedError
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
operator.is_a?(Hash) ?
|
208
|
+
(@statements[field.to_sym] ||= {}).merge!(operator) : @statements[field.to_sym] = operator
|
209
|
+
end
|
210
|
+
|
211
|
+
# Creates Mongo's equivalent of an IN() condition
|
212
|
+
#
|
213
|
+
# @param [DataMapper::Query::Conditions::Comparison] comparison
|
214
|
+
# An comparison to be made suitable for use with Mongo
|
215
|
+
# @param [Object] value
|
216
|
+
# The value to match against.
|
217
|
+
#
|
218
|
+
# @return [Hash]
|
219
|
+
#
|
220
|
+
# @api private
|
221
|
+
def inclusion_comparison_operator(comparison, value)
|
222
|
+
if value.kind_of?(Range)
|
223
|
+
{'$gte' => value.first, value.exclude_end? ? '$lt' : '$lte' => value.last}
|
224
|
+
elsif comparison.kind_of?(InclusionComparison) && value.size == 1
|
225
|
+
value.first
|
226
|
+
elsif comparison.subject.type == DataMapper::Mongo::Types::EmbeddedArray
|
227
|
+
value
|
228
|
+
else
|
229
|
+
{'$in' => value}
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Constructs a sort statement which can be used by the Mongo library
|
234
|
+
#
|
235
|
+
# Mongo::Collection#find requires that sort statements consist of an
|
236
|
+
# array where each element is another array containing the field name
|
237
|
+
# and the sort order.
|
238
|
+
#
|
239
|
+
# @param [Enumerable<DataMapper::Query::Direction>]
|
240
|
+
#
|
241
|
+
# @return [Array<Array>]
|
242
|
+
#
|
243
|
+
# @api private
|
244
|
+
def sort_statement(conditions)
|
245
|
+
conditions.inject([]) do |sort_arr, condition|
|
246
|
+
sort_arr << [condition.target.field, condition.operator == :asc ? 'ascending' : 'descending']
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
end # Query
|
251
|
+
end # Mongo
|
252
|
+
end # DataMapper
|