mongo_mapper 0.5.8 → 0.6.0

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