activemodel 3.2.22.5 → 4.0.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +85 -64
- data/MIT-LICENSE +1 -1
- data/README.rdoc +61 -24
- data/lib/active_model.rb +21 -11
- data/lib/active_model/attribute_methods.rb +150 -125
- data/lib/active_model/callbacks.rb +49 -34
- data/lib/active_model/conversion.rb +39 -19
- data/lib/active_model/deprecated_mass_assignment_security.rb +21 -0
- data/lib/active_model/dirty.rb +48 -32
- data/lib/active_model/errors.rb +176 -88
- data/lib/active_model/forbidden_attributes_protection.rb +27 -0
- data/lib/active_model/lint.rb +42 -55
- data/lib/active_model/locale/en.yml +3 -1
- data/lib/active_model/model.rb +97 -0
- data/lib/active_model/naming.rb +191 -51
- data/lib/active_model/railtie.rb +11 -1
- data/lib/active_model/secure_password.rb +55 -25
- data/lib/active_model/serialization.rb +51 -27
- data/lib/active_model/serializers/json.rb +83 -46
- data/lib/active_model/serializers/xml.rb +46 -12
- data/lib/active_model/test_case.rb +0 -12
- data/lib/active_model/translation.rb +9 -10
- data/lib/active_model/validations.rb +154 -52
- data/lib/active_model/validations/absence.rb +31 -0
- data/lib/active_model/validations/acceptance.rb +10 -22
- data/lib/active_model/validations/callbacks.rb +78 -25
- data/lib/active_model/validations/clusivity.rb +41 -0
- data/lib/active_model/validations/confirmation.rb +13 -23
- data/lib/active_model/validations/exclusion.rb +26 -55
- data/lib/active_model/validations/format.rb +44 -34
- data/lib/active_model/validations/inclusion.rb +22 -52
- data/lib/active_model/validations/length.rb +48 -49
- data/lib/active_model/validations/numericality.rb +30 -32
- data/lib/active_model/validations/presence.rb +12 -22
- data/lib/active_model/validations/validates.rb +68 -36
- data/lib/active_model/validations/with.rb +28 -23
- data/lib/active_model/validator.rb +22 -22
- data/lib/active_model/version.rb +4 -4
- metadata +23 -24
- data/lib/active_model/mass_assignment_security.rb +0 -237
- data/lib/active_model/mass_assignment_security/permission_set.rb +0 -40
- data/lib/active_model/mass_assignment_security/sanitizer.rb +0 -59
- data/lib/active_model/observer_array.rb +0 -147
- data/lib/active_model/observing.rb +0 -252
@@ -1,40 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
|
-
module ActiveModel
|
4
|
-
module MassAssignmentSecurity
|
5
|
-
class PermissionSet < Set
|
6
|
-
|
7
|
-
def +(values)
|
8
|
-
super(values.map(&:to_s))
|
9
|
-
end
|
10
|
-
|
11
|
-
def include?(key)
|
12
|
-
super(remove_multiparameter_id(key))
|
13
|
-
end
|
14
|
-
|
15
|
-
def deny?(key)
|
16
|
-
raise NotImplementedError, "#deny?(key) suppose to be overwritten"
|
17
|
-
end
|
18
|
-
|
19
|
-
protected
|
20
|
-
|
21
|
-
def remove_multiparameter_id(key)
|
22
|
-
key.to_s.gsub(/\(.+/m, '')
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class WhiteList < PermissionSet
|
27
|
-
|
28
|
-
def deny?(key)
|
29
|
-
!include?(key)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class BlackList < PermissionSet
|
34
|
-
|
35
|
-
def deny?(key)
|
36
|
-
include?(key)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
require 'active_support/core_ext/module/delegation'
|
2
|
-
|
3
|
-
module ActiveModel
|
4
|
-
module MassAssignmentSecurity
|
5
|
-
class Sanitizer
|
6
|
-
def initialize(target=nil)
|
7
|
-
end
|
8
|
-
|
9
|
-
# Returns all attributes not denied by the authorizer.
|
10
|
-
def sanitize(attributes, authorizer)
|
11
|
-
sanitized_attributes = attributes.reject { |key, value| authorizer.deny?(key) }
|
12
|
-
debug_protected_attribute_removal(attributes, sanitized_attributes)
|
13
|
-
sanitized_attributes
|
14
|
-
end
|
15
|
-
|
16
|
-
protected
|
17
|
-
|
18
|
-
def debug_protected_attribute_removal(attributes, sanitized_attributes)
|
19
|
-
removed_keys = attributes.keys - sanitized_attributes.keys
|
20
|
-
process_removed_attributes(removed_keys) if removed_keys.any?
|
21
|
-
end
|
22
|
-
|
23
|
-
def process_removed_attributes(attrs)
|
24
|
-
raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class LoggerSanitizer < Sanitizer
|
29
|
-
delegate :logger, :to => :@target
|
30
|
-
|
31
|
-
def initialize(target)
|
32
|
-
@target = target
|
33
|
-
super
|
34
|
-
end
|
35
|
-
|
36
|
-
def logger?
|
37
|
-
@target.respond_to?(:logger) && @target.logger
|
38
|
-
end
|
39
|
-
|
40
|
-
def process_removed_attributes(attrs)
|
41
|
-
logger.debug "WARNING: Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger?
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class StrictSanitizer < Sanitizer
|
46
|
-
def process_removed_attributes(attrs)
|
47
|
-
return if (attrs - insensitive_attributes).empty?
|
48
|
-
raise ActiveModel::MassAssignmentSecurity::Error, "Can't mass-assign protected attributes: #{attrs.join(', ')}"
|
49
|
-
end
|
50
|
-
|
51
|
-
def insensitive_attributes
|
52
|
-
['id']
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
class Error < StandardError
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,147 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
|
-
module ActiveModel
|
4
|
-
# Stores the enabled/disabled state of individual observers for
|
5
|
-
# a particular model class.
|
6
|
-
class ObserverArray < Array
|
7
|
-
attr_reader :model_class
|
8
|
-
def initialize(model_class, *args)
|
9
|
-
@model_class = model_class
|
10
|
-
super(*args)
|
11
|
-
end
|
12
|
-
|
13
|
-
# Returns true if the given observer is disabled for the model class.
|
14
|
-
def disabled_for?(observer)
|
15
|
-
disabled_observers.include?(observer.class)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Disables one or more observers. This supports multiple forms:
|
19
|
-
#
|
20
|
-
# ORM.observers.disable :user_observer
|
21
|
-
# # => disables the UserObserver
|
22
|
-
#
|
23
|
-
# User.observers.disable AuditTrail
|
24
|
-
# # => disables the AuditTrail observer for User notifications.
|
25
|
-
# # Other models will still notify the AuditTrail observer.
|
26
|
-
#
|
27
|
-
# ORM.observers.disable :observer_1, :observer_2
|
28
|
-
# # => disables Observer1 and Observer2 for all models.
|
29
|
-
#
|
30
|
-
# ORM.observers.disable :all
|
31
|
-
# # => disables all observers for all models.
|
32
|
-
#
|
33
|
-
# User.observers.disable :all do
|
34
|
-
# # all user observers are disabled for
|
35
|
-
# # just the duration of the block
|
36
|
-
# end
|
37
|
-
def disable(*observers, &block)
|
38
|
-
set_enablement(false, observers, &block)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Enables one or more observers. This supports multiple forms:
|
42
|
-
#
|
43
|
-
# ORM.observers.enable :user_observer
|
44
|
-
# # => enables the UserObserver
|
45
|
-
#
|
46
|
-
# User.observers.enable AuditTrail
|
47
|
-
# # => enables the AuditTrail observer for User notifications.
|
48
|
-
# # Other models will not be affected (i.e. they will not
|
49
|
-
# # trigger notifications to AuditTrail if previously disabled)
|
50
|
-
#
|
51
|
-
# ORM.observers.enable :observer_1, :observer_2
|
52
|
-
# # => enables Observer1 and Observer2 for all models.
|
53
|
-
#
|
54
|
-
# ORM.observers.enable :all
|
55
|
-
# # => enables all observers for all models.
|
56
|
-
#
|
57
|
-
# User.observers.enable :all do
|
58
|
-
# # all user observers are enabled for
|
59
|
-
# # just the duration of the block
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
# Note: all observers are enabled by default. This method is only
|
63
|
-
# useful when you have previously disabled one or more observers.
|
64
|
-
def enable(*observers, &block)
|
65
|
-
set_enablement(true, observers, &block)
|
66
|
-
end
|
67
|
-
|
68
|
-
protected
|
69
|
-
|
70
|
-
def disabled_observers
|
71
|
-
@disabled_observers ||= Set.new
|
72
|
-
end
|
73
|
-
|
74
|
-
def observer_class_for(observer)
|
75
|
-
return observer if observer.is_a?(Class)
|
76
|
-
|
77
|
-
if observer.respond_to?(:to_sym) # string/symbol
|
78
|
-
observer.to_s.camelize.constantize
|
79
|
-
else
|
80
|
-
raise ArgumentError, "#{observer} was not a class or a " +
|
81
|
-
"lowercase, underscored class name as expected."
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def start_transaction
|
86
|
-
disabled_observer_stack.push(disabled_observers.dup)
|
87
|
-
each_subclass_array do |array|
|
88
|
-
array.start_transaction
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def disabled_observer_stack
|
93
|
-
@disabled_observer_stack ||= []
|
94
|
-
end
|
95
|
-
|
96
|
-
def end_transaction
|
97
|
-
@disabled_observers = disabled_observer_stack.pop
|
98
|
-
each_subclass_array do |array|
|
99
|
-
array.end_transaction
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def transaction
|
104
|
-
start_transaction
|
105
|
-
|
106
|
-
begin
|
107
|
-
yield
|
108
|
-
ensure
|
109
|
-
end_transaction
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def each_subclass_array
|
114
|
-
model_class.descendants.each do |subclass|
|
115
|
-
yield subclass.observers
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def set_enablement(enabled, observers)
|
120
|
-
if block_given?
|
121
|
-
transaction do
|
122
|
-
set_enablement(enabled, observers)
|
123
|
-
yield
|
124
|
-
end
|
125
|
-
else
|
126
|
-
observers = ActiveModel::Observer.descendants if observers == [:all]
|
127
|
-
observers.each do |obs|
|
128
|
-
klass = observer_class_for(obs)
|
129
|
-
|
130
|
-
unless klass < ActiveModel::Observer
|
131
|
-
raise ArgumentError.new("#{obs} does not refer to a valid observer")
|
132
|
-
end
|
133
|
-
|
134
|
-
if enabled
|
135
|
-
disabled_observers.delete(klass)
|
136
|
-
else
|
137
|
-
disabled_observers << klass
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
each_subclass_array do |array|
|
142
|
-
array.set_enablement(enabled, observers)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
@@ -1,252 +0,0 @@
|
|
1
|
-
require 'singleton'
|
2
|
-
require 'active_model/observer_array'
|
3
|
-
require 'active_support/core_ext/array/wrap'
|
4
|
-
require 'active_support/core_ext/module/aliasing'
|
5
|
-
require 'active_support/core_ext/module/remove_method'
|
6
|
-
require 'active_support/core_ext/string/inflections'
|
7
|
-
require 'active_support/core_ext/enumerable'
|
8
|
-
require 'active_support/descendants_tracker'
|
9
|
-
|
10
|
-
module ActiveModel
|
11
|
-
module Observing
|
12
|
-
extend ActiveSupport::Concern
|
13
|
-
|
14
|
-
included do
|
15
|
-
extend ActiveSupport::DescendantsTracker
|
16
|
-
end
|
17
|
-
|
18
|
-
module ClassMethods
|
19
|
-
# == Active Model Observers Activation
|
20
|
-
#
|
21
|
-
# Activates the observers assigned. Examples:
|
22
|
-
#
|
23
|
-
# class ORM
|
24
|
-
# include ActiveModel::Observing
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# # Calls PersonObserver.instance
|
28
|
-
# ORM.observers = :person_observer
|
29
|
-
#
|
30
|
-
# # Calls Cacher.instance and GarbageCollector.instance
|
31
|
-
# ORM.observers = :cacher, :garbage_collector
|
32
|
-
#
|
33
|
-
# # Same as above, just using explicit class references
|
34
|
-
# ORM.observers = Cacher, GarbageCollector
|
35
|
-
#
|
36
|
-
# Note: Setting this does not instantiate the observers yet.
|
37
|
-
# +instantiate_observers+ is called during startup, and before
|
38
|
-
# each development request.
|
39
|
-
def observers=(*values)
|
40
|
-
observers.replace(values.flatten)
|
41
|
-
end
|
42
|
-
|
43
|
-
# Gets an array of observers observing this model.
|
44
|
-
# The array also provides +enable+ and +disable+ methods
|
45
|
-
# that allow you to selectively enable and disable observers.
|
46
|
-
# (see <tt>ActiveModel::ObserverArray.enable</tt> and
|
47
|
-
# <tt>ActiveModel::ObserverArray.disable</tt> for more on this)
|
48
|
-
def observers
|
49
|
-
@observers ||= ObserverArray.new(self)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Gets the current observer instances.
|
53
|
-
def observer_instances
|
54
|
-
@observer_instances ||= []
|
55
|
-
end
|
56
|
-
|
57
|
-
# Instantiate the global observers.
|
58
|
-
def instantiate_observers
|
59
|
-
observers.each { |o| instantiate_observer(o) }
|
60
|
-
end
|
61
|
-
|
62
|
-
# Add a new observer to the pool.
|
63
|
-
# The new observer needs to respond to 'update', otherwise it
|
64
|
-
# raises an +ArgumentError+ exception.
|
65
|
-
def add_observer(observer)
|
66
|
-
unless observer.respond_to? :update
|
67
|
-
raise ArgumentError, "observer needs to respond to `update'"
|
68
|
-
end
|
69
|
-
observer_instances << observer
|
70
|
-
end
|
71
|
-
|
72
|
-
# Notify list of observers of a change.
|
73
|
-
def notify_observers(*arg)
|
74
|
-
observer_instances.each { |observer| observer.update(*arg) }
|
75
|
-
end
|
76
|
-
|
77
|
-
# Total number of observers.
|
78
|
-
def count_observers
|
79
|
-
observer_instances.size
|
80
|
-
end
|
81
|
-
|
82
|
-
protected
|
83
|
-
def instantiate_observer(observer) #:nodoc:
|
84
|
-
# string/symbol
|
85
|
-
if observer.respond_to?(:to_sym)
|
86
|
-
observer.to_s.camelize.constantize.instance
|
87
|
-
elsif observer.respond_to?(:instance)
|
88
|
-
observer.instance
|
89
|
-
else
|
90
|
-
raise ArgumentError,
|
91
|
-
"#{observer} must be a lowercase, underscored class name (or an " +
|
92
|
-
"instance of the class itself) responding to the instance " +
|
93
|
-
"method. Example: Person.observers = :big_brother # calls " +
|
94
|
-
"BigBrother.instance"
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Notify observers when the observed class is subclassed.
|
99
|
-
def inherited(subclass)
|
100
|
-
super
|
101
|
-
notify_observers :observed_class_inherited, subclass
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
# Fires notifications to model's observers
|
107
|
-
#
|
108
|
-
# def save
|
109
|
-
# notify_observers(:before_save)
|
110
|
-
# ...
|
111
|
-
# notify_observers(:after_save)
|
112
|
-
# end
|
113
|
-
def notify_observers(method)
|
114
|
-
self.class.notify_observers(method, self)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# == Active Model Observers
|
119
|
-
#
|
120
|
-
# Observer classes respond to life cycle callbacks to implement trigger-like
|
121
|
-
# behavior outside the original class. This is a great way to reduce the
|
122
|
-
# clutter that normally comes when the model class is burdened with
|
123
|
-
# functionality that doesn't pertain to the core responsibility of the
|
124
|
-
# class. Example:
|
125
|
-
#
|
126
|
-
# class CommentObserver < ActiveModel::Observer
|
127
|
-
# def after_save(comment)
|
128
|
-
# Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
|
129
|
-
# end
|
130
|
-
# end
|
131
|
-
#
|
132
|
-
# This Observer sends an email when a Comment#save is finished.
|
133
|
-
#
|
134
|
-
# class ContactObserver < ActiveModel::Observer
|
135
|
-
# def after_create(contact)
|
136
|
-
# contact.logger.info('New contact added!')
|
137
|
-
# end
|
138
|
-
#
|
139
|
-
# def after_destroy(contact)
|
140
|
-
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
|
141
|
-
# end
|
142
|
-
# end
|
143
|
-
#
|
144
|
-
# This Observer uses logger to log when specific callbacks are triggered.
|
145
|
-
#
|
146
|
-
# == Observing a class that can't be inferred
|
147
|
-
#
|
148
|
-
# Observers will by default be mapped to the class with which they share a
|
149
|
-
# name. So CommentObserver will be tied to observing Comment, ProductManagerObserver
|
150
|
-
# to ProductManager, and so on. If you want to name your observer differently than
|
151
|
-
# the class you're interested in observing, you can use the <tt>Observer.observe</tt>
|
152
|
-
# class method which takes either the concrete class (Product) or a symbol for that
|
153
|
-
# class (:product):
|
154
|
-
#
|
155
|
-
# class AuditObserver < ActiveModel::Observer
|
156
|
-
# observe :account
|
157
|
-
#
|
158
|
-
# def after_update(account)
|
159
|
-
# AuditTrail.new(account, "UPDATED")
|
160
|
-
# end
|
161
|
-
# end
|
162
|
-
#
|
163
|
-
# If the audit observer needs to watch more than one kind of object, this can be
|
164
|
-
# specified with multiple arguments:
|
165
|
-
#
|
166
|
-
# class AuditObserver < ActiveModel::Observer
|
167
|
-
# observe :account, :balance
|
168
|
-
#
|
169
|
-
# def after_update(record)
|
170
|
-
# AuditTrail.new(record, "UPDATED")
|
171
|
-
# end
|
172
|
-
# end
|
173
|
-
#
|
174
|
-
# The AuditObserver will now act on both updates to Account and Balance by treating
|
175
|
-
# them both as records.
|
176
|
-
#
|
177
|
-
# If you're using an Observer in a Rails application with Active Record, be sure to
|
178
|
-
# read about the necessary configuration in the documentation for
|
179
|
-
# ActiveRecord::Observer.
|
180
|
-
#
|
181
|
-
class Observer
|
182
|
-
include Singleton
|
183
|
-
extend ActiveSupport::DescendantsTracker
|
184
|
-
|
185
|
-
class << self
|
186
|
-
# Attaches the observer to the supplied model classes.
|
187
|
-
def observe(*models)
|
188
|
-
models.flatten!
|
189
|
-
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
|
190
|
-
redefine_method(:observed_classes) { models }
|
191
|
-
end
|
192
|
-
|
193
|
-
# Returns an array of Classes to observe.
|
194
|
-
#
|
195
|
-
# You can override this instead of using the +observe+ helper.
|
196
|
-
#
|
197
|
-
# class AuditObserver < ActiveModel::Observer
|
198
|
-
# def self.observed_classes
|
199
|
-
# [Account, Balance]
|
200
|
-
# end
|
201
|
-
# end
|
202
|
-
def observed_classes
|
203
|
-
Array.wrap(observed_class)
|
204
|
-
end
|
205
|
-
|
206
|
-
# The class observed by default is inferred from the observer's class name:
|
207
|
-
# assert_equal Person, PersonObserver.observed_class
|
208
|
-
def observed_class
|
209
|
-
if observed_class_name = name[/(.*)Observer/, 1]
|
210
|
-
observed_class_name.constantize
|
211
|
-
else
|
212
|
-
nil
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# Start observing the declared classes and their subclasses.
|
218
|
-
def initialize
|
219
|
-
observed_classes.each { |klass| add_observer!(klass) }
|
220
|
-
end
|
221
|
-
|
222
|
-
def observed_classes #:nodoc:
|
223
|
-
self.class.observed_classes
|
224
|
-
end
|
225
|
-
|
226
|
-
# Send observed_method(object) if the method exists and
|
227
|
-
# the observer is enabled for the given object's class.
|
228
|
-
def update(observed_method, object, &block) #:nodoc:
|
229
|
-
return unless respond_to?(observed_method)
|
230
|
-
return if disabled_for?(object)
|
231
|
-
send(observed_method, object, &block)
|
232
|
-
end
|
233
|
-
|
234
|
-
# Special method sent by the observed class when it is inherited.
|
235
|
-
# Passes the new subclass.
|
236
|
-
def observed_class_inherited(subclass) #:nodoc:
|
237
|
-
self.class.observe(observed_classes + [subclass])
|
238
|
-
add_observer!(subclass)
|
239
|
-
end
|
240
|
-
|
241
|
-
protected
|
242
|
-
def add_observer!(klass) #:nodoc:
|
243
|
-
klass.add_observer(self)
|
244
|
-
end
|
245
|
-
|
246
|
-
def disabled_for?(object)
|
247
|
-
klass = object.class
|
248
|
-
return false unless klass.respond_to?(:observers)
|
249
|
-
klass.observers.disabled_for?(self)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|