mongo_mapper 0.5.8 → 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 (47) hide show
  1. data/Rakefile +4 -4
  2. data/VERSION +1 -1
  3. data/bin/mmconsole +10 -5
  4. data/lib/mongo_mapper.rb +28 -5
  5. data/lib/mongo_mapper/associations.rb +113 -12
  6. data/lib/mongo_mapper/associations/base.rb +24 -9
  7. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +1 -1
  8. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +1 -1
  9. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +2 -2
  10. data/lib/mongo_mapper/associations/many_documents_proxy.rb +7 -2
  11. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +22 -36
  12. data/lib/mongo_mapper/associations/proxy.rb +11 -6
  13. data/lib/mongo_mapper/document.rb +37 -21
  14. data/lib/mongo_mapper/embedded_document.rb +32 -18
  15. data/lib/mongo_mapper/finder_options.rb +19 -12
  16. data/lib/mongo_mapper/rails_compatibility/document.rb +4 -0
  17. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +4 -0
  18. data/lib/mongo_mapper/support.rb +18 -46
  19. data/lib/mongo_mapper/types.rb +64 -0
  20. data/lib/mongo_mapper/validations.rb +13 -43
  21. data/mongo_mapper.gemspec +13 -10
  22. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +10 -10
  23. data/test/functional/associations/test_belongs_to_proxy.rb +29 -30
  24. data/test/functional/associations/test_many_documents_as_proxy.rb +13 -12
  25. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +34 -34
  26. data/test/functional/associations/test_many_embedded_proxy.rb +69 -74
  27. data/test/functional/associations/test_many_polymorphic_proxy.rb +10 -10
  28. data/test/functional/associations/test_many_proxy.rb +14 -15
  29. data/test/functional/test_associations.rb +4 -4
  30. data/test/functional/test_binary.rb +1 -1
  31. data/test/functional/test_dirty.rb +6 -6
  32. data/test/functional/test_document.rb +76 -69
  33. data/test/functional/test_embedded_document.rb +15 -14
  34. data/test/functional/test_pagination.rb +9 -1
  35. data/test/functional/test_string_id_compatibility.rb +72 -0
  36. data/test/functional/test_validations.rb +56 -7
  37. data/test/models.rb +7 -7
  38. data/test/test_helper.rb +2 -5
  39. data/test/unit/test_association_base.rb +6 -1
  40. data/test/unit/test_document.rb +22 -13
  41. data/test/unit/test_embedded_document.rb +47 -5
  42. data/test/unit/test_finder_options.rb +22 -3
  43. data/test/unit/test_mongo_mapper.rb +65 -0
  44. data/test/unit/test_rails_compatibility.rb +14 -0
  45. data/test/unit/test_support.rb +45 -0
  46. metadata +9 -6
  47. data/test/unit/test_mongomapper.rb +0 -28
@@ -6,7 +6,7 @@ module MongoMapper
6
6
  def initialize(owner, association)
7
7
  @owner = owner
8
8
  @association = association
9
- @association.options[:extend].each { |ext| proxy_extend(ext) }
9
+ @association.options[:extend].each { |ext| class << self; self; end.instance_eval { include ext } }
10
10
  reset
11
11
  end
12
12
 
@@ -25,7 +25,12 @@ module MongoMapper
25
25
  end
26
26
 
27
27
  def send(method, *args)
28
- return super if methods.include?(method.to_s)
28
+ metaclass_instance_methods = class << self; self; end.instance_methods
29
+
30
+ if metaclass_instance_methods.any? { |m| m.to_s == method.to_s }
31
+ return __send__(method, *args)
32
+ end
33
+
29
34
  load_target
30
35
  @target.send(method, *args)
31
36
  end
@@ -45,12 +50,12 @@ module MongoMapper
45
50
  end
46
51
 
47
52
  protected
48
- def method_missing(method, *args)
53
+ def method_missing(method, *args, &block)
49
54
  if load_target
50
- if block_given?
51
- @target.send(method, *args) { |*block_args| yield(*block_args) }
52
- else
55
+ if block.nil?
53
56
  @target.send(method, *args)
57
+ else
58
+ @target.send(method, *args) { |*block_args| block.call(*block_args) }
54
59
  end
55
60
  end
56
61
  end
@@ -55,7 +55,7 @@ module MongoMapper
55
55
  # @overload find(ids, options)
56
56
  #
57
57
  # @raise DocumentNotFound raised when no ID or arguments are provided
58
- def find(*args)
58
+ def find!(*args)
59
59
  options = args.extract_options!
60
60
  case args.first
61
61
  when :first then first(options)
@@ -73,6 +73,12 @@ module MongoMapper
73
73
  end
74
74
  end
75
75
  end
76
+
77
+ def find(*args)
78
+ find!(*args)
79
+ rescue DocumentNotFound
80
+ nil
81
+ end
76
82
 
77
83
  def paginate(options)
78
84
  per_page = options.delete(:per_page) || self.per_page
@@ -247,26 +253,36 @@ module MongoMapper
247
253
  @connection
248
254
  end
249
255
 
250
- # @overload database()
251
- # @return [Mongo] the database object used by your document class
256
+ # Changes the database name from the default to whatever you want
257
+ #
258
+ # @param [#to_s] name the new database name to use.
259
+ def set_database_name(name)
260
+ @database_name = name
261
+ end
262
+
263
+ # Returns the database name
264
+ #
265
+ # @return [String] the database name
266
+ def database_name
267
+ @database_name
268
+ end
269
+
270
+ # Returns the database the document should use. Defaults to
271
+ # MongoMapper.database if other database is not set.
252
272
  #
253
- # @overload database(name)
254
- # @param [String] name the name of an existing, or new, Mongo database
255
- # @return [Mongo] a Mongo database object for the specified database
256
- def database(name=nil)
257
- if name.nil?
258
- @database ||= MongoMapper.database
273
+ # @return [Mongo::DB] the mongo database instance
274
+ def database
275
+ if database_name.nil?
276
+ MongoMapper.database
259
277
  else
260
- @database = connection.db(name)
278
+ connection.db(database_name)
261
279
  end
262
- @database
263
280
  end
264
281
 
265
282
  # Changes the collection name from the default to whatever you want
266
283
  #
267
- # @param [#to_s] name the new collection name to use. Defaults to +nil+
268
- def set_collection_name(name=nil)
269
- @collection = nil
284
+ # @param [#to_s] name the new collection name to use.
285
+ def set_collection_name(name)
270
286
  @collection_name = name
271
287
  end
272
288
 
@@ -280,7 +296,7 @@ module MongoMapper
280
296
 
281
297
  # @return the Mongo Ruby driver +collection+ object
282
298
  def collection
283
- @collection ||= database.collection(collection_name)
299
+ database.collection(collection_name)
284
300
  end
285
301
 
286
302
  # Defines a +created_at+ and +updated_at+ attribute (with a +Time+
@@ -416,22 +432,22 @@ module MongoMapper
416
432
  read_attribute('_id').blank? || using_custom_id?
417
433
  end
418
434
 
419
- def save
420
- valid? ? create_or_update : false
435
+ def save(perform_validations=true)
436
+ !perform_validations || valid? ? create_or_update : false
421
437
  end
422
438
 
423
439
  def save!
424
- valid? ? create_or_update : raise(DocumentNotValid.new(self))
440
+ save || raise(DocumentNotValid.new(self))
425
441
  end
426
442
 
427
443
  def destroy
428
444
  return false if frozen?
429
- self.class.delete(id) unless new?
445
+ self.class.delete(_id) unless new?
430
446
  freeze
431
447
  end
432
448
 
433
449
  def reload
434
- self.class.find(id)
450
+ self.class.find(_id)
435
451
  end
436
452
 
437
453
  private
@@ -447,7 +463,7 @@ module MongoMapper
447
463
 
448
464
  def assign_id
449
465
  if read_attribute(:_id).blank?
450
- write_attribute(:_id, Mongo::ObjectID.new.to_s)
466
+ write_attribute :_id, Mongo::ObjectID.new
451
467
  end
452
468
  end
453
469
 
@@ -16,7 +16,7 @@ module MongoMapper
16
16
 
17
17
  extend Validations::Macros
18
18
 
19
- key :_id, String
19
+ key :_id, ObjectId
20
20
  attr_accessor :_root_document
21
21
  end
22
22
  end
@@ -25,7 +25,7 @@ module MongoMapper
25
25
  def logger
26
26
  MongoMapper.logger
27
27
  end
28
-
28
+
29
29
  def inherited(subclass)
30
30
  unless subclass.embeddable?
31
31
  subclass.set_collection_name(collection_name)
@@ -48,19 +48,23 @@ module MongoMapper
48
48
 
49
49
  def key(*args)
50
50
  key = Key.new(*args)
51
+ keys[key.name] = key
51
52
 
52
- if keys[key.name].blank?
53
- keys[key.name] = key
53
+ create_accessors_for(key)
54
+ create_key_in_subclasses(*args)
55
+ create_validations_for(key)
54
56
 
55
- create_accessors_for(key)
56
- create_key_in_subclasses(*args)
57
- create_validations_for(key)
58
-
59
- key
60
- end
61
-
62
57
  key
63
58
  end
59
+
60
+ def using_object_id?
61
+ object_id_key?(:_id)
62
+ end
63
+
64
+ def object_id_key?(name)
65
+ key = keys[name.to_s]
66
+ key && key.type == ObjectId
67
+ end
64
68
 
65
69
  def embeddable?
66
70
  !self.ancestors.include?(Document)
@@ -89,7 +93,13 @@ module MongoMapper
89
93
 
90
94
  private
91
95
  def accessors_module
92
- if const_defined?('MongoMapperKeys')
96
+ module_defined = if method(:const_defined?).arity == 1 # Ruby 1.9 compat check
97
+ const_defined?('MongoMapperKeys')
98
+ else
99
+ const_defined?('MongoMapperKeys', false)
100
+ end
101
+
102
+ if module_defined
93
103
  const_get 'MongoMapperKeys'
94
104
  else
95
105
  const_set 'MongoMapperKeys', Module.new
@@ -187,14 +197,14 @@ module MongoMapper
187
197
 
188
198
  if self.class.embeddable?
189
199
  if read_attribute(:_id).blank?
190
- write_attribute :_id, Mongo::ObjectID.new.to_s
200
+ write_attribute :_id, Mongo::ObjectID.new
191
201
  @new_document = true
192
202
  else
193
203
  @new_document = false
194
204
  end
195
205
  end
196
206
  end
197
-
207
+
198
208
  def new?
199
209
  !!@new_document
200
210
  end
@@ -220,7 +230,6 @@ module MongoMapper
220
230
  attrs = HashWithIndifferentAccess.new
221
231
 
222
232
  embedded_keys.each do |key|
223
- puts key.inspect
224
233
  attrs[key.name] = read_attribute(key.name).try(:attributes)
225
234
  end
226
235
 
@@ -270,15 +279,20 @@ module MongoMapper
270
279
  end
271
280
 
272
281
  def ==(other)
273
- other.is_a?(self.class) && id == other.id
282
+ other.is_a?(self.class) && _id == other._id
274
283
  end
275
284
 
276
285
  def id
277
- read_attribute(:_id)
286
+ read_attribute(:_id).to_s
278
287
  end
279
288
 
280
289
  def id=(value)
281
- @using_custom_id = true
290
+ if self.class.using_object_id?
291
+ value = MongoMapper.normalize_object_id(value)
292
+ else
293
+ @using_custom_id = true
294
+ end
295
+
282
296
  write_attribute :_id, value
283
297
  end
284
298
 
@@ -8,24 +8,15 @@ module MongoMapper
8
8
  # useful for understanding how MongoMapper handles the parsing of finder
9
9
  # conditions and options.
10
10
  #
11
- # @private
12
- class FinderOperator
13
- def initialize(field, operator)
14
- @field, @operator = field, operator
15
- end
16
-
17
- def to_criteria(value)
18
- {@field => {@operator => value}}
19
- end
20
- end
21
-
11
+ # @private
22
12
  class FinderOptions
23
13
  OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
24
14
 
25
15
  def initialize(model, options)
26
16
  raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
17
+ options = options.clone
27
18
  options.symbolize_keys!
28
-
19
+
29
20
  @model = model
30
21
  @options = {}
31
22
  @conditions = options.delete(:conditions) || {}
@@ -71,10 +62,16 @@ module MongoMapper
71
62
 
72
63
  conditions.each_pair do |field, value|
73
64
  field = normalized_field(field)
65
+
66
+ if @model.object_id_key?(field) && value.is_a?(String)
67
+ value = Mongo::ObjectID.from_string(value)
68
+ end
69
+
74
70
  if field.is_a?(FinderOperator)
75
71
  criteria.merge!(field.to_criteria(value))
76
72
  next
77
73
  end
74
+
78
75
  case value
79
76
  when Array
80
77
  operator_present = field.to_s =~ /^\$/
@@ -127,4 +124,14 @@ module MongoMapper
127
124
  [field, direction]
128
125
  end
129
126
  end
127
+
128
+ class FinderOperator
129
+ def initialize(field, operator)
130
+ @field, @operator = field, operator
131
+ end
132
+
133
+ def to_criteria(value)
134
+ {@field => {@operator => value}}
135
+ end
136
+ end
130
137
  end
@@ -4,6 +4,10 @@ module MongoMapper
4
4
  def self.included(model)
5
5
  model.class_eval do
6
6
  alias_method :new_record?, :new?
7
+
8
+ def human_name
9
+ self.name.demodulize.titleize
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -17,6 +17,10 @@ module MongoMapper
17
17
  def column_names
18
18
  keys.keys
19
19
  end
20
+
21
+ def human_name
22
+ self.name.demodulize.titleize
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -1,10 +1,12 @@
1
+ require 'set'
2
+
1
3
  class BasicObject #:nodoc:
2
- alias_method :proxy_extend, :extend
3
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|^methods$|instance_eval|proxy_|^object_id$)/ }
4
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|instance_eval)/ }
4
5
  end unless defined?(BasicObject)
5
6
 
6
7
  class Array
7
8
  def self.to_mongo(value)
9
+ value = value.respond_to?(:lines) ? value.lines : value
8
10
  value.to_a
9
11
  end
10
12
 
@@ -13,34 +15,6 @@ class Array
13
15
  end
14
16
  end
15
17
 
16
- class Binary
17
- def self.to_mongo(value)
18
- if value.is_a?(ByteBuffer)
19
- value
20
- else
21
- value.nil? ? nil : ByteBuffer.new(value)
22
- end
23
- end
24
-
25
- def self.from_mongo(value)
26
- value
27
- end
28
- end
29
-
30
- class Boolean
31
- def self.to_mongo(value)
32
- if value.is_a?(Boolean)
33
- value
34
- else
35
- ['true', 't', '1'].include?(value.to_s.downcase)
36
- end
37
- end
38
-
39
- def self.from_mongo(value)
40
- !!value
41
- end
42
- end
43
-
44
18
  class Date
45
19
  def self.to_mongo(value)
46
20
  date = Date.parse(value.to_s)
@@ -120,6 +94,16 @@ class Object
120
94
  end
121
95
  end
122
96
 
97
+ class Set
98
+ def self.to_mongo(value)
99
+ value.to_a
100
+ end
101
+
102
+ def self.from_mongo(value)
103
+ Set.new(value || [])
104
+ end
105
+ end
106
+
123
107
  class String
124
108
  def self.to_mongo(value)
125
109
  value.nil? ? nil : value.to_s
@@ -138,33 +122,21 @@ class Symbol
138
122
  end
139
123
  end
140
124
 
141
- class Time
125
+ class Time
142
126
  def self.to_mongo(value)
143
127
  if value.nil? || value == ''
144
128
  nil
145
129
  else
146
- to_utc_time(value)
130
+ time = MongoMapper.time_class.parse(value.to_s)
131
+ time && time.utc
147
132
  end
148
133
  end
149
134
 
150
135
  def self.from_mongo(value)
151
- if Time.respond_to?(:zone) && Time.zone && value.present?
136
+ if MongoMapper.use_time_zone? && value.present?
152
137
  value.in_time_zone(Time.zone)
153
138
  else
154
139
  value
155
140
  end
156
141
  end
157
-
158
- def self.to_utc_time(value)
159
- to_local_time(value).try(:utc)
160
- end
161
-
162
- # make sure we have a time and that it is local
163
- def self.to_local_time(value)
164
- if Time.respond_to?(:zone) && Time.zone
165
- Time.zone.parse(value.to_s)
166
- else
167
- Time.parse(value.to_s)
168
- end
169
- end
170
142
  end