mrkurt-mongo_mapper 0.6.8
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.
- data/.gitignore +10 -0
- data/LICENSE +20 -0
- data/README.rdoc +38 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/mmconsole +60 -0
- data/lib/mongo_mapper.rb +139 -0
- data/lib/mongo_mapper/associations.rb +72 -0
- data/lib/mongo_mapper/associations/base.rb +113 -0
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
- data/lib/mongo_mapper/associations/collection.rb +19 -0
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongo_mapper/associations/one_proxy.rb +61 -0
- data/lib/mongo_mapper/associations/proxy.rb +111 -0
- data/lib/mongo_mapper/callbacks.rb +61 -0
- data/lib/mongo_mapper/dirty.rb +117 -0
- data/lib/mongo_mapper/document.rb +496 -0
- data/lib/mongo_mapper/dynamic_finder.rb +74 -0
- data/lib/mongo_mapper/embedded_document.rb +380 -0
- data/lib/mongo_mapper/finder_options.rb +145 -0
- data/lib/mongo_mapper/key.rb +36 -0
- data/lib/mongo_mapper/mongo_mapper.rb +125 -0
- data/lib/mongo_mapper/pagination.rb +66 -0
- data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +28 -0
- data/lib/mongo_mapper/serialization.rb +54 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +48 -0
- data/lib/mongo_mapper/support.rb +192 -0
- data/lib/mongo_mapper/validations.rb +39 -0
- data/mongo_mapper.gemspec +173 -0
- data/specs.watchr +30 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +91 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
- data/test/functional/associations/test_many_documents_proxy.rb +477 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
- data/test/functional/associations/test_one_proxy.rb +131 -0
- data/test/functional/test_associations.rb +44 -0
- data/test/functional/test_binary.rb +33 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_dirty.rb +159 -0
- data/test/functional/test_document.rb +1198 -0
- data/test/functional/test_embedded_document.rb +135 -0
- data/test/functional/test_logger.rb +20 -0
- data/test/functional/test_modifiers.rb +242 -0
- data/test/functional/test_pagination.rb +95 -0
- data/test/functional/test_rails_compatibility.rb +25 -0
- data/test/functional/test_string_id_compatibility.rb +72 -0
- data/test/functional/test_validations.rb +361 -0
- data/test/models.rb +271 -0
- data/test/support/custom_matchers.rb +55 -0
- data/test/support/timing.rb +16 -0
- data/test/test_helper.rb +27 -0
- data/test/unit/associations/test_base.rb +182 -0
- data/test/unit/associations/test_proxy.rb +91 -0
- data/test/unit/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_document.rb +236 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +709 -0
- data/test/unit/test_finder_options.rb +325 -0
- data/test/unit/test_key.rb +172 -0
- data/test/unit/test_mongo_mapper.rb +65 -0
- data/test/unit/test_pagination.rb +119 -0
- data/test/unit/test_rails_compatibility.rb +52 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +346 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- metadata +239 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Callbacks
|
3
|
+
def self.included(model)
|
4
|
+
model.class_eval do
|
5
|
+
include ActiveSupport::Callbacks
|
6
|
+
|
7
|
+
define_callbacks(
|
8
|
+
:before_save, :after_save,
|
9
|
+
:before_create, :after_create,
|
10
|
+
:before_update, :after_update,
|
11
|
+
:before_validation, :after_validation,
|
12
|
+
:before_validation_on_create, :after_validation_on_create,
|
13
|
+
:before_validation_on_update, :after_validation_on_update,
|
14
|
+
:before_destroy, :after_destroy
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid?
|
20
|
+
action = new? ? 'create' : 'update'
|
21
|
+
|
22
|
+
run_callbacks(:before_validation)
|
23
|
+
run_callbacks("before_validation_on_#{action}".to_sym)
|
24
|
+
result = super
|
25
|
+
run_callbacks("after_validation_on_#{action}".to_sym)
|
26
|
+
run_callbacks(:after_validation)
|
27
|
+
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy
|
32
|
+
run_callbacks(:before_destroy)
|
33
|
+
result = super
|
34
|
+
run_callbacks(:after_destroy)
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def create_or_update(*args)
|
40
|
+
run_callbacks(:before_save)
|
41
|
+
if result = super
|
42
|
+
run_callbacks(:after_save)
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def create(*args)
|
48
|
+
run_callbacks(:before_create)
|
49
|
+
result = super
|
50
|
+
run_callbacks(:after_create)
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def update(*args)
|
55
|
+
run_callbacks(:before_update)
|
56
|
+
result = super
|
57
|
+
run_callbacks(:after_update)
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Dirty
|
3
|
+
DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
|
4
|
+
|
5
|
+
def method_missing(method, *args, &block)
|
6
|
+
if method.to_s =~ /(_changed\?|_change|_will_change!|_was)$/
|
7
|
+
method_suffix = $1
|
8
|
+
key = method.to_s.gsub(method_suffix, '')
|
9
|
+
|
10
|
+
if key_names.include?(key)
|
11
|
+
case method_suffix
|
12
|
+
when '_changed?'
|
13
|
+
key_changed?(key)
|
14
|
+
when '_change'
|
15
|
+
key_change(key)
|
16
|
+
when '_will_change!'
|
17
|
+
key_will_change!(key)
|
18
|
+
when '_was'
|
19
|
+
key_was(key)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def changed?
|
30
|
+
!changed_keys.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def changed
|
34
|
+
changed_keys.keys
|
35
|
+
end
|
36
|
+
|
37
|
+
def changes
|
38
|
+
changed.inject({}) { |h, attribute| h[attribute] = key_change(attribute); h }
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(attrs={})
|
42
|
+
super(attrs)
|
43
|
+
changed_keys.clear unless new?
|
44
|
+
end
|
45
|
+
|
46
|
+
def save(*args)
|
47
|
+
if status = super
|
48
|
+
changed_keys.clear
|
49
|
+
end
|
50
|
+
status
|
51
|
+
end
|
52
|
+
|
53
|
+
def save!(*args)
|
54
|
+
status = super
|
55
|
+
changed_keys.clear
|
56
|
+
status
|
57
|
+
end
|
58
|
+
|
59
|
+
def reload(*args)
|
60
|
+
record = super
|
61
|
+
changed_keys.clear
|
62
|
+
record
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def clone_key_value(attribute_name)
|
67
|
+
value = send(:read_attribute, attribute_name)
|
68
|
+
value.duplicable? ? value.clone : value
|
69
|
+
rescue TypeError, NoMethodError
|
70
|
+
value
|
71
|
+
end
|
72
|
+
|
73
|
+
def changed_keys
|
74
|
+
@changed_keys ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def key_changed?(attribute)
|
78
|
+
changed_keys.include?(attribute)
|
79
|
+
end
|
80
|
+
|
81
|
+
def key_change(attribute)
|
82
|
+
[changed_keys[attribute], __send__(attribute)] if key_changed?(attribute)
|
83
|
+
end
|
84
|
+
|
85
|
+
def key_was(attribute)
|
86
|
+
key_changed?(attribute) ? changed_keys[attribute] : __send__(attribute)
|
87
|
+
end
|
88
|
+
|
89
|
+
def key_will_change!(attribute)
|
90
|
+
changed_keys[attribute] = clone_key_value(attribute)
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_attribute(attribute, value)
|
94
|
+
attribute = attribute.to_s
|
95
|
+
|
96
|
+
if changed_keys.include?(attribute)
|
97
|
+
old = changed_keys[attribute]
|
98
|
+
changed_keys.delete(attribute) unless value_changed?(attribute, old, value)
|
99
|
+
else
|
100
|
+
old = clone_key_value(attribute)
|
101
|
+
changed_keys[attribute] = old if value_changed?(attribute, old, value)
|
102
|
+
end
|
103
|
+
|
104
|
+
super(attribute, value)
|
105
|
+
end
|
106
|
+
|
107
|
+
def value_changed?(key_name, old, value)
|
108
|
+
key = _keys[key_name]
|
109
|
+
|
110
|
+
if key.number? && value.blank?
|
111
|
+
value = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
old != value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,496 @@
|
|
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 Callbacks
|
10
|
+
include Dirty
|
11
|
+
include RailsCompatibility::Document
|
12
|
+
extend Validations::Macros
|
13
|
+
extend ClassMethods
|
14
|
+
extend Finders
|
15
|
+
|
16
|
+
def self.per_page
|
17
|
+
25
|
18
|
+
end unless respond_to?(:per_page)
|
19
|
+
end
|
20
|
+
|
21
|
+
extra_extensions.each { |extension| model.extend(extension) }
|
22
|
+
extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
|
23
|
+
|
24
|
+
descendants << model
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.descendants
|
28
|
+
@descendants ||= Set.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.append_extensions(*extensions)
|
32
|
+
extra_extensions.concat extensions
|
33
|
+
|
34
|
+
# Add the extension to existing descendants
|
35
|
+
descendants.each do |model|
|
36
|
+
extensions.each { |extension| model.extend(extension) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
def self.extra_extensions
|
42
|
+
@extra_extensions ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.append_inclusions(*inclusions)
|
46
|
+
extra_inclusions.concat inclusions
|
47
|
+
|
48
|
+
# Add the inclusion to existing descendants
|
49
|
+
descendants.each do |model|
|
50
|
+
inclusions.each { |inclusion| model.send :include, inclusion }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
def self.extra_inclusions
|
56
|
+
@extra_inclusions ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
def key(*args)
|
61
|
+
key = super
|
62
|
+
create_indexes_for(key)
|
63
|
+
key
|
64
|
+
end
|
65
|
+
|
66
|
+
def ensure_index(name_or_array, options={})
|
67
|
+
keys_to_index = if name_or_array.is_a?(Array)
|
68
|
+
name_or_array.map { |pair| [pair[0], pair[1]] }
|
69
|
+
else
|
70
|
+
name_or_array
|
71
|
+
end
|
72
|
+
|
73
|
+
MongoMapper.ensure_index(self, keys_to_index, options)
|
74
|
+
end
|
75
|
+
|
76
|
+
def find!(*args)
|
77
|
+
options = args.extract_options!
|
78
|
+
case args.first
|
79
|
+
when :first then first(options)
|
80
|
+
when :last then last(options)
|
81
|
+
when :all then find_every(options)
|
82
|
+
when Array then find_some(args, options)
|
83
|
+
else
|
84
|
+
case args.size
|
85
|
+
when 0
|
86
|
+
raise DocumentNotFound, "Couldn't find without an ID"
|
87
|
+
when 1
|
88
|
+
find_one!(options.merge({:_id => args[0]}))
|
89
|
+
else
|
90
|
+
find_some(args, options)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def find(*args)
|
96
|
+
find!(*args)
|
97
|
+
rescue DocumentNotFound
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def paginate(options)
|
102
|
+
per_page = options.delete(:per_page) || self.per_page
|
103
|
+
page = options.delete(:page)
|
104
|
+
total_entries = count(options)
|
105
|
+
pagination = Pagination::PaginationProxy.new(total_entries, page, per_page)
|
106
|
+
|
107
|
+
options.merge!(:limit => pagination.limit, :skip => pagination.skip)
|
108
|
+
pagination.subject = find_every(options)
|
109
|
+
pagination
|
110
|
+
end
|
111
|
+
|
112
|
+
def first(options={})
|
113
|
+
find_one(options)
|
114
|
+
end
|
115
|
+
|
116
|
+
def last(options={})
|
117
|
+
raise ':order option must be provided when using last' if options[:order].blank?
|
118
|
+
find_one(options.merge(:order => invert_order_clause(options[:order])))
|
119
|
+
end
|
120
|
+
|
121
|
+
def all(options={})
|
122
|
+
find_every(options)
|
123
|
+
end
|
124
|
+
|
125
|
+
def find_by_id(id)
|
126
|
+
find_one(:_id => id)
|
127
|
+
end
|
128
|
+
|
129
|
+
def count(options={})
|
130
|
+
collection.find(to_criteria(options)).count
|
131
|
+
end
|
132
|
+
|
133
|
+
def exists?(options={})
|
134
|
+
!count(options).zero?
|
135
|
+
end
|
136
|
+
|
137
|
+
def create(*docs)
|
138
|
+
initialize_each(*docs) { |doc| doc.save }
|
139
|
+
end
|
140
|
+
|
141
|
+
def create!(*docs)
|
142
|
+
initialize_each(*docs) { |doc| doc.save! }
|
143
|
+
end
|
144
|
+
|
145
|
+
def update(*args)
|
146
|
+
if args.length == 1
|
147
|
+
update_multiple(args[0])
|
148
|
+
else
|
149
|
+
id, attributes = args
|
150
|
+
update_single(id, attributes)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def delete(*ids)
|
155
|
+
collection.remove(to_criteria(:_id => ids.flatten))
|
156
|
+
end
|
157
|
+
|
158
|
+
def delete_all(options={})
|
159
|
+
collection.remove(to_criteria(options))
|
160
|
+
end
|
161
|
+
|
162
|
+
def destroy(*ids)
|
163
|
+
find_some(ids.flatten).each(&:destroy)
|
164
|
+
end
|
165
|
+
|
166
|
+
def destroy_all(options={})
|
167
|
+
all(options).each(&:destroy)
|
168
|
+
end
|
169
|
+
|
170
|
+
def increment(*args)
|
171
|
+
modifier_update('$inc', args)
|
172
|
+
end
|
173
|
+
|
174
|
+
def decrement(*args)
|
175
|
+
criteria, keys = criteria_and_keys_from_args(args)
|
176
|
+
values, to_decrement = keys.values, {}
|
177
|
+
keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
|
178
|
+
collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
|
179
|
+
end
|
180
|
+
|
181
|
+
def set(*args)
|
182
|
+
modifier_update('$set', args)
|
183
|
+
end
|
184
|
+
|
185
|
+
def push(*args)
|
186
|
+
modifier_update('$push', args)
|
187
|
+
end
|
188
|
+
|
189
|
+
def push_all(*args)
|
190
|
+
modifier_update('$pushAll', args)
|
191
|
+
end
|
192
|
+
|
193
|
+
def push_uniq(*args)
|
194
|
+
criteria, keys = criteria_and_keys_from_args(args)
|
195
|
+
keys.each { |key, value | criteria[key] = {'$ne' => value} }
|
196
|
+
collection.update(criteria, {'$push' => keys}, :multi => true)
|
197
|
+
end
|
198
|
+
|
199
|
+
def pull(*args)
|
200
|
+
modifier_update('$pull', args)
|
201
|
+
end
|
202
|
+
|
203
|
+
def pull_all(*args)
|
204
|
+
modifier_update('$pullAll', args)
|
205
|
+
end
|
206
|
+
|
207
|
+
def modifier_update(modifier, args)
|
208
|
+
criteria, keys = criteria_and_keys_from_args(args)
|
209
|
+
modifiers = {modifier => keys}
|
210
|
+
collection.update(criteria, modifiers, :multi => true)
|
211
|
+
end
|
212
|
+
private :modifier_update
|
213
|
+
|
214
|
+
def criteria_and_keys_from_args(args)
|
215
|
+
keys = args.pop
|
216
|
+
criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
|
217
|
+
[to_criteria(criteria), keys]
|
218
|
+
end
|
219
|
+
private :criteria_and_keys_from_args
|
220
|
+
|
221
|
+
def connection(mongo_connection=nil)
|
222
|
+
if mongo_connection.nil?
|
223
|
+
@connection ||= MongoMapper.connection
|
224
|
+
else
|
225
|
+
@connection = mongo_connection
|
226
|
+
end
|
227
|
+
@connection
|
228
|
+
end
|
229
|
+
|
230
|
+
def set_database_name(name)
|
231
|
+
@database_name = name
|
232
|
+
end
|
233
|
+
|
234
|
+
def database_name
|
235
|
+
@database_name
|
236
|
+
end
|
237
|
+
|
238
|
+
def database
|
239
|
+
if database_name.nil?
|
240
|
+
MongoMapper.database
|
241
|
+
else
|
242
|
+
connection.db(database_name)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def set_collection_name(name)
|
247
|
+
@collection_name = name
|
248
|
+
end
|
249
|
+
|
250
|
+
def collection_name
|
251
|
+
@collection_name ||= self.to_s.tableize.gsub(/\//, '.')
|
252
|
+
end
|
253
|
+
|
254
|
+
def collection
|
255
|
+
database.collection(collection_name)
|
256
|
+
end
|
257
|
+
|
258
|
+
def timestamps!
|
259
|
+
key :created_at, Time
|
260
|
+
key :updated_at, Time
|
261
|
+
class_eval { before_save :update_timestamps }
|
262
|
+
end
|
263
|
+
|
264
|
+
def locking!(version_field=:_rev)
|
265
|
+
@lock_version_field = version_field
|
266
|
+
key version_field, String if version_field
|
267
|
+
end
|
268
|
+
|
269
|
+
def lock_version_field
|
270
|
+
@lock_version_field ||= false
|
271
|
+
end
|
272
|
+
|
273
|
+
def userstamps!
|
274
|
+
key :creator_id, ObjectId
|
275
|
+
key :updater_id, ObjectId
|
276
|
+
belongs_to :creator, :class_name => 'User'
|
277
|
+
belongs_to :updater, :class_name => 'User'
|
278
|
+
end
|
279
|
+
|
280
|
+
def single_collection_inherited?
|
281
|
+
keys.has_key?('_type') && single_collection_inherited_superclass?
|
282
|
+
end
|
283
|
+
|
284
|
+
def single_collection_inherited_superclass?
|
285
|
+
superclass.respond_to?(:keys) && superclass.keys.has_key?('_type')
|
286
|
+
end
|
287
|
+
|
288
|
+
private
|
289
|
+
def create_indexes_for(key)
|
290
|
+
ensure_index key.name if key.options[:index]
|
291
|
+
end
|
292
|
+
|
293
|
+
def initialize_each(*docs)
|
294
|
+
instances = []
|
295
|
+
docs = [{}] if docs.blank?
|
296
|
+
docs.flatten.each do |attrs|
|
297
|
+
doc = initialize_doc(attrs)
|
298
|
+
yield(doc)
|
299
|
+
instances << doc
|
300
|
+
end
|
301
|
+
instances.size == 1 ? instances[0] : instances
|
302
|
+
end
|
303
|
+
|
304
|
+
def find_every(options)
|
305
|
+
criteria, options = to_finder_options(options)
|
306
|
+
collection.find(criteria, options).to_a.map do |doc|
|
307
|
+
initialize_doc(doc)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def find_some(ids, options={})
|
312
|
+
ids = ids.flatten.compact.uniq
|
313
|
+
documents = find_every(options.merge(:_id => ids))
|
314
|
+
|
315
|
+
if ids.size == documents.size
|
316
|
+
documents
|
317
|
+
else
|
318
|
+
raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def find_one(options={})
|
323
|
+
criteria, options = to_finder_options(options)
|
324
|
+
if doc = collection.find_one(criteria, options)
|
325
|
+
initialize_doc(doc)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def find_one!(options={})
|
330
|
+
find_one(options) || raise(DocumentNotFound, "Document match #{options.inspect} does not exist in #{collection.name} collection")
|
331
|
+
end
|
332
|
+
|
333
|
+
def invert_order_clause(order)
|
334
|
+
order.split(',').map do |order_segment|
|
335
|
+
if order_segment =~ /\sasc/i
|
336
|
+
order_segment.sub /\sasc/i, ' desc'
|
337
|
+
elsif order_segment =~ /\sdesc/i
|
338
|
+
order_segment.sub /\sdesc/i, ' asc'
|
339
|
+
else
|
340
|
+
"#{order_segment.strip} desc"
|
341
|
+
end
|
342
|
+
end.join(',')
|
343
|
+
end
|
344
|
+
|
345
|
+
def update_single(id, attrs)
|
346
|
+
if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
|
347
|
+
raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
|
348
|
+
end
|
349
|
+
|
350
|
+
doc = find(id)
|
351
|
+
doc.update_attributes(attrs)
|
352
|
+
doc
|
353
|
+
end
|
354
|
+
|
355
|
+
def update_multiple(docs)
|
356
|
+
unless docs.is_a?(Hash)
|
357
|
+
raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
|
358
|
+
end
|
359
|
+
|
360
|
+
instances = []
|
361
|
+
docs.each_pair { |id, attrs| instances << update(id, attrs) }
|
362
|
+
instances
|
363
|
+
end
|
364
|
+
|
365
|
+
def to_criteria(options={})
|
366
|
+
FinderOptions.new(self, options).criteria
|
367
|
+
end
|
368
|
+
|
369
|
+
def to_finder_options(options={})
|
370
|
+
FinderOptions.new(self, options).to_a
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
module InstanceMethods
|
375
|
+
def collection
|
376
|
+
self.class.collection
|
377
|
+
end
|
378
|
+
|
379
|
+
def database
|
380
|
+
self.class.database
|
381
|
+
end
|
382
|
+
|
383
|
+
def lock_version_field
|
384
|
+
self.class.lock_version_field
|
385
|
+
end
|
386
|
+
|
387
|
+
def new?
|
388
|
+
read_attribute('_id').blank? || using_custom_id?
|
389
|
+
end
|
390
|
+
|
391
|
+
def save(options={})
|
392
|
+
if options === false
|
393
|
+
ActiveSupport::Deprecation.warn "save with true/false is deprecated. You should now use :validate => true/false."
|
394
|
+
options = {:validate => false}
|
395
|
+
end
|
396
|
+
options.reverse_merge!(:validate => true)
|
397
|
+
perform_validations = options.delete(:validate)
|
398
|
+
!perform_validations || valid? ? create_or_update(options) : false
|
399
|
+
end
|
400
|
+
|
401
|
+
def save!
|
402
|
+
save || raise(DocumentNotValid.new(self))
|
403
|
+
end
|
404
|
+
|
405
|
+
def destroy
|
406
|
+
self.class.delete(id) unless new?
|
407
|
+
end
|
408
|
+
|
409
|
+
def delete
|
410
|
+
self.class.delete(id) unless new?
|
411
|
+
end
|
412
|
+
|
413
|
+
def reload
|
414
|
+
doc = self.class.find(_id)
|
415
|
+
self.class.associations.each { |name, assoc| send(name).reset if respond_to?(name) }
|
416
|
+
self.attributes = doc.attributes
|
417
|
+
self
|
418
|
+
end
|
419
|
+
|
420
|
+
private
|
421
|
+
def create_or_update(options={})
|
422
|
+
result = new? ? create(options) : update(options)
|
423
|
+
result != false
|
424
|
+
end
|
425
|
+
|
426
|
+
def create(options={})
|
427
|
+
assign_id
|
428
|
+
save_to_collection(options)
|
429
|
+
end
|
430
|
+
|
431
|
+
def assign_id
|
432
|
+
if read_attribute(:_id).blank?
|
433
|
+
write_attribute :_id, Mongo::ObjectID.new
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def prep_lock_version
|
438
|
+
if lock_version_field
|
439
|
+
old = read_attribute(lock_version_field)
|
440
|
+
v = Time.now.to_f
|
441
|
+
write_attribute lock_version_field, v
|
442
|
+
old
|
443
|
+
else
|
444
|
+
false
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def update(options={})
|
449
|
+
save_to_collection(options)
|
450
|
+
end
|
451
|
+
|
452
|
+
def save_to_collection(options={})
|
453
|
+
clear_custom_id_flag
|
454
|
+
current_version = prep_lock_version
|
455
|
+
|
456
|
+
safe = options.delete(:safe)
|
457
|
+
safe = MongoMapper.safe_mode if safe.nil?
|
458
|
+
|
459
|
+
selector = { :_id => read_attribute(:_id) }
|
460
|
+
upsert = true
|
461
|
+
|
462
|
+
if current_version
|
463
|
+
selector[lock_version_field] = current_version
|
464
|
+
safe = true
|
465
|
+
upsert = false
|
466
|
+
end
|
467
|
+
|
468
|
+
if new?
|
469
|
+
collection.insert(to_mongo, :safe => safe)
|
470
|
+
else
|
471
|
+
result = collection.update(selector, to_mongo, :upsert => upsert, :safe => safe)
|
472
|
+
|
473
|
+
if result.is_a? Array
|
474
|
+
if current_version && result[0][0]['updatedExisting'] == false
|
475
|
+
raise StaleDocumentError.new
|
476
|
+
end
|
477
|
+
elsif lock_version_field
|
478
|
+
#raise some error
|
479
|
+
end
|
480
|
+
|
481
|
+
selector[:_id]
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
def update_timestamps
|
486
|
+
now = Time.now.utc
|
487
|
+
write_attribute('created_at', now) if new? && read_attribute('created_at').blank?
|
488
|
+
write_attribute('updated_at', now)
|
489
|
+
end
|
490
|
+
|
491
|
+
def clear_custom_id_flag
|
492
|
+
@using_custom_id = nil
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end # Document
|
496
|
+
end # MongoMapper
|