shingara-mongomapper 0.3.3

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 (62) hide show
  1. data/.gitignore +7 -0
  2. data/History +70 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +73 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +56 -0
  8. data/lib/mongomapper.rb +77 -0
  9. data/lib/mongomapper/associations.rb +84 -0
  10. data/lib/mongomapper/associations/base.rb +69 -0
  11. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  12. data/lib/mongomapper/associations/belongs_to_proxy.rb +22 -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/many_embedded_proxy.rb +17 -0
  16. data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongomapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongomapper/associations/proxy.rb +63 -0
  19. data/lib/mongomapper/callbacks.rb +106 -0
  20. data/lib/mongomapper/document.rb +348 -0
  21. data/lib/mongomapper/dynamic_finder.rb +38 -0
  22. data/lib/mongomapper/embedded_document.rb +265 -0
  23. data/lib/mongomapper/finder_options.rb +85 -0
  24. data/lib/mongomapper/key.rb +76 -0
  25. data/lib/mongomapper/observing.rb +50 -0
  26. data/lib/mongomapper/pagination.rb +52 -0
  27. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  29. data/lib/mongomapper/save_with_validation.rb +19 -0
  30. data/lib/mongomapper/serialization.rb +55 -0
  31. data/lib/mongomapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongomapper/support.rb +30 -0
  33. data/lib/mongomapper/validations.rb +47 -0
  34. data/mongomapper.gemspec +142 -0
  35. data/test/NOTE_ON_TESTING +1 -0
  36. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -0
  37. data/test/functional/associations/test_belongs_to_proxy.rb +45 -0
  38. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
  39. data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
  40. data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -0
  41. data/test/functional/associations/test_many_proxy.rb +295 -0
  42. data/test/functional/test_associations.rb +47 -0
  43. data/test/functional/test_callbacks.rb +85 -0
  44. data/test/functional/test_document.rb +952 -0
  45. data/test/functional/test_pagination.rb +81 -0
  46. data/test/functional/test_rails_compatibility.rb +30 -0
  47. data/test/functional/test_validations.rb +172 -0
  48. data/test/models.rb +139 -0
  49. data/test/test_helper.rb +67 -0
  50. data/test/unit/serializers/test_json_serializer.rb +157 -0
  51. data/test/unit/test_association_base.rb +144 -0
  52. data/test/unit/test_document.rb +123 -0
  53. data/test/unit/test_embedded_document.rb +526 -0
  54. data/test/unit/test_finder_options.rb +183 -0
  55. data/test/unit/test_key.rb +247 -0
  56. data/test/unit/test_mongomapper.rb +28 -0
  57. data/test/unit/test_observing.rb +101 -0
  58. data/test/unit/test_pagination.rb +113 -0
  59. data/test/unit/test_rails_compatibility.rb +34 -0
  60. data/test/unit/test_serializations.rb +52 -0
  61. data/test/unit/test_validations.rb +259 -0
  62. metadata +189 -0
@@ -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
@@ -0,0 +1,22 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class BelongsToProxy < Proxy
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id = doc.id
8
+ end
9
+
10
+ @owner.send("#{@association.foreign_key}=", id)
11
+ reset
12
+ end
13
+
14
+ protected
15
+ def find_target
16
+ if association_id = @owner.send(@association.foreign_key)
17
+ @association.klass.find_by_id(association_id)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ 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
@@ -0,0 +1,17 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyEmbeddedProxy < Proxy
4
+ def replace(v)
5
+ @_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
6
+ reset
7
+ end
8
+
9
+ protected
10
+ def find_target
11
+ (@_values || []).map do |e|
12
+ @association.klass.new(e)
13
+ end
14
+ end
15
+ end
16
+ end
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
@@ -0,0 +1,6 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyProxy < ManyDocumentsProxy
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,63 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class Proxy < BasicObject
4
+ attr_reader :owner, :association
5
+
6
+ def initialize(owner, association)
7
+ @owner = owner
8
+ @association = association
9
+ reset
10
+ end
11
+
12
+ def respond_to?(*methods)
13
+ (load_target && @target.respond_to?(*methods))
14
+ end
15
+
16
+ def reset
17
+ @target = nil
18
+ end
19
+
20
+ def reload_target
21
+ reset
22
+ load_target
23
+ self
24
+ end
25
+
26
+ def send(method, *args)
27
+ load_target
28
+ @target.send(method, *args)
29
+ end
30
+
31
+ def replace(v)
32
+ raise NotImplementedError
33
+ end
34
+
35
+ protected
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
43
+ end
44
+ end
45
+
46
+ def load_target
47
+ @target ||= find_target
48
+ end
49
+
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
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,106 @@
1
+ module MongoMapper
2
+ module Callbacks
3
+ def self.included(model) #:nodoc:
4
+ model.class_eval do
5
+ extend Observable
6
+ include ActiveSupport::Callbacks
7
+
8
+ define_callbacks *%w(
9
+ before_save after_save before_create after_create before_update after_update before_validation
10
+ after_validation before_validation_on_create after_validation_on_create before_validation_on_update
11
+ after_validation_on_update before_destroy after_destroy
12
+ )
13
+
14
+ [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
15
+ alias_method_chain method, :callbacks
16
+ end
17
+ end
18
+ end
19
+
20
+ def before_save() end
21
+
22
+ def after_save() end
23
+ def create_or_update_with_callbacks #:nodoc:
24
+ return false if callback(:before_save) == false
25
+ if result = create_or_update_without_callbacks
26
+ callback(:after_save)
27
+ end
28
+ result
29
+ end
30
+ private :create_or_update_with_callbacks
31
+
32
+ def before_create() end
33
+
34
+ def after_create() end
35
+ def create_with_callbacks #:nodoc:
36
+ return false if callback(:before_create) == false
37
+ result = create_without_callbacks
38
+ callback(:after_create)
39
+ result
40
+ end
41
+ private :create_with_callbacks
42
+
43
+ def before_update() end
44
+
45
+ def after_update() end
46
+
47
+ def update_with_callbacks(*args) #:nodoc:
48
+ return false if callback(:before_update) == false
49
+ result = update_without_callbacks(*args)
50
+ callback(:after_update)
51
+ result
52
+ end
53
+ private :update_with_callbacks
54
+
55
+ def before_validation() end
56
+
57
+ def after_validation() end
58
+
59
+ def before_validation_on_create() end
60
+
61
+ def after_validation_on_create() end
62
+
63
+ def before_validation_on_update() end
64
+
65
+ def after_validation_on_update() end
66
+
67
+ def valid_with_callbacks? #:nodoc:
68
+ return false if callback(:before_validation) == false
69
+ result = new? ? callback(:before_validation_on_create) : callback(:before_validation_on_update)
70
+ return false if false == result
71
+
72
+ result = valid_without_callbacks?
73
+ callback(:after_validation)
74
+
75
+ new? ? callback(:after_validation_on_create) : callback(:after_validation_on_update)
76
+ return result
77
+ end
78
+
79
+ def before_destroy() end
80
+
81
+ def after_destroy() end
82
+ def destroy_with_callbacks #:nodoc:
83
+ return false if callback(:before_destroy) == false
84
+ result = destroy_without_callbacks
85
+ callback(:after_destroy)
86
+ result
87
+ end
88
+
89
+ private
90
+ def callback(method)
91
+ result = run_callbacks(method) { |result, object| false == result }
92
+
93
+ if result != false && respond_to?(method)
94
+ result = send(method)
95
+ end
96
+
97
+ notify(method)
98
+ return result
99
+ end
100
+
101
+ def notify(method) #:nodoc:
102
+ self.class.changed
103
+ self.class.notify_observers(method, self)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,348 @@
1
+ require 'set'
2
+
3
+ module MongoMapper
4
+ module Document
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include EmbeddedDocument
8
+ include InstanceMethods
9
+ include Observing
10
+ include Callbacks
11
+ include SaveWithValidation
12
+ include RailsCompatibility::Document
13
+ extend ClassMethods
14
+ end
15
+
16
+ descendants << model
17
+ end
18
+
19
+ def self.descendants
20
+ @descendants ||= Set.new
21
+ end
22
+
23
+ module ClassMethods
24
+ def find(*args)
25
+ options = args.extract_options!
26
+
27
+ case args.first
28
+ when :first then find_first(options)
29
+ when :last then find_last(options)
30
+ when :all then find_every(options)
31
+ else find_from_ids(args, options)
32
+ end
33
+ end
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
+
48
+ def first(options={})
49
+ find_first(options)
50
+ end
51
+
52
+ def last(options={})
53
+ find_last(options)
54
+ end
55
+
56
+ def all(options={})
57
+ find_every(options)
58
+ end
59
+
60
+ def find_by_id(id)
61
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
62
+ if doc = collection.find_first(criteria)
63
+ new(doc)
64
+ end
65
+ end
66
+
67
+ def count(conditions={})
68
+ collection.count(FinderOptions.to_mongo_criteria(conditions))
69
+ end
70
+
71
+ def create(*docs)
72
+ instances = []
73
+ docs = [{}] if docs.blank?
74
+ docs.flatten.each do |attrs|
75
+ doc = new(attrs); doc.save
76
+ instances << doc
77
+ end
78
+ instances.size == 1 ? instances[0] : instances
79
+ end
80
+
81
+ # For updating single document
82
+ # Person.update(1, {:foo => 'bar'})
83
+ #
84
+ # For updating multiple documents at once:
85
+ # Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
86
+ def update(*args)
87
+ updating_multiple = args.length == 1
88
+ if updating_multiple
89
+ update_multiple(args[0])
90
+ else
91
+ id, attributes = args
92
+ update_single(id, attributes)
93
+ end
94
+ end
95
+
96
+ def delete(*ids)
97
+ criteria = FinderOptions.to_mongo_criteria(:_id => ids.flatten)
98
+ collection.remove(criteria)
99
+ end
100
+
101
+ def delete_all(conditions={})
102
+ criteria = FinderOptions.to_mongo_criteria(conditions)
103
+ collection.remove(criteria)
104
+ end
105
+
106
+ def destroy(*ids)
107
+ find_some(ids.flatten).each(&:destroy)
108
+ end
109
+
110
+ def destroy_all(conditions={})
111
+ find(:all, :conditions => conditions).each(&:destroy)
112
+ end
113
+
114
+ def connection(mongo_connection=nil)
115
+ if mongo_connection.nil?
116
+ @connection ||= MongoMapper.connection
117
+ else
118
+ @connection = mongo_connection
119
+ end
120
+ @connection
121
+ end
122
+
123
+ def database(name=nil)
124
+ if name.nil?
125
+ @database ||= MongoMapper.database
126
+ else
127
+ @database = MongoMapper.database(name)
128
+ end
129
+ @database
130
+ end
131
+
132
+ def collection(name=nil)
133
+ if name.nil?
134
+ @collection ||= database.collection(self.to_s.demodulize.tableize)
135
+ else
136
+ @collection = database.collection(name)
137
+ end
138
+ @collection
139
+ end
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
+
148
+ def validates_uniqueness_of(*args)
149
+ add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
150
+ end
151
+
152
+ def validates_exclusion_of(*args)
153
+ add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
154
+ end
155
+
156
+ def validates_inclusion_of(*args)
157
+ add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
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
174
+
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
180
+
181
+ def find_first(options)
182
+ options.merge!(:limit => 1)
183
+ find_every({:order => '$natural asc'}.merge(options))[0]
184
+ end
185
+
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
192
+
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(',')
204
+ end
205
+
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
214
+
215
+ def find_one(id, options={})
216
+ if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
217
+ doc
218
+ else
219
+ raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
220
+ end
221
+ end
222
+
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
257
+ end
258
+
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
263
+
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
277
+ end
278
+ end
279
+
280
+ module InstanceMethods
281
+ def collection
282
+ self.class.collection
283
+ end
284
+
285
+ def new?
286
+ read_attribute('_id').blank? || using_custom_id?
287
+ end
288
+
289
+ def save
290
+ create_or_update
291
+ end
292
+
293
+ def save!
294
+ create_or_update || raise(DocumentNotValid.new(self))
295
+ end
296
+
297
+ def update_attributes(attrs={})
298
+ self.attributes = attrs
299
+ save
300
+ end
301
+
302
+ def destroy
303
+ return false if frozen?
304
+
305
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
306
+ collection.remove(criteria) unless new?
307
+ freeze
308
+ end
309
+
310
+ private
311
+ def create_or_update
312
+ result = new? ? create : update
313
+ result != false
314
+ end
315
+
316
+ def create
317
+ assign_id
318
+ save_to_collection
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
326
+
327
+ def update
328
+ save_to_collection
329
+ end
330
+
331
+ # collection.save returns mongoid
332
+ def save_to_collection
333
+ clear_custom_id_flag
334
+ collection.save(attributes)
335
+ end
336
+
337
+ def update_timestamps
338
+ now = Time.now.utc
339
+ write_attribute('created_at', now) if new?
340
+ write_attribute('updated_at', now)
341
+ end
342
+
343
+ def clear_custom_id_flag
344
+ @using_custom_id = nil
345
+ end
346
+ end
347
+ end # Document
348
+ end # MongoMapper