dm-mongo-adapter 0.2.0.pre1

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.
Files changed (65) hide show
  1. data/.gitignore +9 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +130 -0
  4. data/Rakefile +36 -0
  5. data/TODO +33 -0
  6. data/VERSION.yml +5 -0
  7. data/bin/console +31 -0
  8. data/dm-mongo-adapter.gemspec +154 -0
  9. data/lib/mongo_adapter.rb +71 -0
  10. data/lib/mongo_adapter/adapter.rb +255 -0
  11. data/lib/mongo_adapter/aggregates.rb +21 -0
  12. data/lib/mongo_adapter/conditions.rb +100 -0
  13. data/lib/mongo_adapter/embedded_model.rb +187 -0
  14. data/lib/mongo_adapter/embedded_resource.rb +134 -0
  15. data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
  16. data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
  17. data/lib/mongo_adapter/embedments/relationship.rb +258 -0
  18. data/lib/mongo_adapter/migrations.rb +45 -0
  19. data/lib/mongo_adapter/model.rb +89 -0
  20. data/lib/mongo_adapter/model/embedment.rb +215 -0
  21. data/lib/mongo_adapter/modifier.rb +85 -0
  22. data/lib/mongo_adapter/query.rb +252 -0
  23. data/lib/mongo_adapter/query/java_script.rb +145 -0
  24. data/lib/mongo_adapter/resource.rb +147 -0
  25. data/lib/mongo_adapter/types/date.rb +28 -0
  26. data/lib/mongo_adapter/types/date_time.rb +28 -0
  27. data/lib/mongo_adapter/types/db_ref.rb +65 -0
  28. data/lib/mongo_adapter/types/discriminator.rb +32 -0
  29. data/lib/mongo_adapter/types/object_id.rb +72 -0
  30. data/lib/mongo_adapter/types/objects.rb +31 -0
  31. data/script/performance.rb +260 -0
  32. data/spec/legacy/README +6 -0
  33. data/spec/legacy/adapter_shared_spec.rb +299 -0
  34. data/spec/legacy/adapter_spec.rb +174 -0
  35. data/spec/legacy/associations_spec.rb +140 -0
  36. data/spec/legacy/embedded_resource_spec.rb +157 -0
  37. data/spec/legacy/embedments_spec.rb +177 -0
  38. data/spec/legacy/modifier_spec.rb +81 -0
  39. data/spec/legacy/property_spec.rb +51 -0
  40. data/spec/legacy/spec_helper.rb +3 -0
  41. data/spec/legacy/sti_spec.rb +53 -0
  42. data/spec/lib/cleanup_models.rb +32 -0
  43. data/spec/lib/raw_connections.rb +11 -0
  44. data/spec/public/embedded_collection_spec.rb +61 -0
  45. data/spec/public/embedded_resource_spec.rb +220 -0
  46. data/spec/public/model/embedment_spec.rb +186 -0
  47. data/spec/public/model_spec.rb +37 -0
  48. data/spec/public/resource_spec.rb +564 -0
  49. data/spec/public/shared/model_embedments_spec.rb +338 -0
  50. data/spec/public/shared/object_id_shared_spec.rb +56 -0
  51. data/spec/public/types/df_ref_spec.rb +6 -0
  52. data/spec/public/types/discriminator_spec.rb +118 -0
  53. data/spec/public/types/embedded_array_spec.rb +55 -0
  54. data/spec/public/types/embedded_hash_spec.rb +83 -0
  55. data/spec/public/types/object_id_spec.rb +6 -0
  56. data/spec/rcov.opts +6 -0
  57. data/spec/semipublic/embedded_model_spec.rb +43 -0
  58. data/spec/semipublic/model/embedment_spec.rb +42 -0
  59. data/spec/semipublic/resource_spec.rb +70 -0
  60. data/spec/spec.opts +4 -0
  61. data/spec/spec_helper.rb +45 -0
  62. data/tasks/spec.rake +37 -0
  63. data/tasks/yard.rake +9 -0
  64. data/tasks/yardstick.rake +21 -0
  65. 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