crnixon-mongomapper 0.2.0 → 0.3.4

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 (68) hide show
  1. data/.gitignore +1 -0
  2. data/History +48 -0
  3. data/README.rdoc +5 -3
  4. data/Rakefile +6 -4
  5. data/VERSION +1 -1
  6. data/bin/mmconsole +56 -0
  7. data/lib/mongomapper.rb +29 -18
  8. data/lib/mongomapper/associations.rb +53 -38
  9. data/lib/mongomapper/associations/base.rb +53 -20
  10. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
  12. data/lib/mongomapper/associations/many_documents_as_proxy.rb +27 -0
  13. data/lib/mongomapper/associations/many_documents_proxy.rb +103 -0
  14. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +6 -8
  16. data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongomapper/associations/{array_proxy.rb → many_proxy.rb} +1 -1
  18. data/lib/mongomapper/associations/proxy.rb +24 -21
  19. data/lib/mongomapper/callbacks.rb +1 -1
  20. data/lib/mongomapper/document.rb +160 -74
  21. data/lib/mongomapper/dynamic_finder.rb +38 -0
  22. data/lib/mongomapper/embedded_document.rb +154 -105
  23. data/lib/mongomapper/finder_options.rb +11 -7
  24. data/lib/mongomapper/key.rb +15 -21
  25. data/lib/mongomapper/pagination.rb +52 -0
  26. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  27. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  28. data/lib/mongomapper/serialization.rb +1 -1
  29. data/lib/mongomapper/serializers/json_serializer.rb +15 -0
  30. data/lib/mongomapper/support.rb +30 -0
  31. data/mongomapper.gemspec +87 -46
  32. data/test/NOTE_ON_TESTING +1 -0
  33. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -0
  34. data/test/functional/associations/test_belongs_to_proxy.rb +45 -0
  35. data/test/functional/associations/test_many_documents_as_proxy.rb +253 -0
  36. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
  37. data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
  38. data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -0
  39. data/test/functional/associations/test_many_proxy.rb +295 -0
  40. data/test/functional/test_associations.rb +47 -0
  41. data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
  42. data/test/functional/test_document.rb +952 -0
  43. data/test/functional/test_pagination.rb +81 -0
  44. data/test/functional/test_rails_compatibility.rb +30 -0
  45. data/test/functional/test_validations.rb +172 -0
  46. data/test/models.rb +169 -0
  47. data/test/test_helper.rb +7 -2
  48. data/test/unit/serializers/test_json_serializer.rb +189 -0
  49. data/test/unit/test_association_base.rb +144 -0
  50. data/test/unit/test_document.rb +123 -0
  51. data/test/unit/test_embedded_document.rb +526 -0
  52. data/test/{test_finder_options.rb → unit/test_finder_options.rb} +36 -1
  53. data/test/{test_key.rb → unit/test_key.rb} +59 -12
  54. data/test/{test_mongomapper.rb → unit/test_mongomapper.rb} +0 -0
  55. data/test/{test_observing.rb → unit/test_observing.rb} +0 -0
  56. data/test/unit/test_pagination.rb +113 -0
  57. data/test/unit/test_rails_compatibility.rb +34 -0
  58. data/test/{test_serializations.rb → unit/test_serializations.rb} +0 -2
  59. data/test/{test_validations.rb → unit/test_validations.rb} +0 -134
  60. metadata +81 -43
  61. data/lib/mongomapper/associations/has_many_proxy.rb +0 -28
  62. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +0 -31
  63. data/lib/mongomapper/rails_compatibility.rb +0 -23
  64. data/test/serializers/test_json_serializer.rb +0 -104
  65. data/test/test_associations.rb +0 -174
  66. data/test/test_document.rb +0 -944
  67. data/test/test_embedded_document.rb +0 -253
  68. data/test/test_rails_compatibility.rb +0 -29
@@ -0,0 +1,34 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class BelongsToPolymorphicProxy < Proxy
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id, type = doc.id, doc.class.name
8
+ end
9
+
10
+ @owner.send("#{@association.foreign_key}=", id)
11
+ @owner.send("#{@association.type_key_name}=", type)
12
+ reset
13
+ end
14
+
15
+ protected
16
+ def find_target
17
+ if proxy_id && proxy_class
18
+ proxy_class.find_by_id(proxy_id)
19
+ end
20
+ end
21
+
22
+ def proxy_id
23
+ @proxy_id ||= @owner.send(@association.foreign_key)
24
+ end
25
+
26
+ def proxy_class
27
+ @proxy_class ||= begin
28
+ klass = @owner.send(@association.type_key_name)
29
+ klass && klass.constantize
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,26 +1,22 @@
1
1
  module MongoMapper
2
2
  module Associations
3
3
  class BelongsToProxy < Proxy
4
- def replace(v)
5
- ref_id = "#{@association.name}_id"
6
-
7
- if v
8
- v.save if v.new?
9
- @owner.__send__(:write_attribute, ref_id, v.id)
10
- else
11
- @owner.__send__(:write_attribute, ref_id, nil)
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id = doc.id
12
8
  end
13
9
 
14
- reload_target
10
+ @owner.send("#{@association.foreign_key}=", id)
11
+ reset
15
12
  end
16
13
 
17
14
  protected
18
- def find_target
19
- ref = @owner.__send__(:read_attribute, "#{@association.name}_id")
20
- if ref
21
- @association.klass.find(ref)
15
+ def find_target
16
+ if association_id = @owner.send(@association.foreign_key)
17
+ @association.klass.find_by_id(association_id)
18
+ end
22
19
  end
23
- end
24
20
  end
25
21
  end
26
22
  end
@@ -0,0 +1,27 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsAsProxy < ManyDocumentsProxy
4
+ protected
5
+ def scoped_conditions
6
+ {as_type_name => @owner.class.name, as_id_name => @owner.id}
7
+ end
8
+
9
+ def apply_scope(doc)
10
+ ensure_owner_saved
11
+
12
+ doc.send("#{as_type_name}=", @owner.class.name)
13
+ doc.send("#{as_id_name}=", @owner.id)
14
+
15
+ doc
16
+ end
17
+
18
+ def as_type_name
19
+ @as_type_name ||= @association.options[:as].to_s + "_type"
20
+ end
21
+
22
+ def as_id_name
23
+ @as_id_name ||= @association.options[:as].to_s + "_id"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,103 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsProxy < Proxy
4
+ delegate :klass, :to => :@association
5
+
6
+ def find(*args)
7
+ options = args.extract_options!
8
+ klass.find(*args << scoped_options(options))
9
+ end
10
+
11
+ def paginate(options)
12
+ klass.paginate(scoped_options(options))
13
+ end
14
+
15
+ def all(options={})
16
+ find(:all, scoped_options(options))
17
+ end
18
+
19
+ def first(options={})
20
+ find(:first, scoped_options(options))
21
+ end
22
+
23
+ def last(options={})
24
+ find(:last, scoped_options(options))
25
+ end
26
+
27
+ def count(conditions={})
28
+ klass.count(conditions.deep_merge(scoped_conditions))
29
+ end
30
+
31
+ def replace(docs)
32
+ @target.map(&:destroy) if load_target
33
+ docs.each { |doc| apply_scope(doc).save }
34
+ reset
35
+ end
36
+
37
+ def <<(*docs)
38
+ ensure_owner_saved
39
+ flatten_deeper(docs).each { |doc| apply_scope(doc).save }
40
+ reset
41
+ end
42
+ alias_method :push, :<<
43
+ alias_method :concat, :<<
44
+
45
+ def build(attrs={})
46
+ doc = klass.new(attrs)
47
+ apply_scope(doc)
48
+ doc
49
+ end
50
+
51
+ def create(attrs={})
52
+ doc = klass.new(attrs)
53
+ apply_scope(doc).save
54
+ doc
55
+ end
56
+
57
+ def destroy_all(conditions={})
58
+ all(:conditions => conditions).map(&:destroy)
59
+ reset
60
+ end
61
+
62
+ def delete_all(conditions={})
63
+ klass.delete_all(conditions.deep_merge(scoped_conditions))
64
+ reset
65
+ end
66
+
67
+ def nullify
68
+ criteria = FinderOptions.to_mongo_criteria(scoped_conditions)
69
+ all(criteria).each do |doc|
70
+ doc.update_attributes self.foreign_key => nil
71
+ end
72
+ reset
73
+ end
74
+
75
+ protected
76
+ def scoped_conditions
77
+ {self.foreign_key => @owner.id}
78
+ end
79
+
80
+ def scoped_options(options)
81
+ options.deep_merge({:conditions => scoped_conditions})
82
+ end
83
+
84
+ def find_target
85
+ find(:all)
86
+ end
87
+
88
+ def ensure_owner_saved
89
+ @owner.save if @owner.new?
90
+ end
91
+
92
+ def apply_scope(doc)
93
+ ensure_owner_saved
94
+ doc.send("#{self.foreign_key}=", @owner.id)
95
+ doc
96
+ end
97
+
98
+ def foreign_key
99
+ @association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,33 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyEmbeddedPolymorphicProxy < Proxy
4
+ def replace(v)
5
+ @_values = v.map do |doc_or_hash|
6
+ if doc_or_hash.kind_of?(EmbeddedDocument)
7
+ doc = doc_or_hash
8
+ {@association.type_key_name => doc.class.name}.merge(doc.attributes)
9
+ else
10
+ doc_or_hash
11
+ end
12
+ end
13
+
14
+ reset
15
+ end
16
+
17
+ protected
18
+ def find_target
19
+ (@_values || []).map do |hash|
20
+ polymorphic_class(hash).new(hash)
21
+ end
22
+ end
23
+
24
+ def polymorphic_class(doc)
25
+ if class_name = doc[@association.type_key_name]
26
+ class_name.constantize
27
+ else
28
+ @association.klass
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,19 +1,17 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- class HasManyEmbeddedProxy < ArrayProxy
3
+ class ManyEmbeddedProxy < Proxy
4
4
  def replace(v)
5
5
  @_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
6
- @target = nil
7
-
8
- reload_target
6
+ reset
9
7
  end
10
8
 
11
9
  protected
12
- def find_target
13
- (@_values || []).map do |e|
14
- @association.klass.new(e)
10
+ def find_target
11
+ (@_values || []).map do |e|
12
+ @association.klass.new(e)
13
+ end
15
14
  end
16
- end
17
15
  end
18
16
  end
19
17
  end
@@ -0,0 +1,11 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyPolymorphicProxy < ManyDocumentsProxy
4
+ private
5
+ def apply_scope(doc)
6
+ doc.send("#{@association.type_key_name}=", doc.class.name)
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,6 +1,6 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- class ArrayProxy < Proxy
3
+ class ManyProxy < ManyDocumentsProxy
4
4
  end
5
5
  end
6
6
  end
@@ -1,16 +1,11 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- class Proxy
3
+ class Proxy < BasicObject
4
4
  attr_reader :owner, :association
5
-
6
- instance_methods.each do |m|
7
- undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/
8
- end
9
-
5
+
10
6
  def initialize(owner, association)
11
- @owner= owner
7
+ @owner = owner
12
8
  @association = association
13
-
14
9
  reset
15
10
  end
16
11
 
@@ -38,23 +33,31 @@ module MongoMapper
38
33
  end
39
34
 
40
35
  protected
41
- def method_missing(method, *args)
42
- if load_target
43
- if block_given?
44
- @target.send(method, *args) { |*block_args| yield(*block_args) }
45
- else
46
- @target.send(method, *args)
36
+ def method_missing(method, *args)
37
+ if load_target
38
+ if block_given?
39
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
40
+ else
41
+ @target.send(method, *args)
42
+ end
47
43
  end
48
44
  end
49
- end
50
45
 
51
- def load_target
52
- @target ||= find_target
53
- end
46
+ def load_target
47
+ @target ||= find_target
48
+ end
54
49
 
55
- def find_target
56
- raise NotImplementedError
57
- end
50
+ def find_target
51
+ raise NotImplementedError
52
+ end
53
+
54
+ # Array#flatten has problems with recursive arrays. Going one level
55
+ # deeper solves the majority of the problems.
56
+ def flatten_deeper(array)
57
+ array.collect do |element|
58
+ (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
59
+ end.flatten
60
+ end
58
61
  end
59
62
  end
60
63
  end
@@ -90,7 +90,7 @@ module MongoMapper
90
90
  def callback(method)
91
91
  result = run_callbacks(method) { |result, object| false == result }
92
92
 
93
- if result != false && respond_to_without_attributes?(method)
93
+ if result != false && respond_to?(method)
94
94
  result = send(method)
95
95
  end
96
96
 
@@ -9,12 +9,8 @@ module MongoMapper
9
9
  include Observing
10
10
  include Callbacks
11
11
  include SaveWithValidation
12
- include RailsCompatibility
12
+ include RailsCompatibility::Document
13
13
  extend ClassMethods
14
-
15
- key :_id, String
16
- key :created_at, Time
17
- key :updated_at, Time
18
14
  end
19
15
 
20
16
  descendants << model
@@ -23,7 +19,7 @@ module MongoMapper
23
19
  def self.descendants
24
20
  @descendants ||= Set.new
25
21
  end
26
-
22
+
27
23
  module ClassMethods
28
24
  def find(*args)
29
25
  options = args.extract_options!
@@ -32,10 +28,23 @@ module MongoMapper
32
28
  when :first then find_first(options)
33
29
  when :last then find_last(options)
34
30
  when :all then find_every(options)
35
- else find_from_ids(args)
31
+ else find_from_ids(args, options)
36
32
  end
37
33
  end
38
34
 
35
+ def paginate(options)
36
+ per_page = options.delete(:per_page)
37
+ page = options.delete(:page)
38
+ total_entries = count(options[:conditions] || {})
39
+ collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
40
+
41
+ options[:limit] = collection.limit
42
+ options[:offset] = collection.offset
43
+
44
+ collection.subject = find_every(options)
45
+ collection
46
+ end
47
+
39
48
  def first(options={})
40
49
  find_first(options)
41
50
  end
@@ -49,18 +58,19 @@ module MongoMapper
49
58
  end
50
59
 
51
60
  def find_by_id(id)
52
- if doc = collection.find_first({:_id => id})
61
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
62
+ if doc = collection.find_first(criteria)
53
63
  new(doc)
54
64
  end
55
65
  end
56
66
 
57
- # TODO: remove the rescuing when ruby driver works correctly
58
67
  def count(conditions={})
59
68
  collection.count(FinderOptions.to_mongo_criteria(conditions))
60
69
  end
61
70
 
62
71
  def create(*docs)
63
72
  instances = []
73
+ docs = [{}] if docs.blank?
64
74
  docs.flatten.each do |attrs|
65
75
  doc = new(attrs); doc.save
66
76
  instances << doc
@@ -84,11 +94,13 @@ module MongoMapper
84
94
  end
85
95
 
86
96
  def delete(*ids)
87
- collection.remove(:_id => {'$in' => ids.flatten})
97
+ criteria = FinderOptions.to_mongo_criteria(:_id => ids.flatten)
98
+ collection.remove(criteria)
88
99
  end
89
100
 
90
101
  def delete_all(conditions={})
91
- collection.remove(FinderOptions.to_mongo_criteria(conditions))
102
+ criteria = FinderOptions.to_mongo_criteria(conditions)
103
+ collection.remove(criteria)
92
104
  end
93
105
 
94
106
  def destroy(*ids)
@@ -125,11 +137,18 @@ module MongoMapper
125
137
  end
126
138
  @collection
127
139
  end
128
-
140
+
141
+ def timestamps!
142
+ key :created_at, Time
143
+ key :updated_at, Time
144
+
145
+ class_eval { before_save :update_timestamps }
146
+ end
147
+
129
148
  def validates_uniqueness_of(*args)
130
149
  add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
131
150
  end
132
-
151
+
133
152
  def validates_exclusion_of(*args)
134
153
  add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
135
154
  end
@@ -137,59 +156,125 @@ module MongoMapper
137
156
  def validates_inclusion_of(*args)
138
157
  add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
139
158
  end
159
+
160
+ protected
161
+ def method_missing(method, *args)
162
+ finder = DynamicFinder.new(self, method)
163
+
164
+ if finder.valid?
165
+ meta_def(finder.options[:method]) do |*args|
166
+ find_with_args(args, finder.options)
167
+ end
168
+
169
+ send(finder.options[:method], *args)
170
+ else
171
+ super
172
+ end
173
+ end
140
174
 
141
- private
142
- def find_every(options)
143
- criteria, options = FinderOptions.new(options).to_a
144
- collection.find(criteria, options).to_a.map { |doc| new(doc) }
145
- end
175
+ private
176
+ def find_every(options)
177
+ criteria, options = FinderOptions.new(options).to_a
178
+ collection.find(criteria, options).to_a.map { |doc| new(doc) }
179
+ end
146
180
 
147
- def find_first(options)
148
- find_every(options.merge(:limit => 1, :order => 'created_at')).first
149
- end
181
+ def find_first(options)
182
+ options.merge!(:limit => 1)
183
+ find_every({:order => '$natural asc'}.merge(options))[0]
184
+ end
150
185
 
151
- def find_last(options)
152
- find_every(options.merge(:limit => 1, :order => 'created_at desc')).first
153
- end
186
+ def find_last(options)
187
+ options.merge!(:limit => 1)
188
+ options[:order] = invert_order_clause(options)
189
+ find_every(options)[0]
190
+ #find_every({:order => '$natural desc'}.merge(invert_order_clause(options)))[0]
191
+ end
154
192
 
155
- def find_some(ids)
156
- documents = find_every(:conditions => {'_id' => ids})
157
- if ids.size == documents.size
158
- documents
159
- else
160
- raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
193
+ def invert_order_clause(options)
194
+ return '$natural desc' unless options[:order]
195
+ options[:order].split(',').map do |order_segment|
196
+ if order_segment =~ /\sasc/i
197
+ order_segment.sub /\sasc/i, ' desc'
198
+ elsif order_segment =~ /\sdesc/i
199
+ order_segment.sub /\sdesc/i, ' asc'
200
+ else
201
+ "#{order_segment.strip} desc"
202
+ end
203
+ end.join(',')
161
204
  end
162
- end
163
205
 
164
- def find_from_ids(*ids)
165
- ids = ids.flatten.compact.uniq
206
+ def find_some(ids, options={})
207
+ documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
208
+ if ids.size == documents.size
209
+ documents
210
+ else
211
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
212
+ end
213
+ end
166
214
 
167
- case ids.size
168
- when 0
169
- raise(DocumentNotFound, "Couldn't find without an ID")
170
- when 1
171
- find_by_id(ids[0]) || raise(DocumentNotFound, "Document with id of #{ids[0]} does not exist in collection named #{collection.name}")
215
+ def find_one(id, options={})
216
+ if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
217
+ doc
172
218
  else
173
- find_some(ids)
219
+ raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
220
+ end
174
221
  end
175
- end
176
222
 
177
- def update_single(id, attrs)
178
- if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
179
- raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
223
+ def find_from_ids(ids, options={})
224
+ ids = ids.flatten.compact.uniq
225
+
226
+ case ids.size
227
+ when 0
228
+ raise(DocumentNotFound, "Couldn't find without an ID")
229
+ when 1
230
+ find_one(ids[0], options)
231
+ else
232
+ find_some(ids, options)
233
+ end
234
+ end
235
+
236
+ def find_with_args(args, options)
237
+ attributes, = {}
238
+ find_options = args.extract_options!.deep_merge(:conditions => attributes)
239
+
240
+ options[:attribute_names].each_with_index do |attr, index|
241
+ attributes[attr] = args[index]
242
+ end
243
+
244
+ result = find(options[:finder], find_options)
245
+
246
+ if result.nil?
247
+ if options[:bang]
248
+ raise DocumentNotFound, "Couldn't find Document with #{attributes.inspect} in collection named #{collection.name}"
249
+ end
250
+
251
+ if options[:instantiator]
252
+ self.send(options[:instantiator], attributes)
253
+ end
254
+ else
255
+ result
256
+ end
180
257
  end
181
258
 
182
- find(id).update_attributes(attrs)
183
- end
259
+ def update_single(id, attrs)
260
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
261
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
262
+ end
184
263
 
185
- def update_multiple(docs)
186
- unless docs.is_a?(Hash)
187
- raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
264
+ doc = find(id)
265
+ doc.update_attributes(attrs)
266
+ doc
267
+ end
268
+
269
+ def update_multiple(docs)
270
+ unless docs.is_a?(Hash)
271
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
272
+ end
273
+
274
+ instances = []
275
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
276
+ instances
188
277
  end
189
- instances = []
190
- docs.each_pair { |id, attrs| instances << update(id, attrs) }
191
- instances
192
- end
193
278
  end
194
279
 
195
280
  module InstanceMethods
@@ -198,13 +283,13 @@ module MongoMapper
198
283
  end
199
284
 
200
285
  def new?
201
- read_attribute('_id').blank? || self.class.find_by_id(id).blank?
286
+ read_attribute('_id').blank? || using_custom_id?
202
287
  end
203
288
 
204
289
  def save
205
290
  create_or_update
206
291
  end
207
-
292
+
208
293
  def save!
209
294
  create_or_update || raise(DocumentNotValid.new(self))
210
295
  end
@@ -212,20 +297,14 @@ module MongoMapper
212
297
  def update_attributes(attrs={})
213
298
  self.attributes = attrs
214
299
  save
215
- self
216
300
  end
217
301
 
218
302
  def destroy
219
- collection.remove(:_id => id) unless new?
220
- freeze
221
- end
303
+ return false if frozen?
222
304
 
223
- def ==(other)
224
- other.is_a?(self.class) && id == other.id
225
- end
226
-
227
- def id
228
- read_attribute('_id')
305
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
306
+ collection.remove(criteria) unless new?
307
+ freeze
229
308
  end
230
309
 
231
310
  private
@@ -233,29 +312,36 @@ module MongoMapper
233
312
  result = new? ? create : update
234
313
  result != false
235
314
  end
236
-
315
+
237
316
  def create
238
- write_attribute('_id', generate_id) if read_attribute('_id').blank?
239
- update_timestamps
317
+ assign_id
240
318
  save_to_collection
241
319
  end
320
+
321
+ def assign_id
322
+ if read_attribute(:_id).blank?
323
+ write_attribute(:_id, XGen::Mongo::Driver::ObjectID.new.to_s)
324
+ end
325
+ end
242
326
 
243
327
  def update
244
- update_timestamps
245
328
  save_to_collection
246
329
  end
247
330
 
331
+ # collection.save returns mongoid
248
332
  def save_to_collection
249
- collection.save(attributes.merge!(embedded_association_attributes))
333
+ clear_custom_id_flag
334
+ collection.save(attributes)
250
335
  end
251
336
 
252
337
  def update_timestamps
253
- write_attribute('created_at', Time.now.utc) if new?
254
- write_attribute('updated_at', Time.now.utc)
338
+ now = Time.now.utc
339
+ write_attribute('created_at', now) if new?
340
+ write_attribute('updated_at', now)
255
341
  end
256
-
257
- def generate_id
258
- XGen::Mongo::Driver::ObjectID.new
342
+
343
+ def clear_custom_id_flag
344
+ @using_custom_id = nil
259
345
  end
260
346
  end
261
347
  end # Document