mongo_mapper 0.7.0 → 0.7.1
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/README.rdoc +3 -1
- data/Rakefile +9 -12
- data/lib/mongo_mapper.rb +30 -10
- data/lib/mongo_mapper/document.rb +16 -74
- data/lib/mongo_mapper/embedded_document.rb +7 -1
- data/lib/mongo_mapper/plugins.rb +3 -0
- data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +1 -12
- data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +6 -1
- data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +4 -1
- data/lib/mongo_mapper/plugins/callbacks.rb +183 -12
- data/lib/mongo_mapper/plugins/keys.rb +17 -5
- data/lib/mongo_mapper/plugins/modifiers.rb +87 -0
- data/lib/mongo_mapper/plugins/pagination/proxy.rb +7 -3
- data/lib/mongo_mapper/plugins/protected.rb +1 -1
- data/lib/mongo_mapper/plugins/rails.rb +16 -8
- data/lib/mongo_mapper/plugins/serialization.rb +51 -81
- data/lib/mongo_mapper/plugins/timestamps.rb +21 -0
- data/lib/mongo_mapper/plugins/userstamps.rb +14 -0
- data/lib/mongo_mapper/query.rb +1 -1
- data/lib/mongo_mapper/version.rb +3 -0
- data/mongo_mapper.gemspec +22 -11
- data/test/active_model_lint_test.rb +11 -0
- data/test/functional/associations/test_in_array_proxy.rb +16 -0
- data/test/functional/associations/test_many_documents_proxy.rb +22 -0
- data/test/functional/test_callbacks.rb +104 -34
- data/test/functional/test_document.rb +70 -149
- data/test/functional/test_embedded_document.rb +39 -34
- data/test/functional/test_indexing.rb +44 -0
- data/test/functional/test_modifiers.rb +297 -227
- data/test/functional/test_protected.rb +11 -5
- data/test/functional/test_timestamps.rb +64 -0
- data/test/functional/test_userstamps.rb +28 -0
- data/test/support/timing.rb +1 -1
- data/test/unit/serializers/test_json_serializer.rb +30 -17
- data/test/unit/test_embedded_document.rb +15 -15
- data/test/unit/test_keys.rb +15 -11
- data/test/unit/test_mongo_mapper.rb +31 -1
- data/test/unit/test_pagination.rb +33 -0
- data/test/unit/test_query.rb +6 -0
- data/test/unit/test_serialization.rb +3 -3
- data/test/unit/test_support.rb +9 -5
- metadata +17 -6
- data/VERSION +0 -1
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= MongoMapper
|
2
2
|
|
3
|
-
|
3
|
+
A Ruby Object Mapper for Mongo.
|
4
4
|
|
5
5
|
Releases are tagged on github and released on gemcutter. Master is pushed to whenever I add a patch or a new feature, but I do not release a new gem version each time I push.
|
6
6
|
|
@@ -24,6 +24,8 @@ http://groups.google.com/group/mongomapper
|
|
24
24
|
To see if the problem you are having is a verified issue, you can see the MM pivotal tracker project:
|
25
25
|
http://www.pivotaltracker.com/projects/33576
|
26
26
|
|
27
|
+
There is no need to request to join the Pivotal Tracker project as I am only granting access to a select few (easier to keep things organized). If you have a problem, please use the mailing list. If I confirm it to be a bug, I am happy to add it to PT. Thanks!
|
28
|
+
|
27
29
|
== Copyright
|
28
30
|
|
29
31
|
Copyright (c) 2009 John Nunemaker. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
require 'jeweler'
|
4
|
-
|
5
|
-
require '
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/lib/mongo_mapper/version'
|
6
6
|
|
7
7
|
Jeweler::Tasks.new do |gem|
|
8
8
|
gem.name = "mongo_mapper"
|
9
|
-
gem.summary = %Q{
|
9
|
+
gem.summary = %Q{A Ruby Object Mapper for Mongo}
|
10
10
|
gem.email = "nunemaker@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/jnunemaker/mongomapper"
|
12
12
|
gem.authors = ["John Nunemaker"]
|
13
|
-
|
13
|
+
gem.version = MongoMapper::Version
|
14
|
+
|
14
15
|
gem.add_dependency('activesupport', '>= 2.3')
|
15
|
-
gem.add_dependency('mongo', '0.
|
16
|
-
gem.add_dependency('jnunemaker-validatable', '1.8.
|
17
|
-
|
16
|
+
gem.add_dependency('mongo', '0.19.1')
|
17
|
+
gem.add_dependency('jnunemaker-validatable', '1.8.3')
|
18
|
+
|
18
19
|
gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
|
19
20
|
gem.add_development_dependency('shoulda', '2.10.2')
|
20
21
|
gem.add_development_dependency('timecop', '0.3.1')
|
@@ -38,7 +39,7 @@ namespace :test do
|
|
38
39
|
test.pattern = 'test/unit/**/test_*.rb'
|
39
40
|
test.verbose = true
|
40
41
|
end
|
41
|
-
|
42
|
+
|
42
43
|
Rake::TestTask.new(:functionals) do |test|
|
43
44
|
test.libs << 'test'
|
44
45
|
test.ruby_opts << '-rubygems'
|
@@ -49,7 +50,3 @@ end
|
|
49
50
|
|
50
51
|
task :default => :test
|
51
52
|
task :test => :check_dependencies
|
52
|
-
|
53
|
-
YARD::Rake::YardocTask.new(:doc) do |t|
|
54
|
-
t.options = ["--legacy"] if RUBY_VERSION < "1.9.0"
|
55
|
-
end
|
data/lib/mongo_mapper.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'uri'
|
2
3
|
|
3
4
|
# if Gem is defined i'll assume you are using rubygems and lock specific versions
|
4
5
|
# call me crazy but a plain old require will just get the latest version you have installed
|
@@ -6,11 +7,11 @@ require 'set'
|
|
6
7
|
# if there is a better way to do this, please enlighten me!
|
7
8
|
if self.class.const_defined?(:Gem)
|
8
9
|
gem 'activesupport', '>= 2.3'
|
9
|
-
gem 'mongo', '0.
|
10
|
-
gem 'jnunemaker-validatable', '1.8.
|
10
|
+
gem 'mongo', '0.19.1'
|
11
|
+
gem 'jnunemaker-validatable', '1.8.3'
|
11
12
|
end
|
12
13
|
|
13
|
-
require 'active_support'
|
14
|
+
require 'active_support/all'
|
14
15
|
require 'mongo'
|
15
16
|
require 'validatable'
|
16
17
|
|
@@ -24,6 +25,9 @@ module MongoMapper
|
|
24
25
|
# raised when document expected but not found
|
25
26
|
class DocumentNotFound < MongoMapperError; end
|
26
27
|
|
28
|
+
# raised when trying to connect using uri with incorrect scheme
|
29
|
+
class InvalidScheme < MongoMapperError; end
|
30
|
+
|
27
31
|
# raised when document not valid and using !
|
28
32
|
class DocumentNotValid < MongoMapperError
|
29
33
|
def initialize(document)
|
@@ -70,13 +74,28 @@ module MongoMapper
|
|
70
74
|
@@config
|
71
75
|
end
|
72
76
|
|
77
|
+
# @api private
|
78
|
+
def self.config_for_environment(environment)
|
79
|
+
env = config[environment]
|
80
|
+
return env if env['uri'].blank?
|
81
|
+
|
82
|
+
uri = URI.parse(env['uri'])
|
83
|
+
raise InvalidScheme.new('must be mongodb') unless uri.scheme == 'mongodb'
|
84
|
+
{
|
85
|
+
'host' => uri.host,
|
86
|
+
'port' => uri.port,
|
87
|
+
'database' => uri.path.gsub(/^\//, ''),
|
88
|
+
'username' => uri.user,
|
89
|
+
'password' => uri.password,
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
73
93
|
def self.connect(environment, options={})
|
74
94
|
raise 'Set config before connecting. MongoMapper.config = {...}' if config.blank?
|
75
|
-
|
76
|
-
MongoMapper.
|
77
|
-
|
78
|
-
|
79
|
-
end
|
95
|
+
env = config_for_environment(environment)
|
96
|
+
MongoMapper.connection = Mongo::Connection.new(env['host'], env['port'], options)
|
97
|
+
MongoMapper.database = env['database']
|
98
|
+
MongoMapper.database.authenticate(env['username'], env['password']) if env['username'] && env['password']
|
80
99
|
end
|
81
100
|
|
82
101
|
def self.setup(config, environment, options={})
|
@@ -108,11 +127,12 @@ module MongoMapper
|
|
108
127
|
def self.normalize_object_id(value)
|
109
128
|
value.is_a?(String) ? Mongo::ObjectID.from_string(value) : value
|
110
129
|
end
|
111
|
-
|
130
|
+
|
112
131
|
autoload :Query, 'mongo_mapper/query'
|
113
132
|
autoload :Document, 'mongo_mapper/document'
|
114
133
|
autoload :EmbeddedDocument, 'mongo_mapper/embedded_document'
|
134
|
+
autoload :Version, 'mongo_mapper/version'
|
115
135
|
end
|
116
136
|
|
117
137
|
require 'mongo_mapper/support'
|
118
|
-
require 'mongo_mapper/plugins'
|
138
|
+
require 'mongo_mapper/plugins'
|
@@ -17,13 +17,16 @@ module MongoMapper
|
|
17
17
|
plugin Plugins::Keys
|
18
18
|
plugin Plugins::Dirty # for now dirty needs to be after keys
|
19
19
|
plugin Plugins::Logger
|
20
|
+
plugin Plugins::Modifiers
|
20
21
|
plugin Plugins::Pagination
|
21
22
|
plugin Plugins::Protected
|
22
23
|
plugin Plugins::Rails
|
23
24
|
plugin Plugins::Serialization
|
25
|
+
plugin Plugins::Timestamps
|
26
|
+
plugin Plugins::Userstamps
|
24
27
|
plugin Plugins::Validations
|
25
28
|
plugin Plugins::Callbacks # for now callbacks needs to be after validations
|
26
|
-
|
29
|
+
|
27
30
|
extend Plugins::Validations::DocumentMacros
|
28
31
|
end
|
29
32
|
|
@@ -143,47 +146,6 @@ module MongoMapper
|
|
143
146
|
find_each(options) { |document| document.destroy }
|
144
147
|
end
|
145
148
|
|
146
|
-
def increment(*args)
|
147
|
-
modifier_update('$inc', args)
|
148
|
-
end
|
149
|
-
|
150
|
-
def decrement(*args)
|
151
|
-
criteria, keys = criteria_and_keys_from_args(args)
|
152
|
-
values, to_decrement = keys.values, {}
|
153
|
-
keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
|
154
|
-
collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
|
155
|
-
end
|
156
|
-
|
157
|
-
def set(*args)
|
158
|
-
modifier_update('$set', args)
|
159
|
-
end
|
160
|
-
|
161
|
-
def push(*args)
|
162
|
-
modifier_update('$push', args)
|
163
|
-
end
|
164
|
-
|
165
|
-
def push_all(*args)
|
166
|
-
modifier_update('$pushAll', args)
|
167
|
-
end
|
168
|
-
|
169
|
-
def push_uniq(*args)
|
170
|
-
criteria, keys = criteria_and_keys_from_args(args)
|
171
|
-
keys.each { |key, value | criteria[key] = {'$ne' => value} }
|
172
|
-
collection.update(criteria, {'$push' => keys}, :multi => true)
|
173
|
-
end
|
174
|
-
|
175
|
-
def pull(*args)
|
176
|
-
modifier_update('$pull', args)
|
177
|
-
end
|
178
|
-
|
179
|
-
def pull_all(*args)
|
180
|
-
modifier_update('$pullAll', args)
|
181
|
-
end
|
182
|
-
|
183
|
-
def pop(*args)
|
184
|
-
modifier_update('$pop', args)
|
185
|
-
end
|
186
|
-
|
187
149
|
def embeddable?
|
188
150
|
false
|
189
151
|
end
|
@@ -200,7 +162,7 @@ module MongoMapper
|
|
200
162
|
def set_database_name(name)
|
201
163
|
@database_name = name
|
202
164
|
end
|
203
|
-
|
165
|
+
|
204
166
|
def database_name
|
205
167
|
@database_name
|
206
168
|
end
|
@@ -225,19 +187,6 @@ module MongoMapper
|
|
225
187
|
database.collection(collection_name)
|
226
188
|
end
|
227
189
|
|
228
|
-
def timestamps!
|
229
|
-
key :created_at, Time
|
230
|
-
key :updated_at, Time
|
231
|
-
class_eval { before_save :update_timestamps }
|
232
|
-
end
|
233
|
-
|
234
|
-
def userstamps!
|
235
|
-
key :creator_id, ObjectId
|
236
|
-
key :updater_id, ObjectId
|
237
|
-
belongs_to :creator, :class_name => 'User'
|
238
|
-
belongs_to :updater, :class_name => 'User'
|
239
|
-
end
|
240
|
-
|
241
190
|
def single_collection_inherited?
|
242
191
|
keys.key?(:_type) && single_collection_inherited_superclass?
|
243
192
|
end
|
@@ -258,18 +207,6 @@ module MongoMapper
|
|
258
207
|
instances.size == 1 ? instances[0] : instances
|
259
208
|
end
|
260
209
|
|
261
|
-
def modifier_update(modifier, args)
|
262
|
-
criteria, keys = criteria_and_keys_from_args(args)
|
263
|
-
modifiers = {modifier => keys}
|
264
|
-
collection.update(criteria, modifiers, :multi => true)
|
265
|
-
end
|
266
|
-
|
267
|
-
def criteria_and_keys_from_args(args)
|
268
|
-
keys = args.pop
|
269
|
-
criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
|
270
|
-
[to_criteria(criteria), keys]
|
271
|
-
end
|
272
|
-
|
273
210
|
def assert_no_first_last_or_all(args)
|
274
211
|
if args[0] == :first || args[0] == :last || args[0] == :all
|
275
212
|
raise ArgumentError, "#{self}.find(:#{args}) is no longer supported, use #{self}.#{args} instead."
|
@@ -374,9 +311,14 @@ module MongoMapper
|
|
374
311
|
end
|
375
312
|
|
376
313
|
def delete
|
314
|
+
@_destroyed = true
|
377
315
|
self.class.delete(id) unless new?
|
378
316
|
end
|
379
317
|
|
318
|
+
def destroyed?
|
319
|
+
@_destroyed == true
|
320
|
+
end
|
321
|
+
|
380
322
|
def reload
|
381
323
|
if attrs = collection.find_one({:_id => _id})
|
382
324
|
self.class.associations.each { |name, assoc| send(name).reset if respond_to?(name) }
|
@@ -387,6 +329,12 @@ module MongoMapper
|
|
387
329
|
end
|
388
330
|
end
|
389
331
|
|
332
|
+
# Used by embedded docs to find root easily without if/respond_to? stuff.
|
333
|
+
# Documents are always root documents.
|
334
|
+
def _root_document
|
335
|
+
self
|
336
|
+
end
|
337
|
+
|
390
338
|
private
|
391
339
|
def create_or_update(options={})
|
392
340
|
result = new? ? create(options) : update(options)
|
@@ -406,12 +354,6 @@ module MongoMapper
|
|
406
354
|
@new = false
|
407
355
|
collection.save(to_mongo, :safe => safe)
|
408
356
|
end
|
409
|
-
|
410
|
-
def update_timestamps
|
411
|
-
now = Time.now.utc
|
412
|
-
self[:created_at] = now if new? && !created_at?
|
413
|
-
self[:updated_at] = now
|
414
|
-
end
|
415
357
|
end
|
416
358
|
end # Document
|
417
359
|
end # MongoMapper
|
@@ -19,8 +19,9 @@ module MongoMapper
|
|
19
19
|
plugin Plugins::Rails
|
20
20
|
plugin Plugins::Serialization
|
21
21
|
plugin Plugins::Validations
|
22
|
+
plugin Plugins::Callbacks
|
22
23
|
|
23
|
-
|
24
|
+
attr_reader :_root_document, :_parent_document
|
24
25
|
end
|
25
26
|
|
26
27
|
super
|
@@ -50,6 +51,11 @@ module MongoMapper
|
|
50
51
|
end
|
51
52
|
result
|
52
53
|
end
|
54
|
+
|
55
|
+
def _parent_document=(value)
|
56
|
+
@_root_document = value._root_document
|
57
|
+
@_parent_document = value
|
58
|
+
end
|
53
59
|
end # InstanceMethods
|
54
60
|
end # EmbeddedDocument
|
55
61
|
end # MongoMapper
|
data/lib/mongo_mapper/plugins.rb
CHANGED
@@ -20,9 +20,12 @@ module MongoMapper
|
|
20
20
|
autoload :Inspect, 'mongo_mapper/plugins/inspect'
|
21
21
|
autoload :Keys, 'mongo_mapper/plugins/keys'
|
22
22
|
autoload :Logger, 'mongo_mapper/plugins/logger'
|
23
|
+
autoload :Modifiers, 'mongo_mapper/plugins/modifiers'
|
23
24
|
autoload :Protected, 'mongo_mapper/plugins/protected'
|
24
25
|
autoload :Rails, 'mongo_mapper/plugins/rails'
|
25
26
|
autoload :Serialization, 'mongo_mapper/plugins/serialization'
|
27
|
+
autoload :Timestamps, 'mongo_mapper/plugins/timestamps'
|
28
|
+
autoload :Userstamps, 'mongo_mapper/plugins/userstamps'
|
26
29
|
autoload :Validations, 'mongo_mapper/plugins/validations'
|
27
30
|
end
|
28
31
|
end
|
@@ -30,19 +30,8 @@ module MongoMapper
|
|
30
30
|
alias_method :concat, :<<
|
31
31
|
|
32
32
|
private
|
33
|
-
def _root_document
|
34
|
-
if owner.respond_to?(:_root_document)
|
35
|
-
owner._root_document
|
36
|
-
else
|
37
|
-
owner
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
33
|
def assign_references(*docs)
|
42
|
-
docs.each
|
43
|
-
doc._root_document = _root_document
|
44
|
-
doc._parent_document = owner
|
45
|
-
end
|
34
|
+
docs.each { |doc| doc._parent_document = owner }
|
46
35
|
end
|
47
36
|
end
|
48
37
|
end
|
@@ -79,6 +79,7 @@ module MongoMapper
|
|
79
79
|
unless doc.new?
|
80
80
|
ids << doc.id
|
81
81
|
owner.save
|
82
|
+
reset
|
82
83
|
end
|
83
84
|
doc
|
84
85
|
end
|
@@ -88,6 +89,7 @@ module MongoMapper
|
|
88
89
|
unless doc.new?
|
89
90
|
ids << doc.id
|
90
91
|
owner.save
|
92
|
+
reset
|
91
93
|
end
|
92
94
|
doc
|
93
95
|
end
|
@@ -123,7 +125,10 @@ module MongoMapper
|
|
123
125
|
end
|
124
126
|
|
125
127
|
def scoped_ids(args)
|
126
|
-
args.flatten.
|
128
|
+
args.flatten.select do |id|
|
129
|
+
id = ObjectId.to_mongo(id) if klass.using_object_id?
|
130
|
+
ids.include?(id)
|
131
|
+
end
|
127
132
|
end
|
128
133
|
|
129
134
|
def find_target
|
@@ -52,18 +52,21 @@ module MongoMapper
|
|
52
52
|
def build(attrs={})
|
53
53
|
doc = klass.new(attrs)
|
54
54
|
apply_scope(doc)
|
55
|
+
reset
|
55
56
|
doc
|
56
57
|
end
|
57
58
|
|
58
59
|
def create(attrs={})
|
59
60
|
doc = klass.new(attrs)
|
60
61
|
apply_scope(doc).save
|
62
|
+
reset
|
61
63
|
doc
|
62
64
|
end
|
63
65
|
|
64
66
|
def create!(attrs={})
|
65
67
|
doc = klass.new(attrs)
|
66
68
|
apply_scope(doc).save!
|
69
|
+
reset
|
67
70
|
doc
|
68
71
|
end
|
69
72
|
|
@@ -109,7 +112,7 @@ module MongoMapper
|
|
109
112
|
end
|
110
113
|
|
111
114
|
def foreign_key
|
112
|
-
options[:foreign_key] || owner.class.name.underscore.gsub("/", "_") + "_id"
|
115
|
+
options[:foreign_key] || owner.class.name.to_s.underscore.gsub("/", "_") + "_id"
|
113
116
|
end
|
114
117
|
end
|
115
118
|
end
|
@@ -1,35 +1,74 @@
|
|
1
|
+
# Almost all of this callback stuff is pulled directly from ActiveSupport
|
2
|
+
# in the interest of support rails 2 and 3 at the same time and is the
|
3
|
+
# same copyright as rails.
|
1
4
|
module MongoMapper
|
2
5
|
module Plugins
|
3
6
|
module Callbacks
|
4
7
|
def self.configure(model)
|
5
8
|
model.class_eval do
|
6
|
-
include ActiveSupport::Callbacks
|
7
|
-
|
8
9
|
define_callbacks(
|
9
|
-
:before_save,
|
10
|
-
:before_create,
|
11
|
-
:before_update,
|
12
|
-
:before_validation,
|
13
|
-
:before_validation_on_create, :after_validation_on_create,
|
14
|
-
:before_validation_on_update, :after_validation_on_update,
|
15
|
-
:before_destroy,
|
10
|
+
:before_save, :after_save,
|
11
|
+
:before_create, :after_create,
|
12
|
+
:before_update, :after_update,
|
13
|
+
:before_validation, :after_validation,
|
14
|
+
:before_validation_on_create, :after_validation_on_create,
|
15
|
+
:before_validation_on_update, :after_validation_on_update,
|
16
|
+
:before_destroy, :after_destroy,
|
17
|
+
:validate_on_create, :validate_on_update,
|
18
|
+
:validate
|
16
19
|
)
|
17
20
|
end
|
18
21
|
end
|
19
|
-
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def define_callbacks(*callbacks)
|
25
|
+
callbacks.each do |callback|
|
26
|
+
class_eval <<-"end_eval"
|
27
|
+
def self.#{callback}(*methods, &block)
|
28
|
+
callbacks = CallbackChain.build(:#{callback}, *methods, &block)
|
29
|
+
@#{callback}_callbacks ||= CallbackChain.new
|
30
|
+
@#{callback}_callbacks.concat callbacks
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.#{callback}_callback_chain
|
34
|
+
@#{callback}_callbacks ||= CallbackChain.new
|
35
|
+
|
36
|
+
if superclass.respond_to?(:#{callback}_callback_chain)
|
37
|
+
CallbackChain.new(
|
38
|
+
superclass.#{callback}_callback_chain +
|
39
|
+
@#{callback}_callbacks
|
40
|
+
)
|
41
|
+
else
|
42
|
+
@#{callback}_callbacks
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end_eval
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
20
50
|
module InstanceMethods
|
21
51
|
def valid?
|
22
52
|
action = new? ? 'create' : 'update'
|
23
|
-
|
24
53
|
run_callbacks(:before_validation)
|
25
54
|
run_callbacks("before_validation_on_#{action}".to_sym)
|
26
55
|
result = super
|
27
56
|
run_callbacks("after_validation_on_#{action}".to_sym)
|
28
57
|
run_callbacks(:after_validation)
|
29
|
-
|
30
58
|
result
|
31
59
|
end
|
32
60
|
|
61
|
+
# Overriding validatable's valid_for_group? to integrate validation callbacks
|
62
|
+
def valid_for_group?(group) #:nodoc:
|
63
|
+
errors.clear
|
64
|
+
run_before_validations
|
65
|
+
run_callbacks(:validate)
|
66
|
+
new? ? run_callbacks(:validate_on_create) : run_callbacks(:validate_on_update)
|
67
|
+
self.class.validate_children(self, group)
|
68
|
+
self.validate_group(group)
|
69
|
+
errors.empty?
|
70
|
+
end
|
71
|
+
|
33
72
|
def destroy
|
34
73
|
run_callbacks(:before_destroy)
|
35
74
|
result = super
|
@@ -37,6 +76,17 @@ module MongoMapper
|
|
37
76
|
result
|
38
77
|
end
|
39
78
|
|
79
|
+
def run_callbacks(kind, options = {}, &block)
|
80
|
+
callback_chain_method = "#{kind}_callback_chain"
|
81
|
+
return unless self.class.respond_to?(callback_chain_method)
|
82
|
+
self.class.send(callback_chain_method).run(self, options, &block)
|
83
|
+
self.embedded_associations.each do |association|
|
84
|
+
self.send(association.name).each do |document|
|
85
|
+
document.run_callbacks(kind, options, &block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
40
90
|
private
|
41
91
|
def create_or_update(*args)
|
42
92
|
run_callbacks(:before_save)
|
@@ -60,6 +110,127 @@ module MongoMapper
|
|
60
110
|
result
|
61
111
|
end
|
62
112
|
end
|
113
|
+
|
114
|
+
class CallbackChain < Array
|
115
|
+
def self.build(kind, *methods, &block)
|
116
|
+
methods, options = extract_options(*methods, &block)
|
117
|
+
methods.map! { |method| Callback.new(kind, method, options) }
|
118
|
+
new(methods)
|
119
|
+
end
|
120
|
+
|
121
|
+
def run(object, options = {}, &terminator)
|
122
|
+
enumerator = options[:enumerator] || :each
|
123
|
+
|
124
|
+
unless block_given?
|
125
|
+
send(enumerator) { |callback| callback.call(object) }
|
126
|
+
else
|
127
|
+
send(enumerator) do |callback|
|
128
|
+
result = callback.call(object)
|
129
|
+
break result if terminator.call(result, object)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# TODO: Decompose into more Array like behavior
|
135
|
+
def replace_or_append!(chain)
|
136
|
+
if index = index(chain)
|
137
|
+
self[index] = chain
|
138
|
+
else
|
139
|
+
self << chain
|
140
|
+
end
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def find(callback, &block)
|
145
|
+
select { |c| c == callback && (!block_given? || yield(c)) }.first
|
146
|
+
end
|
147
|
+
|
148
|
+
def delete(callback)
|
149
|
+
super(callback.is_a?(Callback) ? callback : find(callback))
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def self.extract_options(*methods, &block)
|
154
|
+
methods.flatten!
|
155
|
+
options = methods.extract_options!
|
156
|
+
methods << block if block_given?
|
157
|
+
return methods, options
|
158
|
+
end
|
159
|
+
|
160
|
+
def extract_options(*methods, &block)
|
161
|
+
self.class.extract_options(*methods, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class Callback
|
166
|
+
attr_reader :kind, :method, :identifier, :options
|
167
|
+
|
168
|
+
def initialize(kind, method, options = {})
|
169
|
+
@kind = kind
|
170
|
+
@method = method
|
171
|
+
@identifier = options[:identifier]
|
172
|
+
@options = options
|
173
|
+
end
|
174
|
+
|
175
|
+
def ==(other)
|
176
|
+
case other
|
177
|
+
when Callback
|
178
|
+
(self.identifier && self.identifier == other.identifier) || self.method == other.method
|
179
|
+
else
|
180
|
+
(self.identifier && self.identifier == other) || self.method == other
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def eql?(other)
|
185
|
+
self == other
|
186
|
+
end
|
187
|
+
|
188
|
+
def dup
|
189
|
+
self.class.new(@kind, @method, @options.dup)
|
190
|
+
end
|
191
|
+
|
192
|
+
def hash
|
193
|
+
if @identifier
|
194
|
+
@identifier.hash
|
195
|
+
else
|
196
|
+
@method.hash
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def call(*args, &block)
|
201
|
+
evaluate_method(method, *args, &block) if should_run_callback?(*args)
|
202
|
+
rescue LocalJumpError
|
203
|
+
raise ArgumentError,
|
204
|
+
"Cannot yield from a Proc type filter. The Proc must take two " +
|
205
|
+
"arguments and execute #call on the second argument."
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
def evaluate_method(method, *args, &block)
|
210
|
+
case method
|
211
|
+
when Symbol
|
212
|
+
object = args.shift
|
213
|
+
object.send(method, *args, &block)
|
214
|
+
when String
|
215
|
+
eval(method, args.first.instance_eval { binding })
|
216
|
+
when Proc, Method
|
217
|
+
method.call(*args, &block)
|
218
|
+
else
|
219
|
+
if method.respond_to?(kind)
|
220
|
+
method.send(kind, *args, &block)
|
221
|
+
else
|
222
|
+
raise ArgumentError,
|
223
|
+
"Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
|
224
|
+
"a block to be invoked, or an object responding to the callback method."
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def should_run_callback?(*args)
|
230
|
+
[options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
|
231
|
+
![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
|
232
|
+
end
|
233
|
+
end
|
63
234
|
end
|
64
235
|
end
|
65
236
|
end
|