dm-mongo-adapter 0.2.0.pre3 → 0.6.0

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 (64) hide show
  1. data/Gemfile +27 -0
  2. data/README.rdoc +16 -35
  3. data/Rakefile +6 -16
  4. data/VERSION.yml +2 -2
  5. data/dm-mongo-adapter.gemspec +100 -121
  6. data/lib/mongo_adapter.rb +19 -62
  7. data/lib/mongo_adapter/adapter.rb +49 -36
  8. data/lib/mongo_adapter/conditions.rb +0 -5
  9. data/lib/mongo_adapter/migrations.rb +17 -23
  10. data/lib/mongo_adapter/model.rb +3 -74
  11. data/lib/mongo_adapter/modifier.rb +1 -1
  12. data/lib/mongo_adapter/property/array.rb +10 -0
  13. data/lib/mongo_adapter/property/db_ref.rb +17 -0
  14. data/lib/mongo_adapter/property/hash.rb +27 -0
  15. data/lib/mongo_adapter/property/object_id.rb +47 -0
  16. data/lib/mongo_adapter/query.rb +42 -45
  17. data/lib/mongo_adapter/rails/storage.rb +15 -0
  18. data/lib/mongo_adapter/resource.rb +0 -130
  19. data/lib/mongo_adapter/support/class.rb +11 -0
  20. data/lib/mongo_adapter/support/date.rb +11 -0
  21. data/lib/mongo_adapter/support/date_time.rb +12 -0
  22. data/lib/mongo_adapter/support/object.rb +9 -0
  23. data/spec/legacy/adapter_spec.rb +9 -6
  24. data/spec/legacy/associations_spec.rb +37 -29
  25. data/spec/legacy/property_spec.rb +4 -3
  26. data/spec/legacy/sti_spec.rb +1 -2
  27. data/spec/lib/cleanup_models.rb +0 -1
  28. data/spec/public/aggregates_spec.rb +171 -0
  29. data/spec/public/model_spec.rb +8 -22
  30. data/spec/public/modifier_spec.rb +109 -0
  31. data/spec/public/properties/db_ref_spec.rb +17 -0
  32. data/spec/public/properties/object_id_spec.rb +15 -0
  33. data/spec/public/resource_spec.rb +2 -383
  34. data/spec/public/shared/object_id_shared_spec.rb +16 -39
  35. data/spec/spec_helper.rb +0 -4
  36. metadata +87 -117
  37. data/.gitignore +0 -9
  38. data/lib/mongo_adapter/embedded_model.rb +0 -187
  39. data/lib/mongo_adapter/embedded_resource.rb +0 -134
  40. data/lib/mongo_adapter/embedments/one_to_many.rb +0 -144
  41. data/lib/mongo_adapter/embedments/one_to_one.rb +0 -57
  42. data/lib/mongo_adapter/embedments/relationship.rb +0 -258
  43. data/lib/mongo_adapter/model/embedment.rb +0 -215
  44. data/lib/mongo_adapter/types/date.rb +0 -24
  45. data/lib/mongo_adapter/types/date_time.rb +0 -28
  46. data/lib/mongo_adapter/types/db_ref.rb +0 -65
  47. data/lib/mongo_adapter/types/discriminator.rb +0 -32
  48. data/lib/mongo_adapter/types/object_id.rb +0 -72
  49. data/lib/mongo_adapter/types/objects.rb +0 -31
  50. data/spec/legacy/embedded_resource_spec.rb +0 -157
  51. data/spec/legacy/embedments_spec.rb +0 -177
  52. data/spec/legacy/modifier_spec.rb +0 -81
  53. data/spec/public/embedded_collection_spec.rb +0 -61
  54. data/spec/public/embedded_resource_spec.rb +0 -220
  55. data/spec/public/model/embedment_spec.rb +0 -186
  56. data/spec/public/shared/model_embedments_spec.rb +0 -338
  57. data/spec/public/types/df_ref_spec.rb +0 -6
  58. data/spec/public/types/discriminator_spec.rb +0 -118
  59. data/spec/public/types/embedded_array_spec.rb +0 -55
  60. data/spec/public/types/embedded_hash_spec.rb +0 -83
  61. data/spec/public/types/object_id_spec.rb +0 -6
  62. data/spec/semipublic/embedded_model_spec.rb +0 -43
  63. data/spec/semipublic/model/embedment_spec.rb +0 -42
  64. data/spec/semipublic/resource_spec.rb +0 -70
@@ -1,6 +1,9 @@
1
1
  module DataMapper
2
2
  module Mongo
3
3
  class Adapter < DataMapper::Adapters::AbstractAdapter
4
+ include DataMapper::Mongo::Aggregates
5
+ include Migrations
6
+
4
7
  class ConnectionError < StandardError; end
5
8
 
6
9
  # Persists one or more new resources
@@ -37,7 +40,7 @@ module DataMapper
37
40
  # @api semipublic
38
41
  def read(query)
39
42
  with_collection(query.model) do |collection|
40
- Query.new(collection, query).read
43
+ load_retrieved_fields!(Query.new(collection, query).read, query.model)
41
44
  end
42
45
  end
43
46
 
@@ -117,7 +120,25 @@ module DataMapper
117
120
  #
118
121
  # @api private
119
122
  def key(resource)
120
- resource.model.key(name).map{ |key| [key.field, key.value(resource.__send__(key.name))] }.to_hash
123
+ DataMapper::Ext::Array.to_hash(resource.model.key(name).map do |key|
124
+ [ key.field, key.dump(resource.__send__(key.name)) ]
125
+ end)
126
+ end
127
+
128
+ # TODO: document
129
+ def load_retrieved_fields!(fields, model)
130
+ fields.each do |attributes|
131
+ if discriminator = model.properties.discriminator
132
+ attributes[discriminator.field] = Class.from_mongo(attributes[discriminator.field])
133
+ end
134
+
135
+ attributes.each do |key, value|
136
+ next if discriminator && key == discriminator
137
+ attributes[key] = load_field_value(value)
138
+ end
139
+ end
140
+
141
+ fields
121
142
  end
122
143
 
123
144
  # Retrieves all of a records attributes and returns them as a Hash.
@@ -142,7 +163,9 @@ module DataMapper
142
163
  attributes_from_properties_hash(record)
143
164
  end
144
165
 
145
- attributes.except('_id') unless attributes.nil?
166
+ attributes.delete('_id') unless attributes.nil?
167
+
168
+ attributes
146
169
  end
147
170
 
148
171
  # TODO: document
@@ -152,27 +175,7 @@ module DataMapper
152
175
  model = record.model
153
176
 
154
177
  model.properties.each do |property|
155
- name = property.name
156
- if model.public_method_defined?(name)
157
- value = property.get(record)
158
-
159
- attributes[property.field] = property.custom? ?
160
- property.type.dump(value, property) : value
161
- end
162
- end
163
-
164
- if model.respond_to?(:embedments)
165
- model.embedments.each do |name, embedment|
166
- if model.public_method_defined?(name)
167
- value = record.__send__(name)
168
-
169
- if embedment.kind_of?(Embedments::OneToMany::Relationship)
170
- attributes[embedment.storage_name] = value.map{ |resource| attributes_as_fields(resource) }
171
- else
172
- attributes[name] = attributes_as_fields(value)
173
- end
174
- end
175
- end
178
+ attributes[property.field] = dump_field_value(property.dump(property.get(record)))
176
179
  end
177
180
 
178
181
  attributes
@@ -183,17 +186,24 @@ module DataMapper
183
186
  attributes = {}
184
187
 
185
188
  record.each do |key, value|
186
- case key
187
- when DataMapper::Property
188
- attributes[key.field] = key.custom? ? key.type.dump(value, key) : value
189
- when Embedments::Relationship
190
- attributes[key.storage_name] = attributes_as_fields(value)
191
- end
189
+ attributes[key.field] = dump_field_value(key.dump(value))
192
190
  end
193
191
 
194
192
  attributes
195
193
  end
196
194
 
195
+ # TODO: document
196
+ def dump_field_value(value)
197
+ return nil if value.nil?
198
+ value.class.to_mongo(value)
199
+ end
200
+
201
+ # TODO: document
202
+ def load_field_value(value)
203
+ return nil if value.nil?
204
+ value.class.from_mongo(value)
205
+ end
206
+
197
207
  # Runs the given block within the context of a Mongo collection.
198
208
  #
199
209
  # @param [Model] model
@@ -226,11 +236,14 @@ module DataMapper
226
236
  unless defined?(@database)
227
237
  @database = connection.db(@options[:database])
228
238
 
229
- if @options[:username] and not @database.authenticate(
230
- @options[:username], @options[:password])
231
- raise ConnectionError,
232
- 'MongoDB did not recognize the given username and/or ' \
233
- 'password; see the server logs for more information'
239
+ if @options[:username]
240
+ begin
241
+ @database.authenticate(@options[:username], @options[:password])
242
+ rescue ::Mongo::AuthenticationError
243
+ raise ConnectionError,
244
+ 'MongoDB did not recognize the given username and/or ' \
245
+ 'password; see the server logs for more information'
246
+ end
234
247
  end
235
248
  end
236
249
 
@@ -246,7 +259,7 @@ module DataMapper
246
259
  #
247
260
  # @api semipublic
248
261
  def connection
249
- @connection ||= ::Mongo::Connection.new(*@options.values_at(:host, :port))
262
+ @connection ||= ::Mongo::Connection.new(@options[:host], @options[:port], :slave_ok => true)
250
263
  end
251
264
  end # Adapter
252
265
  end # Mongo
@@ -70,7 +70,6 @@ module DataMapper
70
70
  # Comparisons not current supported are:
71
71
  #
72
72
  # * $nin with range
73
- # * negated regexp comparison (see: http://jira.mongodb.org/browse/SERVER-251)
74
73
  #
75
74
  # @param [DataMapper::Query::Conditions::AbstractOperation, DataMapper::Query::Conditions::AbstractComparison] operand
76
75
  # An operation to be made suitable for use with Mongo
@@ -82,10 +81,6 @@ module DataMapper
82
81
  case operand
83
82
  when OrOperation
84
83
  true
85
- when RegexpComparison
86
- if operand.negated?
87
- true
88
- end
89
84
  when InclusionComparison
90
85
  if operand.negated?
91
86
  true
@@ -1,26 +1,8 @@
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
1
+ require 'dm-migrations/auto_migration'
13
2
 
3
+ module DataMapper
14
4
  module Mongo
15
5
  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
6
  def storage_exists?(storage_name)
25
7
  database.collections.map(&:name).include?(storage_name)
26
8
  end
@@ -38,8 +20,20 @@ module DataMapper
38
20
  database.drop_collection(model.storage_name)
39
21
  end
40
22
 
41
- end
23
+ module Model
24
+ def auto_migrate!(repository_name = self.repository_name)
25
+ adapter = repository(repository_name).adapter
26
+
27
+ return unless adapter.kind_of?(Mongo::Adapter)
28
+
29
+ adapter.destroy_model_storage(self)
30
+ adapter.create_model_storage(self)
31
+ end
32
+
33
+ def auto_upgrade!(repository_name = self.repository_name)
34
+ # noop
35
+ end
36
+ end
37
+ end
42
38
  end
43
39
  end
44
-
45
- DataMapper::Mongo.send(:include, DataMapper::Mongo::Migrations)
@@ -1,85 +1,14 @@
1
1
  module DataMapper
2
2
  module Mongo
3
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
4
+ include Migrations::Model
76
5
 
77
6
  private
78
7
 
79
8
  # @api private
80
9
  def const_missing(name)
81
- if DataMapper::Mongo::Types.const_defined?(name)
82
- DataMapper::Mongo::Types.const_get(name)
10
+ if DataMapper::Mongo::Property.const_defined?(name)
11
+ DataMapper::Mongo::Property.const_get(name)
83
12
  else
84
13
  super
85
14
  end
@@ -35,7 +35,7 @@ module DataMapper
35
35
  # @api public
36
36
  def unset(*args)
37
37
  new_args = {}
38
-
38
+
39
39
  args.each do |arg|
40
40
  new_args[arg] = 1
41
41
  end
@@ -0,0 +1,10 @@
1
+ module DataMapper
2
+ module Mongo
3
+ class Property
4
+ class Array < DataMapper::Property::Object
5
+ include DataMapper::Property::PassThroughLoadDump
6
+ primitive ::Array
7
+ end
8
+ end # Property
9
+ end # Mongo
10
+ end # DataMapper
@@ -0,0 +1,17 @@
1
+ module DataMapper
2
+ module Mongo
3
+ class Property
4
+ # Database references are references from one document (object) to
5
+ # another within a database. A database reference is a standard embedded
6
+ # object: this is a MongoDB convention, not a special type.
7
+ #
8
+ # The DBRef is made available via your model as a String.
9
+ #
10
+ # @see http://www.mongodb.org/display/DOCS/DB+Ref
11
+ #
12
+ # @api public
13
+ class DBRef < DataMapper::Mongo::Property::ObjectId
14
+ end # DBRef
15
+ end # Property
16
+ end # Mongo
17
+ end # DataMapper
@@ -0,0 +1,27 @@
1
+ module DataMapper
2
+ module Mongo
3
+ class Property
4
+ class Hash < DataMapper::Property::Object
5
+ include DataMapper::Property::PassThroughLoadDump
6
+ primitive ::Hash
7
+
8
+ # @api semipublic
9
+ def load(value)
10
+ typecast_to_primitive(value)
11
+ end
12
+
13
+ # @api semipublic
14
+ def typecast_to_primitive(value)
15
+ case value
16
+ when NilClass
17
+ nil
18
+ when ::Hash
19
+ DataMapper::Ext::Hash.to_mash(value).symbolize_keys
20
+ when ::Array
21
+ value.empty? ? {} : {value.first.to_sym => value.last}
22
+ end
23
+ end
24
+ end #Array
25
+ end # Property
26
+ end # Mongo
27
+ end # DataMapper
@@ -0,0 +1,47 @@
1
+ module DataMapper
2
+ module Mongo
3
+ class Property
4
+ # Each object (document) stored in Mongo DB has an _id field as its
5
+ # first attribute. This is an object id. It must be unique for each
6
+ # member of a collection (this is enforced if the collection has an _id
7
+ # index, which is the case by default).
8
+ #
9
+ # The database will assign an _id if an object being inserted into a
10
+ # collection does not have one.
11
+ #
12
+ # The _id may be of any type as long as it is a unique value.
13
+ #
14
+ # @see http://www.mongodb.org/display/DOCS/Object+IDs
15
+ #
16
+ # @api public
17
+ class ObjectId < DataMapper::Property::Object
18
+ include DataMapper::Property::PassThroughLoadDump
19
+
20
+ primitive ::BSON::ObjectId
21
+ key true
22
+ field "_id"
23
+ required false
24
+
25
+ # Returns the ObjectId as a string
26
+ #
27
+ # @return [String]
28
+ #
29
+ # @api semipublic
30
+ def typecast_to_primitive(value)
31
+ case value
32
+ when ::String
33
+ ::BSON::ObjectId.from_string(value)
34
+ else
35
+ raise ArgumentError.new('+value+ must String')
36
+ end
37
+ end
38
+
39
+ # @api semipublic
40
+ def valid?(value, negated = false)
41
+ value.nil? || primitive?(value)
42
+ end
43
+
44
+ end # ObjectId
45
+ end # Property
46
+ end # Mongo
47
+ end # DataMapper
@@ -3,7 +3,7 @@ module DataMapper
3
3
  # This class is responsible for taking Query instances from DataMapper and
4
4
  # formatting the query such that it can be performed by the Mongo library.
5
5
  class Query
6
- include Extlib::Assertions
6
+ include DataMapper::Assertions
7
7
  include DataMapper::Query::Conditions
8
8
 
9
9
  # Creates a new Query instance
@@ -42,7 +42,7 @@ module DataMapper
42
42
 
43
43
  # TODO: atm ruby driver doesn't support count with statements,
44
44
  # that's why we use find and size here as a tmp workaround
45
- if @statements.blank?
45
+ if @statements.keys.empty?
46
46
  [@collection.count]
47
47
  else
48
48
  [find.size]
@@ -68,7 +68,7 @@ module DataMapper
68
68
  end
69
69
  end
70
70
 
71
- if operators.blank?
71
+ if operators.empty?
72
72
  initial = {}
73
73
  reduce = JavaScript::Reduce.new.to_s
74
74
  finalize = nil
@@ -81,9 +81,14 @@ module DataMapper
81
81
 
82
82
  keys = keys - initial.keys
83
83
  end
84
-
85
- @collection.group(keys, @statements, initial, reduce, true, finalize).map do |records|
86
- records.to_mash.symbolize_keys.only(*property_names)
84
+
85
+ opts = {
86
+ :key => keys, :cond => @statements, :initial => initial,
87
+ :reduce => reduce, :finalize => finalize
88
+ }
89
+
90
+ @collection.group(opts).map do |records|
91
+ DataMapper::Ext::Hash.to_mash(records).symbolize_keys
87
92
  end
88
93
  end
89
94
 
@@ -93,10 +98,10 @@ module DataMapper
93
98
  # @api private
94
99
  def setup_conditions_and_options
95
100
  @options = {}
96
-
101
+
97
102
  @options[:limit] = @query.limit if @query.limit
98
103
  @options[:skip] = @query.offset if @query.offset
99
- @options[:sort] = sort_statement(@query.order) unless @query.order.empty?
104
+ @options[:sort] = sort_statement(@query.order) unless @query.order.nil?
100
105
 
101
106
  conditions_statement(@query.conditions)
102
107
  end
@@ -144,16 +149,6 @@ module DataMapper
144
149
  end
145
150
  end
146
151
 
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.storage_name}.#{comparison.subject.field}"
153
-
154
- update_statements(comparison, field, affirmative)
155
- end
156
-
157
152
  # Takes a Comparison condition and returns a Mongo-compatible hash
158
153
  #
159
154
  # @param [DataMapper::Query::Conditions::Comparison] comparison
@@ -170,39 +165,41 @@ module DataMapper
170
165
  return conditions_statement(comparison.foreign_key_mapping, affirmative)
171
166
  end
172
167
 
173
- if comparison.subject.model && comparison.subject.model < EmbeddedResource
174
- return comparison_statement_for_embedment(comparison, affirmative)
175
- end
176
-
177
168
  update_statements(comparison, comparison.subject.field, affirmative)
178
169
  end
179
170
 
180
171
  # TODO: document
181
172
  # @api private
182
- def update_statements(comparison, field, affirmative = true)
183
- value = comparison.value
173
+ def
174
+ update_statements(comparison, field, affirmative = true)
175
+ value = if comparison.value.kind_of?(Array)
176
+ comparison.value.map { |value| value.class.to_mongo(value) }
177
+ else
178
+ comparison.value.class.to_mongo(comparison.value)
179
+ end
184
180
 
185
181
  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
182
+ case comparison
183
+ when EqualToComparison then value
184
+ when GreaterThanComparison then {'$gt' => value}
185
+ when LessThanComparison then {'$lt' => value}
186
+ when GreaterThanOrEqualToComparison then {'$gte' => value}
187
+ when LessThanOrEqualToComparison then {'$lte' => value}
188
+ when InclusionComparison then inclusion_comparison_operator(comparison, value)
189
+ when RegexpComparison then value
190
+ when LikeComparison then comparison.send(:expected)
191
+ else
192
+ raise NotImplementedError
193
+ end
194
+ else
195
+ case comparison
196
+ when EqualToComparison then {'$ne' => value}
197
+ when InclusionComparison then {'$nin' => value}
198
+ when RegexpComparison then {'$not' => value}
199
+ else
200
+ raise NotImplementedError
201
+ end
202
+ end
206
203
 
207
204
  operator.is_a?(Hash) ?
208
205
  (@statements[field.to_sym] ||= {}).merge!(operator) : @statements[field.to_sym] = operator
@@ -223,7 +220,7 @@ module DataMapper
223
220
  {'$gte' => value.first, value.exclude_end? ? '$lt' : '$lte' => value.last}
224
221
  elsif comparison.kind_of?(InclusionComparison) && value.size == 1
225
222
  value.first
226
- elsif comparison.subject.type == DataMapper::Mongo::Types::EmbeddedArray
223
+ elsif comparison.subject.kind_of?(DataMapper::Mongo::Property::Array)
227
224
  value
228
225
  else
229
226
  {'$in' => value}