activemodel 3.1.12 → 3.2.0.rc1
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/CHANGELOG.md +81 -36
- data/README.rdoc +1 -1
- data/lib/active_model/attribute_methods.rb +123 -104
- data/lib/active_model/callbacks.rb +2 -2
- data/lib/active_model/conversion.rb +26 -2
- data/lib/active_model/dirty.rb +3 -3
- data/lib/active_model/errors.rb +63 -51
- data/lib/active_model/lint.rb +12 -3
- data/lib/active_model/mass_assignment_security.rb +27 -8
- data/lib/active_model/mass_assignment_security/permission_set.rb +5 -5
- data/lib/active_model/mass_assignment_security/sanitizer.rb +42 -6
- data/lib/active_model/naming.rb +18 -10
- data/lib/active_model/observer_array.rb +3 -3
- data/lib/active_model/observing.rb +1 -2
- data/lib/active_model/secure_password.rb +2 -2
- data/lib/active_model/serialization.rb +61 -10
- data/lib/active_model/serializers/json.rb +20 -14
- data/lib/active_model/serializers/xml.rb +55 -31
- data/lib/active_model/translation.rb +15 -3
- data/lib/active_model/validations.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +3 -1
- data/lib/active_model/validations/confirmation.rb +3 -1
- data/lib/active_model/validations/exclusion.rb +5 -3
- data/lib/active_model/validations/format.rb +4 -2
- data/lib/active_model/validations/inclusion.rb +5 -3
- data/lib/active_model/validations/length.rb +22 -10
- data/lib/active_model/validations/numericality.rb +4 -2
- data/lib/active_model/validations/presence.rb +5 -3
- data/lib/active_model/validations/validates.rb +15 -3
- data/lib/active_model/validations/with.rb +4 -2
- data/lib/active_model/version.rb +3 -3
- metadata +21 -28
- checksums.yaml +0 -7
@@ -59,7 +59,7 @@ module ActiveModel
|
|
59
59
|
# define_model_callbacks :initializer, :only => :after
|
60
60
|
#
|
61
61
|
# Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on
|
62
|
-
# that method call.
|
62
|
+
# that method call. To get around this you can call the define_model_callbacks
|
63
63
|
# method as many times as you need.
|
64
64
|
#
|
65
65
|
# define_model_callbacks :create, :only => :after
|
@@ -93,7 +93,7 @@ module ActiveModel
|
|
93
93
|
:only => [:before, :around, :after]
|
94
94
|
}.merge(options)
|
95
95
|
|
96
|
-
types
|
96
|
+
types = Array.wrap(options.delete(:only))
|
97
97
|
|
98
98
|
callbacks.each do |callback|
|
99
99
|
define_callbacks(callback, options)
|
@@ -1,9 +1,12 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
1
4
|
module ActiveModel
|
2
5
|
# == Active Model Conversions
|
3
6
|
#
|
4
|
-
# Handles default conversions: to_model, to_key and
|
7
|
+
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
|
5
8
|
#
|
6
|
-
# Let's take for example this non
|
9
|
+
# Let's take for example this non-persisted object.
|
7
10
|
#
|
8
11
|
# class ContactMessage
|
9
12
|
# include ActiveModel::Conversion
|
@@ -18,8 +21,11 @@ module ActiveModel
|
|
18
21
|
# cm.to_model == self # => true
|
19
22
|
# cm.to_key # => nil
|
20
23
|
# cm.to_param # => nil
|
24
|
+
# cm.to_path # => "contact_messages/contact_message"
|
21
25
|
#
|
22
26
|
module Conversion
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
23
29
|
# If your object is already designed to implement all of the Active Model
|
24
30
|
# you can use the default <tt>:to_model</tt> implementation, which simply
|
25
31
|
# returns self.
|
@@ -45,5 +51,23 @@ module ActiveModel
|
|
45
51
|
def to_param
|
46
52
|
persisted? ? to_key.join('-') : nil
|
47
53
|
end
|
54
|
+
|
55
|
+
# Returns a string identifying the path associated with the object.
|
56
|
+
# ActionPack uses this to find a suitable partial to represent the object.
|
57
|
+
def to_partial_path
|
58
|
+
self.class._to_partial_path
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassMethods #:nodoc:
|
62
|
+
# Provide a class level cache for the to_path. This is an
|
63
|
+
# internal method and should not be accessed directly.
|
64
|
+
def _to_partial_path #:nodoc:
|
65
|
+
@_to_partial_path ||= begin
|
66
|
+
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self))
|
67
|
+
collection = ActiveSupport::Inflector.tableize(self)
|
68
|
+
"#{collection}/#{element}".freeze
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
48
72
|
end
|
49
73
|
end
|
data/lib/active_model/dirty.rb
CHANGED
@@ -29,7 +29,7 @@ module ActiveModel
|
|
29
29
|
#
|
30
30
|
# include ActiveModel::Dirty
|
31
31
|
#
|
32
|
-
# define_attribute_methods
|
32
|
+
# define_attribute_methods [:name]
|
33
33
|
#
|
34
34
|
# def name
|
35
35
|
# @name
|
@@ -98,7 +98,7 @@ module ActiveModel
|
|
98
98
|
# person.name = 'bob'
|
99
99
|
# person.changed? # => true
|
100
100
|
def changed?
|
101
|
-
|
101
|
+
changed_attributes.any?
|
102
102
|
end
|
103
103
|
|
104
104
|
# List of attributes with unsaved changes.
|
@@ -156,7 +156,7 @@ module ActiveModel
|
|
156
156
|
rescue TypeError, NoMethodError
|
157
157
|
end
|
158
158
|
|
159
|
-
changed_attributes[attr] = value
|
159
|
+
changed_attributes[attr] = value unless changed_attributes.include?(attr)
|
160
160
|
end
|
161
161
|
|
162
162
|
# Handle <tt>reset_*!</tt> for +method_missing+.
|
data/lib/active_model/errors.rb
CHANGED
@@ -49,8 +49,8 @@ module ActiveModel
|
|
49
49
|
#
|
50
50
|
# The last three methods are required in your object for Errors to be
|
51
51
|
# able to generate error messages correctly and also handle multiple
|
52
|
-
# languages.
|
53
|
-
# you will not need to implement the last two.
|
52
|
+
# languages. Of course, if you extend your object with ActiveModel::Translation
|
53
|
+
# you will not need to implement the last two. Likewise, using
|
54
54
|
# ActiveModel::Validations will handle the validation related methods
|
55
55
|
# for you.
|
56
56
|
#
|
@@ -63,7 +63,7 @@ module ActiveModel
|
|
63
63
|
class Errors
|
64
64
|
include Enumerable
|
65
65
|
|
66
|
-
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
|
66
|
+
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
|
67
67
|
|
68
68
|
attr_reader :messages
|
69
69
|
|
@@ -79,19 +79,6 @@ module ActiveModel
|
|
79
79
|
@messages = ActiveSupport::OrderedHash.new
|
80
80
|
end
|
81
81
|
|
82
|
-
def initialize_dup(other)
|
83
|
-
@messages = other.messages.dup
|
84
|
-
end
|
85
|
-
|
86
|
-
# Backport dup from 1.9 so that #initialize_dup gets called
|
87
|
-
unless Object.respond_to?(:initialize_dup)
|
88
|
-
def dup # :nodoc:
|
89
|
-
copy = super
|
90
|
-
copy.initialize_dup(self)
|
91
|
-
copy
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
82
|
# Clear the messages
|
96
83
|
def clear
|
97
84
|
messages.clear
|
@@ -101,6 +88,7 @@ module ActiveModel
|
|
101
88
|
def include?(error)
|
102
89
|
(v = messages[error]) && v.any?
|
103
90
|
end
|
91
|
+
alias :has_key? :include?
|
104
92
|
|
105
93
|
# Get messages for +key+
|
106
94
|
def get(key)
|
@@ -112,11 +100,6 @@ module ActiveModel
|
|
112
100
|
messages[key] = value
|
113
101
|
end
|
114
102
|
|
115
|
-
# Delete messages for +key+
|
116
|
-
def delete(key)
|
117
|
-
messages.delete(key)
|
118
|
-
end
|
119
|
-
|
120
103
|
# When passed a symbol or a name of a method, returns an array of errors
|
121
104
|
# for the method.
|
122
105
|
#
|
@@ -131,11 +114,11 @@ module ActiveModel
|
|
131
114
|
# p.errors[:name] = "must be set"
|
132
115
|
# p.errors[:name] # => ['must be set']
|
133
116
|
def []=(attribute, error)
|
134
|
-
self[attribute] << error
|
117
|
+
self[attribute.to_sym] << error
|
135
118
|
end
|
136
119
|
|
137
120
|
# Iterates through each error key, value pair in the error messages hash.
|
138
|
-
# Yields the attribute and the error for that attribute.
|
121
|
+
# Yields the attribute and the error for that attribute. If the attribute
|
139
122
|
# has more than one error message, yields once for each error message.
|
140
123
|
#
|
141
124
|
# p.errors.add(:name, "can't be blank")
|
@@ -193,10 +176,12 @@ module ActiveModel
|
|
193
176
|
end
|
194
177
|
|
195
178
|
# Returns true if no errors are found, false otherwise.
|
179
|
+
# If the error message is a string it can be empty.
|
196
180
|
def empty?
|
197
|
-
all? { |k, v| v && v.empty? }
|
181
|
+
all? { |k, v| v && v.empty? && !v.is_a?(String) }
|
198
182
|
end
|
199
183
|
alias_method :blank?, :empty?
|
184
|
+
|
200
185
|
# Returns an xml formatted representation of the Errors hash.
|
201
186
|
#
|
202
187
|
# p.errors.add(:name, "can't be blank")
|
@@ -221,20 +206,16 @@ module ActiveModel
|
|
221
206
|
messages.dup
|
222
207
|
end
|
223
208
|
|
224
|
-
# Adds +message+ to the error messages on +attribute
|
225
|
-
#
|
226
|
-
# +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
209
|
+
# Adds +message+ to the error messages on +attribute+. More than one error can be added to the same
|
210
|
+
# +attribute+.
|
227
211
|
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
|
228
212
|
#
|
229
213
|
# If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
|
230
214
|
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
|
231
215
|
def add(attribute, message = nil, options = {})
|
232
|
-
message
|
233
|
-
|
234
|
-
|
235
|
-
message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
|
236
|
-
elsif message.is_a?(Proc)
|
237
|
-
message = message.call
|
216
|
+
message = normalize_message(attribute, message, options)
|
217
|
+
if options[:strict]
|
218
|
+
raise ActiveModel::StrictValidationFailed, message
|
238
219
|
end
|
239
220
|
|
240
221
|
self[attribute] << message
|
@@ -257,6 +238,15 @@ module ActiveModel
|
|
257
238
|
end
|
258
239
|
end
|
259
240
|
|
241
|
+
# Returns true if an error on the attribute with the given message is present, false otherwise.
|
242
|
+
# +message+ is treated the same as for +add+.
|
243
|
+
# p.errors.add :name, :blank
|
244
|
+
# p.errors.added? :name, :blank # => true
|
245
|
+
def added?(attribute, message = nil, options = {})
|
246
|
+
message = normalize_message(attribute, message, options)
|
247
|
+
self[attribute].include? message
|
248
|
+
end
|
249
|
+
|
260
250
|
# Returns all the full error messages in an array.
|
261
251
|
#
|
262
252
|
# class Company
|
@@ -268,20 +258,22 @@ module ActiveModel
|
|
268
258
|
# company.errors.full_messages # =>
|
269
259
|
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
|
270
260
|
def full_messages
|
271
|
-
map { |attribute, message|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
261
|
+
map { |attribute, message| full_message(attribute, message) }
|
262
|
+
end
|
263
|
+
|
264
|
+
# Returns a full message for a given attribute.
|
265
|
+
#
|
266
|
+
# company.errors.full_message(:name, "is invalid") # =>
|
267
|
+
# "Name is invalid"
|
268
|
+
def full_message(attribute, message)
|
269
|
+
return message if attribute == :base
|
270
|
+
attr_name = attribute.to_s.gsub('.', '_').humanize
|
271
|
+
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
|
272
|
+
I18n.t(:"errors.format", {
|
273
|
+
:default => "%{attribute} %{message}",
|
274
|
+
:attribute => attr_name,
|
275
|
+
:message => message
|
276
|
+
})
|
285
277
|
end
|
286
278
|
|
287
279
|
# Translates an error message in its default scope
|
@@ -311,13 +303,17 @@ module ActiveModel
|
|
311
303
|
def generate_message(attribute, type = :invalid, options = {})
|
312
304
|
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
313
305
|
|
314
|
-
|
315
|
-
|
316
|
-
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}"
|
306
|
+
if @base.class.respond_to?(:i18n_scope)
|
307
|
+
defaults = @base.class.lookup_ancestors.map do |klass|
|
308
|
+
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
|
309
|
+
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
|
310
|
+
end
|
311
|
+
else
|
312
|
+
defaults = []
|
317
313
|
end
|
318
314
|
|
319
315
|
defaults << options.delete(:message)
|
320
|
-
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}"
|
316
|
+
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
|
321
317
|
defaults << :"errors.attributes.#{attribute}.#{type}"
|
322
318
|
defaults << :"errors.messages.#{type}"
|
323
319
|
|
@@ -336,5 +332,21 @@ module ActiveModel
|
|
336
332
|
|
337
333
|
I18n.translate(key, options)
|
338
334
|
end
|
335
|
+
|
336
|
+
private
|
337
|
+
def normalize_message(attribute, message, options)
|
338
|
+
message ||= :invalid
|
339
|
+
|
340
|
+
if message.is_a?(Symbol)
|
341
|
+
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
|
342
|
+
elsif message.is_a?(Proc)
|
343
|
+
message.call
|
344
|
+
else
|
345
|
+
message
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
class StrictValidationFailed < StandardError
|
339
351
|
end
|
340
352
|
end
|
data/lib/active_model/lint.rb
CHANGED
@@ -43,6 +43,16 @@ module ActiveModel
|
|
43
43
|
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
|
44
44
|
end
|
45
45
|
|
46
|
+
# == Responds to <tt>to_partial_path</tt>
|
47
|
+
#
|
48
|
+
# Returns a string giving a relative path. This is used for looking up
|
49
|
+
# partials. For example, a BlogPost model might return "blog_posts/blog_post"
|
50
|
+
#
|
51
|
+
def test_to_partial_path
|
52
|
+
assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
|
53
|
+
assert_kind_of String, model.to_partial_path
|
54
|
+
end
|
55
|
+
|
46
56
|
# == Responds to <tt>valid?</tt>
|
47
57
|
#
|
48
58
|
# Returns a boolean that specifies whether the object is in a valid or invalid
|
@@ -66,15 +76,14 @@ module ActiveModel
|
|
66
76
|
|
67
77
|
# == Naming
|
68
78
|
#
|
69
|
-
# Model.model_name must return a string with some convenience methods
|
70
|
-
# :human and :
|
79
|
+
# Model.model_name must return a string with some convenience methods:
|
80
|
+
# :human, :singular, and :plural. Check ActiveModel::Naming for more information.
|
71
81
|
#
|
72
82
|
def test_model_naming
|
73
83
|
assert model.class.respond_to?(:model_name), "The model should respond to model_name"
|
74
84
|
model_name = model.class.model_name
|
75
85
|
assert_kind_of String, model_name
|
76
86
|
assert_kind_of String, model_name.human
|
77
|
-
assert_kind_of String, model_name.partial_path
|
78
87
|
assert_kind_of String, model_name.singular
|
79
88
|
assert_kind_of String, model_name.plural
|
80
89
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
-
require 'active_support/core_ext/class/attribute
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
2
3
|
require 'active_support/core_ext/array/wrap'
|
3
4
|
require 'active_model/mass_assignment_security/permission_set'
|
5
|
+
require 'active_model/mass_assignment_security/sanitizer'
|
4
6
|
|
5
7
|
module ActiveModel
|
6
8
|
# = Active Model Mass-Assignment Security
|
@@ -11,6 +13,9 @@ module ActiveModel
|
|
11
13
|
class_attribute :_accessible_attributes
|
12
14
|
class_attribute :_protected_attributes
|
13
15
|
class_attribute :_active_authorizer
|
16
|
+
|
17
|
+
class_attribute :_mass_assignment_sanitizer
|
18
|
+
self.mass_assignment_sanitizer = :logger
|
14
19
|
end
|
15
20
|
|
16
21
|
# Mass assignment security provides an interface for protecting attributes
|
@@ -42,6 +47,16 @@ module ActiveModel
|
|
42
47
|
#
|
43
48
|
# end
|
44
49
|
#
|
50
|
+
# = Configuration options
|
51
|
+
#
|
52
|
+
# * <tt>mass_assignment_sanitizer</tt> - Defines sanitize method. Possible values are:
|
53
|
+
# * <tt>:logger</tt> (default) - writes filtered attributes to logger
|
54
|
+
# * <tt>:strict</tt> - raise <tt>ActiveModel::MassAssignmentSecurity::Error</tt> on any protected attribute update
|
55
|
+
#
|
56
|
+
# You can specify your own sanitizer object eg. MySanitizer.new.
|
57
|
+
# See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for example implementation.
|
58
|
+
#
|
59
|
+
#
|
45
60
|
module ClassMethods
|
46
61
|
# Attributes named in this macro are protected from mass-assignment
|
47
62
|
# whenever attributes are sanitized before assignment. A role for the
|
@@ -184,21 +199,25 @@ module ActiveModel
|
|
184
199
|
[]
|
185
200
|
end
|
186
201
|
|
202
|
+
def mass_assignment_sanitizer=(value)
|
203
|
+
self._mass_assignment_sanitizer = if value.is_a?(Symbol)
|
204
|
+
const_get(:"#{value.to_s.camelize}Sanitizer").new(self)
|
205
|
+
else
|
206
|
+
value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
187
210
|
private
|
188
211
|
|
189
212
|
def protected_attributes_configs
|
190
213
|
self._protected_attributes ||= begin
|
191
|
-
|
192
|
-
w.logger = self.logger if self.respond_to?(:logger)
|
193
|
-
end
|
194
|
-
Hash.new(default_black_list)
|
214
|
+
Hash.new { |h,k| h[k] = BlackList.new(attributes_protected_by_default) }
|
195
215
|
end
|
196
216
|
end
|
197
217
|
|
198
218
|
def accessible_attributes_configs
|
199
219
|
self._accessible_attributes ||= begin
|
200
|
-
|
201
|
-
Hash.new(default_white_list)
|
220
|
+
Hash.new { |h,k| h[k] = WhiteList.new }
|
202
221
|
end
|
203
222
|
end
|
204
223
|
end
|
@@ -206,7 +225,7 @@ module ActiveModel
|
|
206
225
|
protected
|
207
226
|
|
208
227
|
def sanitize_for_mass_assignment(attributes, role = :default)
|
209
|
-
|
228
|
+
_mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role))
|
210
229
|
end
|
211
230
|
|
212
231
|
def mass_assignment_authorizer(role = :default)
|
@@ -1,10 +1,8 @@
|
|
1
1
|
require 'set'
|
2
|
-
require 'active_model/mass_assignment_security/sanitizer'
|
3
2
|
|
4
3
|
module ActiveModel
|
5
4
|
module MassAssignmentSecurity
|
6
5
|
class PermissionSet < Set
|
7
|
-
attr_accessor :logger
|
8
6
|
|
9
7
|
def +(values)
|
10
8
|
super(values.map(&:to_s))
|
@@ -14,15 +12,18 @@ module ActiveModel
|
|
14
12
|
super(remove_multiparameter_id(key))
|
15
13
|
end
|
16
14
|
|
15
|
+
def deny?(key)
|
16
|
+
raise NotImplementedError, "#deny?(key) suppose to be overwritten"
|
17
|
+
end
|
18
|
+
|
17
19
|
protected
|
18
20
|
|
19
21
|
def remove_multiparameter_id(key)
|
20
|
-
key.to_s.gsub(/\(
|
22
|
+
key.to_s.gsub(/\(.+/, '')
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
class WhiteList < PermissionSet
|
25
|
-
include Sanitizer
|
26
27
|
|
27
28
|
def deny?(key)
|
28
29
|
!include?(key)
|
@@ -30,7 +31,6 @@ module ActiveModel
|
|
30
31
|
end
|
31
32
|
|
32
33
|
class BlackList < PermissionSet
|
33
|
-
include Sanitizer
|
34
34
|
|
35
35
|
def deny?(key)
|
36
36
|
include?(key)
|