dm-mongo-adapter 0.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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