ign-mongo_mapper 0.8.6.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/LICENSE +20 -0
- data/README.rdoc +33 -0
- data/UPGRADES +7 -0
- data/bin/mmconsole +60 -0
- data/examples/attr_accessible.rb +22 -0
- data/examples/attr_protected.rb +22 -0
- data/examples/cache_key.rb +24 -0
- data/examples/custom_types.rb +24 -0
- data/examples/identity_map.rb +33 -0
- data/examples/identity_map/automatic.rb +8 -0
- data/examples/keys.rb +40 -0
- data/examples/modifiers/set.rb +25 -0
- data/examples/plugins.rb +41 -0
- data/examples/querying.rb +35 -0
- data/examples/safe.rb +43 -0
- data/examples/scopes.rb +52 -0
- data/examples/validating/embedded_docs.rb +29 -0
- data/lib/mongo_mapper.rb +83 -0
- data/lib/mongo_mapper/connection.rb +83 -0
- data/lib/mongo_mapper/document.rb +41 -0
- data/lib/mongo_mapper/embedded_document.rb +31 -0
- data/lib/mongo_mapper/exceptions.rb +27 -0
- data/lib/mongo_mapper/extensions/array.rb +19 -0
- data/lib/mongo_mapper/extensions/binary.rb +22 -0
- data/lib/mongo_mapper/extensions/boolean.rb +44 -0
- data/lib/mongo_mapper/extensions/date.rb +25 -0
- data/lib/mongo_mapper/extensions/float.rb +14 -0
- data/lib/mongo_mapper/extensions/hash.rb +14 -0
- data/lib/mongo_mapper/extensions/integer.rb +19 -0
- data/lib/mongo_mapper/extensions/kernel.rb +9 -0
- data/lib/mongo_mapper/extensions/nil_class.rb +18 -0
- data/lib/mongo_mapper/extensions/object.rb +27 -0
- data/lib/mongo_mapper/extensions/object_id.rb +30 -0
- data/lib/mongo_mapper/extensions/set.rb +20 -0
- data/lib/mongo_mapper/extensions/string.rb +18 -0
- data/lib/mongo_mapper/extensions/time.rb +29 -0
- data/lib/mongo_mapper/middleware/identity_map.rb +16 -0
- data/lib/mongo_mapper/plugins.rb +15 -0
- data/lib/mongo_mapper/plugins/accessible.rb +44 -0
- data/lib/mongo_mapper/plugins/associations.rb +134 -0
- data/lib/mongo_mapper/plugins/associations/base.rb +124 -0
- data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +29 -0
- data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +24 -0
- data/lib/mongo_mapper/plugins/associations/collection.rb +27 -0
- data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +40 -0
- data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +151 -0
- data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
- data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +109 -0
- data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
- data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
- data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
- data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +41 -0
- data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
- data/lib/mongo_mapper/plugins/associations/proxy.rb +139 -0
- data/lib/mongo_mapper/plugins/caching.rb +21 -0
- data/lib/mongo_mapper/plugins/callbacks.rb +243 -0
- data/lib/mongo_mapper/plugins/clone.rb +22 -0
- data/lib/mongo_mapper/plugins/descendants.rb +17 -0
- data/lib/mongo_mapper/plugins/dirty.rb +124 -0
- data/lib/mongo_mapper/plugins/document.rb +41 -0
- data/lib/mongo_mapper/plugins/dynamic_querying.rb +43 -0
- data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
- data/lib/mongo_mapper/plugins/embedded_document.rb +48 -0
- data/lib/mongo_mapper/plugins/equality.rb +17 -0
- data/lib/mongo_mapper/plugins/identity_map.rb +128 -0
- data/lib/mongo_mapper/plugins/indexes.rb +12 -0
- data/lib/mongo_mapper/plugins/inspect.rb +15 -0
- data/lib/mongo_mapper/plugins/keys.rb +311 -0
- data/lib/mongo_mapper/plugins/keys/key.rb +65 -0
- data/lib/mongo_mapper/plugins/logger.rb +18 -0
- data/lib/mongo_mapper/plugins/modifiers.rb +112 -0
- data/lib/mongo_mapper/plugins/pagination.rb +14 -0
- data/lib/mongo_mapper/plugins/persistence.rb +69 -0
- data/lib/mongo_mapper/plugins/protected.rb +53 -0
- data/lib/mongo_mapper/plugins/querying.rb +176 -0
- data/lib/mongo_mapper/plugins/querying/decorator.rb +46 -0
- data/lib/mongo_mapper/plugins/querying/plucky_methods.rb +15 -0
- data/lib/mongo_mapper/plugins/rails.rb +58 -0
- data/lib/mongo_mapper/plugins/safe.rb +28 -0
- data/lib/mongo_mapper/plugins/sci.rb +32 -0
- data/lib/mongo_mapper/plugins/scopes.rb +21 -0
- data/lib/mongo_mapper/plugins/serialization.rb +76 -0
- data/lib/mongo_mapper/plugins/timestamps.rb +22 -0
- data/lib/mongo_mapper/plugins/userstamps.rb +15 -0
- data/lib/mongo_mapper/plugins/validations.rb +50 -0
- data/lib/mongo_mapper/support/descendant_appends.rb +45 -0
- data/lib/mongo_mapper/version.rb +4 -0
- data/rails/init.rb +15 -0
- data/test/_NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +64 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +117 -0
- data/test/functional/associations/test_in_array_proxy.rb +349 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
- data/test/functional/associations/test_many_documents_proxy.rb +615 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +303 -0
- data/test/functional/associations/test_one_embedded_proxy.rb +100 -0
- data/test/functional/associations/test_one_proxy.rb +206 -0
- data/test/functional/test_accessible.rb +168 -0
- data/test/functional/test_associations.rb +46 -0
- data/test/functional/test_binary.rb +27 -0
- data/test/functional/test_caching.rb +76 -0
- data/test/functional/test_callbacks.rb +151 -0
- data/test/functional/test_dirty.rb +163 -0
- data/test/functional/test_document.rb +272 -0
- data/test/functional/test_dynamic_querying.rb +75 -0
- data/test/functional/test_embedded_document.rb +210 -0
- data/test/functional/test_identity_map.rb +513 -0
- data/test/functional/test_indexes.rb +42 -0
- data/test/functional/test_logger.rb +20 -0
- data/test/functional/test_modifiers.rb +416 -0
- data/test/functional/test_pagination.rb +91 -0
- data/test/functional/test_protected.rb +175 -0
- data/test/functional/test_querying.rb +873 -0
- data/test/functional/test_safe.rb +76 -0
- data/test/functional/test_sci.rb +230 -0
- data/test/functional/test_scopes.rb +171 -0
- data/test/functional/test_string_id_compatibility.rb +67 -0
- data/test/functional/test_timestamps.rb +62 -0
- data/test/functional/test_userstamps.rb +27 -0
- data/test/functional/test_validations.rb +342 -0
- data/test/models.rb +233 -0
- data/test/test_active_model_lint.rb +13 -0
- data/test/test_helper.rb +102 -0
- data/test/unit/associations/test_base.rb +212 -0
- data/test/unit/associations/test_proxy.rb +105 -0
- data/test/unit/serializers/test_json_serializer.rb +217 -0
- data/test/unit/test_clone.rb +69 -0
- data/test/unit/test_descendant_appends.rb +71 -0
- data/test/unit/test_document.rb +208 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +639 -0
- data/test/unit/test_extensions.rb +376 -0
- data/test/unit/test_identity_map_middleware.rb +34 -0
- data/test/unit/test_inspect.rb +22 -0
- data/test/unit/test_key.rb +205 -0
- data/test/unit/test_keys.rb +89 -0
- data/test/unit/test_mongo_mapper.rb +110 -0
- data/test/unit/test_pagination.rb +11 -0
- data/test/unit/test_plugins.rb +50 -0
- data/test/unit/test_rails.rb +181 -0
- data/test/unit/test_rails_compatibility.rb +52 -0
- data/test/unit/test_serialization.rb +51 -0
- data/test/unit/test_time_zones.rb +39 -0
- data/test/unit/test_validations.rb +564 -0
- metadata +385 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module MongoMapper
|
3
|
+
module Plugins
|
4
|
+
module Associations
|
5
|
+
class ManyEmbeddedPolymorphicProxy < EmbeddedCollection
|
6
|
+
def replace(values)
|
7
|
+
@_values = values.map do |v|
|
8
|
+
v.respond_to?(:attributes) ? v.attributes.merge(association.type_key_name => v.class.name) : v
|
9
|
+
end
|
10
|
+
reset
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
def find_target
|
15
|
+
(@_values || []).map do |hash|
|
16
|
+
child = polymorphic_class(hash).load(hash)
|
17
|
+
assign_references(child)
|
18
|
+
child
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def polymorphic_class(doc)
|
23
|
+
if class_name = doc[association.type_key_name]
|
24
|
+
class_name.constantize
|
25
|
+
else
|
26
|
+
klass
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module MongoMapper
|
3
|
+
module Plugins
|
4
|
+
module Associations
|
5
|
+
class ManyEmbeddedProxy < EmbeddedCollection
|
6
|
+
def replace(values)
|
7
|
+
@_values = values.map do |v|
|
8
|
+
v.respond_to?(:attributes) ? v.attributes : v
|
9
|
+
end
|
10
|
+
reset
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def find_target
|
15
|
+
(@_values || []).map do |attrs|
|
16
|
+
klass.load(attrs).tap do |child|
|
17
|
+
assign_references(child)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module MongoMapper
|
3
|
+
module Plugins
|
4
|
+
module Associations
|
5
|
+
class ManyPolymorphicProxy < ManyDocumentsProxy
|
6
|
+
private
|
7
|
+
def apply_scope(doc)
|
8
|
+
doc[association.type_key_name] = doc.class.name
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module MongoMapper
|
3
|
+
module Plugins
|
4
|
+
module Associations
|
5
|
+
class OneEmbeddedProxy < Proxy
|
6
|
+
def build(attributes={})
|
7
|
+
@target = klass.new(attributes)
|
8
|
+
assign_references(@target)
|
9
|
+
loaded
|
10
|
+
@target
|
11
|
+
end
|
12
|
+
|
13
|
+
def replace(doc)
|
14
|
+
if doc.respond_to?(:attributes)
|
15
|
+
@target = klass.load(doc.attributes)
|
16
|
+
else
|
17
|
+
@target = klass.load(doc)
|
18
|
+
end
|
19
|
+
@target.default_id_value if @target && @target.id.nil?
|
20
|
+
assign_references(@target)
|
21
|
+
loaded
|
22
|
+
@target
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def find_target
|
28
|
+
if @value
|
29
|
+
klass.load(@value).tap do |child|
|
30
|
+
assign_references(child)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def assign_references(doc)
|
36
|
+
doc._parent_document = proxy_owner if doc
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module MongoMapper
|
3
|
+
module Plugins
|
4
|
+
module Associations
|
5
|
+
class OneProxy < Proxy
|
6
|
+
def build(attrs={})
|
7
|
+
instantiate_target(:new, attrs)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(attrs={})
|
11
|
+
instantiate_target(:create, attrs)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create!(attrs={})
|
15
|
+
instantiate_target(:create!, attrs)
|
16
|
+
end
|
17
|
+
|
18
|
+
def replace(doc)
|
19
|
+
load_target
|
20
|
+
|
21
|
+
if !target.nil? && target != doc
|
22
|
+
if options[:dependent] && !target.new?
|
23
|
+
case options[:dependent]
|
24
|
+
when :delete
|
25
|
+
target.delete
|
26
|
+
when :destroy
|
27
|
+
target.destroy
|
28
|
+
when :nullify
|
29
|
+
target[foreign_key] = nil
|
30
|
+
target.save
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if doc.nil?
|
36
|
+
target.update_attributes(foreign_key => nil) unless target.nil?
|
37
|
+
else
|
38
|
+
proxy_owner.save if proxy_owner.new?
|
39
|
+
doc = klass.new(doc) unless doc.is_a?(klass)
|
40
|
+
doc[foreign_key] = proxy_owner.id
|
41
|
+
doc.save if doc.new?
|
42
|
+
loaded
|
43
|
+
@target = doc
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
def find_target
|
49
|
+
target_class.first(association.query_options.merge(foreign_key => proxy_owner.id))
|
50
|
+
end
|
51
|
+
|
52
|
+
def instantiate_target(instantiator, attrs={})
|
53
|
+
@target = target_class.send(instantiator, attrs.update(foreign_key => proxy_owner.id))
|
54
|
+
loaded
|
55
|
+
@target
|
56
|
+
end
|
57
|
+
|
58
|
+
def target_class
|
59
|
+
@target_class ||= options[:class] || (options[:class_name] || association.name.to_s.camelize).constantize
|
60
|
+
end
|
61
|
+
|
62
|
+
def foreign_key
|
63
|
+
options[:foreign_key] || proxy_owner.class.name.foreign_key
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'forwardable'
|
3
|
+
module MongoMapper
|
4
|
+
module Plugins
|
5
|
+
module Associations
|
6
|
+
class Proxy
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
alias :proxy_respond_to? :respond_to?
|
10
|
+
alias :proxy_extend :extend
|
11
|
+
|
12
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
13
|
+
|
14
|
+
attr_reader :proxy_owner, :association, :target
|
15
|
+
|
16
|
+
alias :proxy_target :target
|
17
|
+
alias :proxy_association :association
|
18
|
+
|
19
|
+
def_delegators :proxy_association, :klass, :options
|
20
|
+
def_delegator :klass, :collection
|
21
|
+
|
22
|
+
def initialize(owner, association)
|
23
|
+
@proxy_owner, @association, @loaded = owner, association, false
|
24
|
+
Array(association.options[:extend]).each { |ext| proxy_extend(ext) }
|
25
|
+
reset
|
26
|
+
end
|
27
|
+
|
28
|
+
# Active support in rails 3 beta 4 can override to_json after this is loaded,
|
29
|
+
# at least when run in mongomapper tests. The implementation was changed in master
|
30
|
+
# some time after this, so not sure whether this is still a problem.
|
31
|
+
#
|
32
|
+
# In rails 2, this isn't a problem however it also solves an issue where
|
33
|
+
# to_json isn't forwarded because it supports to_json itself
|
34
|
+
def to_json(*options)
|
35
|
+
load_target
|
36
|
+
target.to_json(*options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# see comments to to_json
|
40
|
+
def as_json(*options)
|
41
|
+
load_target
|
42
|
+
target.as_json(*options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
load_target
|
47
|
+
target.inspect
|
48
|
+
end
|
49
|
+
|
50
|
+
def loaded?
|
51
|
+
@loaded
|
52
|
+
end
|
53
|
+
|
54
|
+
def loaded
|
55
|
+
@loaded = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def nil?
|
59
|
+
load_target
|
60
|
+
target.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def blank?
|
64
|
+
load_target
|
65
|
+
target.blank?
|
66
|
+
end
|
67
|
+
|
68
|
+
def present?
|
69
|
+
load_target
|
70
|
+
target.present?
|
71
|
+
end
|
72
|
+
|
73
|
+
def reload
|
74
|
+
reset
|
75
|
+
load_target
|
76
|
+
self unless target.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def replace(v)
|
80
|
+
raise NotImplementedError
|
81
|
+
end
|
82
|
+
|
83
|
+
def reset
|
84
|
+
@loaded = false
|
85
|
+
@target = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def respond_to?(*args)
|
89
|
+
proxy_respond_to?(*args) || (load_target && target.respond_to?(*args))
|
90
|
+
end
|
91
|
+
|
92
|
+
def send(method, *args)
|
93
|
+
if proxy_respond_to?(method)
|
94
|
+
super
|
95
|
+
else
|
96
|
+
load_target
|
97
|
+
target.send(method, *args)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def ===(other)
|
102
|
+
load_target
|
103
|
+
other === target
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
def method_missing(method, *args, &block)
|
108
|
+
if load_target
|
109
|
+
target.send(method, *args, &block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def load_target
|
114
|
+
unless loaded?
|
115
|
+
if @target.is_a?(Array) && @target.any?
|
116
|
+
@target = find_target + @target.find_all { |record| record.new? }
|
117
|
+
else
|
118
|
+
@target = find_target
|
119
|
+
end
|
120
|
+
loaded
|
121
|
+
end
|
122
|
+
@target
|
123
|
+
rescue MongoMapper::DocumentNotFound
|
124
|
+
reset
|
125
|
+
end
|
126
|
+
|
127
|
+
def find_target
|
128
|
+
raise NotImplementedError
|
129
|
+
end
|
130
|
+
|
131
|
+
def flatten_deeper(array)
|
132
|
+
array.collect do |element|
|
133
|
+
(element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
|
134
|
+
end.flatten
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module MongoMapper
|
3
|
+
module Plugins
|
4
|
+
module Caching
|
5
|
+
module InstanceMethods
|
6
|
+
def cache_key(*suffixes)
|
7
|
+
cache_key = case
|
8
|
+
when new?
|
9
|
+
"#{self.class.name}/new"
|
10
|
+
when timestamp = self[:updated_at]
|
11
|
+
"#{self.class.name}/#{id}-#{timestamp.to_s(:number)}"
|
12
|
+
else
|
13
|
+
"#{self.class.name}/#{id}"
|
14
|
+
end
|
15
|
+
cache_key += "/#{suffixes.join('/')}" unless suffixes.empty?
|
16
|
+
cache_key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Almost all of this callback stuff is pulled directly from ActiveSupport
|
3
|
+
# in the interest of support rails 2 and 3 at the same time and is the
|
4
|
+
# same copyright as rails.
|
5
|
+
module MongoMapper
|
6
|
+
module Plugins
|
7
|
+
module Callbacks
|
8
|
+
def self.configure(model)
|
9
|
+
model.class_eval do
|
10
|
+
define_callbacks(
|
11
|
+
:before_save, :after_save,
|
12
|
+
:before_create, :after_create,
|
13
|
+
:before_update, :after_update,
|
14
|
+
:before_validation, :after_validation,
|
15
|
+
:before_validation_on_create, :after_validation_on_create,
|
16
|
+
:before_validation_on_update, :after_validation_on_update,
|
17
|
+
:before_destroy, :after_destroy,
|
18
|
+
:validate_on_create, :validate_on_update,
|
19
|
+
:validate
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def define_callbacks(*callbacks)
|
26
|
+
callbacks.each do |callback|
|
27
|
+
class_eval <<-"end_eval"
|
28
|
+
def self.#{callback}(*methods, &block)
|
29
|
+
callbacks = CallbackChain.build(:#{callback}, *methods, &block)
|
30
|
+
@#{callback}_callbacks ||= CallbackChain.new
|
31
|
+
@#{callback}_callbacks.concat callbacks
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.#{callback}_callback_chain
|
35
|
+
@#{callback}_callbacks ||= CallbackChain.new
|
36
|
+
|
37
|
+
if superclass.respond_to?(:#{callback}_callback_chain)
|
38
|
+
CallbackChain.new(
|
39
|
+
superclass.#{callback}_callback_chain +
|
40
|
+
@#{callback}_callbacks
|
41
|
+
)
|
42
|
+
else
|
43
|
+
@#{callback}_callbacks
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end_eval
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module InstanceMethods
|
52
|
+
def valid?
|
53
|
+
action = new? ? 'create' : 'update'
|
54
|
+
run_callbacks(:before_validation)
|
55
|
+
run_callbacks("before_validation_on_#{action}".to_sym)
|
56
|
+
result = super
|
57
|
+
run_callbacks("after_validation_on_#{action}".to_sym)
|
58
|
+
run_callbacks(:after_validation)
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
# Overriding validatable's valid_for_group? to integrate validation callbacks
|
63
|
+
def valid_for_group?(group) #:nodoc:
|
64
|
+
errors.clear
|
65
|
+
run_before_validations
|
66
|
+
run_callbacks(:validate)
|
67
|
+
new? ? run_callbacks(:validate_on_create) : run_callbacks(:validate_on_update)
|
68
|
+
self.class.validate_children(self, group)
|
69
|
+
self.validate_group(group)
|
70
|
+
errors.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def destroy
|
74
|
+
run_callbacks(:before_destroy)
|
75
|
+
result = super
|
76
|
+
run_callbacks(:after_destroy)
|
77
|
+
result
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_callbacks(kind, options={}, &block)
|
81
|
+
callback_chain_method = "#{kind}_callback_chain"
|
82
|
+
return unless self.class.respond_to?(callback_chain_method)
|
83
|
+
self.class.send(callback_chain_method).run(self, options, &block)
|
84
|
+
self.embedded_associations.each do |association|
|
85
|
+
if association.one?
|
86
|
+
if !self.send("#{association.name}").nil?
|
87
|
+
self.send(association.name).run_callbacks(kind, options, &block)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
self.send(association.name).each do |document|
|
91
|
+
document.run_callbacks(kind, options, &block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def create_or_update(*args)
|
99
|
+
run_callbacks(:before_save)
|
100
|
+
if result = super
|
101
|
+
run_callbacks(:after_save)
|
102
|
+
end
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
def create(*args)
|
107
|
+
run_callbacks(:before_create)
|
108
|
+
result = super
|
109
|
+
run_callbacks(:after_create)
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
def update(*args)
|
114
|
+
run_callbacks(:before_update)
|
115
|
+
result = super
|
116
|
+
run_callbacks(:after_update)
|
117
|
+
result
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class CallbackChain < Array
|
122
|
+
def self.build(kind, *methods, &block)
|
123
|
+
methods, options = extract_options(*methods, &block)
|
124
|
+
methods.map! { |method| Callback.new(kind, method, options) }
|
125
|
+
new(methods)
|
126
|
+
end
|
127
|
+
|
128
|
+
def run(object, options={}, &terminator)
|
129
|
+
enumerator = options[:enumerator] || :each
|
130
|
+
|
131
|
+
unless block_given?
|
132
|
+
send(enumerator) { |callback| callback.call(object) }
|
133
|
+
else
|
134
|
+
send(enumerator) do |callback|
|
135
|
+
result = callback.call(object)
|
136
|
+
break result if terminator.call(result, object)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# TODO: Decompose into more Array like behavior
|
142
|
+
def replace_or_append!(chain)
|
143
|
+
if index = index(chain)
|
144
|
+
self[index] = chain
|
145
|
+
else
|
146
|
+
self << chain
|
147
|
+
end
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
def find(callback, &block)
|
152
|
+
select { |c| c == callback && (!block_given? || yield(c)) }.first
|
153
|
+
end
|
154
|
+
|
155
|
+
def delete(callback)
|
156
|
+
super(callback.is_a?(Callback) ? callback : find(callback))
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
def self.extract_options(*methods, &block)
|
161
|
+
methods.flatten!
|
162
|
+
options = methods.extract_options!
|
163
|
+
methods << block if block_given?
|
164
|
+
return methods, options
|
165
|
+
end
|
166
|
+
|
167
|
+
def extract_options(*methods, &block)
|
168
|
+
self.class.extract_options(*methods, &block)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class Callback
|
173
|
+
attr_reader :kind, :method, :identifier, :options
|
174
|
+
|
175
|
+
def initialize(kind, method, options={})
|
176
|
+
@kind = kind
|
177
|
+
@method = method
|
178
|
+
@identifier = options[:identifier]
|
179
|
+
@options = options
|
180
|
+
end
|
181
|
+
|
182
|
+
def ==(other)
|
183
|
+
case other
|
184
|
+
when Callback
|
185
|
+
(self.identifier && self.identifier == other.identifier) || self.method == other.method
|
186
|
+
else
|
187
|
+
(self.identifier && self.identifier == other) || self.method == other
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def eql?(other)
|
192
|
+
self == other
|
193
|
+
end
|
194
|
+
|
195
|
+
def dup
|
196
|
+
self.class.new(@kind, @method, @options.dup)
|
197
|
+
end
|
198
|
+
|
199
|
+
def hash
|
200
|
+
if @identifier
|
201
|
+
@identifier.hash
|
202
|
+
else
|
203
|
+
@method.hash
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def call(*args, &block)
|
208
|
+
evaluate_method(method, *args, &block) if should_run_callback?(*args)
|
209
|
+
rescue LocalJumpError
|
210
|
+
raise ArgumentError,
|
211
|
+
"Cannot yield from a Proc type filter. The Proc must take two " +
|
212
|
+
"arguments and execute #call on the second argument."
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
def evaluate_method(method, *args, &block)
|
217
|
+
case method
|
218
|
+
when Symbol
|
219
|
+
object = args.shift
|
220
|
+
object.send(method, *args, &block)
|
221
|
+
when String
|
222
|
+
eval(method, args.first.instance_eval { binding })
|
223
|
+
when Proc, Method
|
224
|
+
method.call(*args, &block)
|
225
|
+
else
|
226
|
+
if method.respond_to?(kind)
|
227
|
+
method.send(kind, *args, &block)
|
228
|
+
else
|
229
|
+
raise ArgumentError,
|
230
|
+
"Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
|
231
|
+
"a block to be invoked, or an object responding to the callback method."
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def should_run_callback?(*args)
|
237
|
+
[options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
|
238
|
+
![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|