dm-mongo-adapter 0.2.0.pre3 → 0.6.0

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