activemodel 4.0.13 → 4.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activemodel might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -276
- data/README.rdoc +1 -1
- data/lib/active_model.rb +2 -4
- data/lib/active_model/attribute_methods.rb +12 -11
- data/lib/active_model/callbacks.rb +19 -22
- data/lib/active_model/conversion.rb +2 -2
- data/lib/active_model/dirty.rb +38 -20
- data/lib/active_model/errors.rb +11 -13
- data/lib/active_model/forbidden_attributes_protection.rb +0 -1
- data/lib/active_model/naming.rb +8 -8
- data/lib/active_model/secure_password.rb +17 -9
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/serializers/xml.rb +7 -7
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/validations.rb +4 -3
- data/lib/active_model/validations/acceptance.rb +4 -2
- data/lib/active_model/validations/callbacks.rb +7 -2
- data/lib/active_model/validations/clusivity.rb +15 -12
- data/lib/active_model/validations/confirmation.rb +8 -2
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +18 -16
- data/lib/active_model/validations/inclusion.rb +2 -3
- data/lib/active_model/validations/length.rb +2 -2
- data/lib/active_model/validations/numericality.rb +19 -19
- data/lib/active_model/validations/validates.rb +2 -2
- data/lib/active_model/validations/with.rb +2 -1
- data/lib/active_model/validator.rb +24 -10
- data/lib/active_model/version.rb +1 -1
- metadata +16 -17
- data/lib/active_model/deprecated_mass_assignment_security.rb +0 -21
@@ -41,7 +41,7 @@ module ActiveModel
|
|
41
41
|
end
|
42
42
|
|
43
43
|
# Returns an Enumerable of all key attributes if any is set, regardless if
|
44
|
-
# the object is persisted or not.
|
44
|
+
# the object is persisted or not. Returns +nil+ if there are no key attributes.
|
45
45
|
#
|
46
46
|
# class Person < ActiveRecord::Base
|
47
47
|
# end
|
@@ -62,7 +62,7 @@ module ActiveModel
|
|
62
62
|
# person = Person.create
|
63
63
|
# person.to_param # => "1"
|
64
64
|
def to_param
|
65
|
-
|
65
|
+
persisted? ? to_key.join('-') : nil
|
66
66
|
end
|
67
67
|
|
68
68
|
# Returns a +string+ identifying the path associated with the object.
|
data/lib/active_model/dirty.rb
CHANGED
@@ -14,13 +14,9 @@ module ActiveModel
|
|
14
14
|
# track.
|
15
15
|
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
|
16
16
|
# attribute.
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# @previously_changed = changes
|
22
|
-
#
|
23
|
-
# inside of your save or update method.
|
17
|
+
# * Call <tt>changes_applied</tt> after the changes are persisted.
|
18
|
+
# * Call <tt>reset_changes</tt> when you want to reset the changes
|
19
|
+
# information.
|
24
20
|
#
|
25
21
|
# A minimal implementation could be:
|
26
22
|
#
|
@@ -39,8 +35,12 @@ module ActiveModel
|
|
39
35
|
# end
|
40
36
|
#
|
41
37
|
# def save
|
42
|
-
#
|
43
|
-
#
|
38
|
+
# # do persistence work
|
39
|
+
# changes_applied
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def reload!
|
43
|
+
# reset_changes
|
44
44
|
# end
|
45
45
|
# end
|
46
46
|
#
|
@@ -65,6 +65,12 @@ module ActiveModel
|
|
65
65
|
# person.changed? # => false
|
66
66
|
# person.name_changed? # => false
|
67
67
|
#
|
68
|
+
# Reset the changes:
|
69
|
+
#
|
70
|
+
# person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
|
71
|
+
# person.reload!
|
72
|
+
# person.previous_changes # => {}
|
73
|
+
#
|
68
74
|
# Assigning the same value leaves the attribute unchanged:
|
69
75
|
#
|
70
76
|
# person.name = 'Bill'
|
@@ -91,7 +97,7 @@ module ActiveModel
|
|
91
97
|
|
92
98
|
included do
|
93
99
|
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
|
94
|
-
attribute_method_affix :
|
100
|
+
attribute_method_affix prefix: 'reset_', suffix: '!'
|
95
101
|
end
|
96
102
|
|
97
103
|
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
|
@@ -129,7 +135,7 @@ module ActiveModel
|
|
129
135
|
# person.save
|
130
136
|
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
131
137
|
def previous_changes
|
132
|
-
@previously_changed
|
138
|
+
@previously_changed ||= {}
|
133
139
|
end
|
134
140
|
|
135
141
|
# Returns a hash of the attributes with unsaved changes indicating their original
|
@@ -139,14 +145,31 @@ module ActiveModel
|
|
139
145
|
# person.name = 'robert'
|
140
146
|
# person.changed_attributes # => {"name" => "bob"}
|
141
147
|
def changed_attributes
|
142
|
-
@changed_attributes ||=
|
148
|
+
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
149
|
+
end
|
150
|
+
|
151
|
+
# Handle <tt>*_changed?</tt> for +method_missing+.
|
152
|
+
def attribute_changed?(attr) # :nodoc:
|
153
|
+
changed_attributes.include?(attr)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Handle <tt>*_was</tt> for +method_missing+.
|
157
|
+
def attribute_was(attr) # :nodoc:
|
158
|
+
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
|
143
159
|
end
|
144
160
|
|
145
161
|
private
|
146
162
|
|
147
|
-
#
|
148
|
-
def
|
149
|
-
|
163
|
+
# Removes current changes and makes them accessible through +previous_changes+.
|
164
|
+
def changes_applied
|
165
|
+
@previously_changed = changes
|
166
|
+
@changed_attributes = {}
|
167
|
+
end
|
168
|
+
|
169
|
+
# Removes all dirty data: current changes and previous changes
|
170
|
+
def reset_changes
|
171
|
+
@previously_changed = {}
|
172
|
+
@changed_attributes = {}
|
150
173
|
end
|
151
174
|
|
152
175
|
# Handle <tt>*_change</tt> for +method_missing+.
|
@@ -154,11 +177,6 @@ module ActiveModel
|
|
154
177
|
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
|
155
178
|
end
|
156
179
|
|
157
|
-
# Handle <tt>*_was</tt> for +method_missing+.
|
158
|
-
def attribute_was(attr)
|
159
|
-
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
|
160
|
-
end
|
161
|
-
|
162
180
|
# Handle <tt>*_will_change!</tt> for +method_missing+.
|
163
181
|
def attribute_will_change!(attr)
|
164
182
|
return if attribute_changed?(attr)
|
data/lib/active_model/errors.rb
CHANGED
@@ -231,7 +231,7 @@ module ActiveModel
|
|
231
231
|
# # <error>name must be specified</error>
|
232
232
|
# # </errors>
|
233
233
|
def to_xml(options={})
|
234
|
-
to_a.to_xml({ :
|
234
|
+
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
|
235
235
|
end
|
236
236
|
|
237
237
|
# Returns a Hash that can be used as the JSON representation for this
|
@@ -289,7 +289,7 @@ module ActiveModel
|
|
289
289
|
# # => NameIsInvalid: name is invalid
|
290
290
|
#
|
291
291
|
# person.errors.messages # => {}
|
292
|
-
def add(attribute, message =
|
292
|
+
def add(attribute, message = :invalid, options = {})
|
293
293
|
message = normalize_message(attribute, message, options)
|
294
294
|
if exception = options[:strict]
|
295
295
|
exception = ActiveModel::StrictValidationFailed if exception == true
|
@@ -331,7 +331,7 @@ module ActiveModel
|
|
331
331
|
#
|
332
332
|
# person.errors.add :name, :blank
|
333
333
|
# person.errors.added? :name, :blank # => true
|
334
|
-
def added?(attribute, message =
|
334
|
+
def added?(attribute, message = :invalid, options = {})
|
335
335
|
message = normalize_message(attribute, message, options)
|
336
336
|
self[attribute].include? message
|
337
337
|
end
|
@@ -370,11 +370,11 @@ module ActiveModel
|
|
370
370
|
def full_message(attribute, message)
|
371
371
|
return message if attribute == :base
|
372
372
|
attr_name = attribute.to_s.tr('.', '_').humanize
|
373
|
-
attr_name = @base.class.human_attribute_name(attribute, :
|
373
|
+
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
|
374
374
|
I18n.t(:"errors.format", {
|
375
|
-
:
|
376
|
-
:
|
377
|
-
:
|
375
|
+
default: "%{attribute} %{message}",
|
376
|
+
attribute: attr_name,
|
377
|
+
message: message
|
378
378
|
})
|
379
379
|
end
|
380
380
|
|
@@ -426,10 +426,10 @@ module ActiveModel
|
|
426
426
|
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
|
427
427
|
|
428
428
|
options = {
|
429
|
-
:
|
430
|
-
:
|
431
|
-
:
|
432
|
-
:
|
429
|
+
default: defaults,
|
430
|
+
model: @base.class.model_name.human,
|
431
|
+
attribute: @base.class.human_attribute_name(attribute),
|
432
|
+
value: value
|
433
433
|
}.merge!(options)
|
434
434
|
|
435
435
|
I18n.translate(key, options)
|
@@ -437,8 +437,6 @@ module ActiveModel
|
|
437
437
|
|
438
438
|
private
|
439
439
|
def normalize_message(attribute, message, options)
|
440
|
-
message ||= :invalid
|
441
|
-
|
442
440
|
case message
|
443
441
|
when Symbol
|
444
442
|
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
|
data/lib/active_model/naming.rb
CHANGED
@@ -129,7 +129,7 @@ module ActiveModel
|
|
129
129
|
#
|
130
130
|
# Equivalent to +to_s+.
|
131
131
|
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
|
132
|
-
:to_str, :
|
132
|
+
:to_str, to: :name
|
133
133
|
|
134
134
|
# Returns a new ActiveModel::Name instance. By default, the +namespace+
|
135
135
|
# and +name+ option will take the namespace and name of the given class
|
@@ -183,7 +183,7 @@ module ActiveModel
|
|
183
183
|
defaults << options[:default] if options[:default]
|
184
184
|
defaults << @human
|
185
185
|
|
186
|
-
options = { :
|
186
|
+
options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
|
187
187
|
I18n.translate(defaults.shift, options)
|
188
188
|
end
|
189
189
|
|
@@ -262,10 +262,10 @@ module ActiveModel
|
|
262
262
|
# namespaced models regarding whether it's inside isolated engine.
|
263
263
|
#
|
264
264
|
# # For isolated engine:
|
265
|
-
# ActiveModel::Naming.singular_route_key(Blog::Post)
|
265
|
+
# ActiveModel::Naming.singular_route_key(Blog::Post) # => "post"
|
266
266
|
#
|
267
267
|
# # For shared engine:
|
268
|
-
# ActiveModel::Naming.singular_route_key(Blog::Post)
|
268
|
+
# ActiveModel::Naming.singular_route_key(Blog::Post) # => "blog_post"
|
269
269
|
def self.singular_route_key(record_or_class)
|
270
270
|
model_name_from_record_or_class(record_or_class).singular_route_key
|
271
271
|
end
|
@@ -274,10 +274,10 @@ module ActiveModel
|
|
274
274
|
# namespaced models regarding whether it's inside isolated engine.
|
275
275
|
#
|
276
276
|
# # For isolated engine:
|
277
|
-
# ActiveModel::Naming.route_key(Blog::Post)
|
277
|
+
# ActiveModel::Naming.route_key(Blog::Post) # => "posts"
|
278
278
|
#
|
279
279
|
# # For shared engine:
|
280
|
-
# ActiveModel::Naming.route_key(Blog::Post)
|
280
|
+
# ActiveModel::Naming.route_key(Blog::Post) # => "blog_posts"
|
281
281
|
#
|
282
282
|
# The route key also considers if the noun is uncountable and, in
|
283
283
|
# such cases, automatically appends _index.
|
@@ -289,10 +289,10 @@ module ActiveModel
|
|
289
289
|
# namespaced models regarding whether it's inside isolated engine.
|
290
290
|
#
|
291
291
|
# # For isolated engine:
|
292
|
-
# ActiveModel::Naming.param_key(Blog::Post)
|
292
|
+
# ActiveModel::Naming.param_key(Blog::Post) # => "post"
|
293
293
|
#
|
294
294
|
# # For shared engine:
|
295
|
-
# ActiveModel::Naming.param_key(Blog::Post)
|
295
|
+
# ActiveModel::Naming.param_key(Blog::Post) # => "blog_post"
|
296
296
|
def self.param_key(record_or_class)
|
297
297
|
model_name_from_record_or_class(record_or_class).param_key
|
298
298
|
end
|
@@ -2,7 +2,9 @@ module ActiveModel
|
|
2
2
|
module SecurePassword
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
-
class << self
|
5
|
+
class << self
|
6
|
+
attr_accessor :min_cost # :nodoc:
|
7
|
+
end
|
6
8
|
self.min_cost = false
|
7
9
|
|
8
10
|
module ClassMethods
|
@@ -15,12 +17,12 @@ module ActiveModel
|
|
15
17
|
# argument. You can add more validations by hand if need be.
|
16
18
|
#
|
17
19
|
# If you don't need the confirmation validation, just don't set any
|
18
|
-
# value to the password_confirmation attribute and the
|
20
|
+
# value to the password_confirmation attribute and the validation
|
19
21
|
# will not be triggered.
|
20
22
|
#
|
21
|
-
# You need to add bcrypt (~> 3.1.
|
23
|
+
# You need to add bcrypt-ruby (~> 3.1.2) to Gemfile to use #has_secure_password:
|
22
24
|
#
|
23
|
-
# gem 'bcrypt', '~> 3.1.
|
25
|
+
# gem 'bcrypt-ruby', '~> 3.1.2'
|
24
26
|
#
|
25
27
|
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
|
26
28
|
#
|
@@ -40,13 +42,13 @@ module ActiveModel
|
|
40
42
|
# User.find_by(name: 'david').try(:authenticate, 'notright') # => false
|
41
43
|
# User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
|
42
44
|
def has_secure_password(options = {})
|
43
|
-
# Load bcrypt
|
45
|
+
# Load bcrypt-ruby only when has_secure_password is used.
|
44
46
|
# This is to avoid ActiveModel (and by extension the entire framework)
|
45
47
|
# being dependent on a binary library.
|
46
48
|
begin
|
47
49
|
require 'bcrypt'
|
48
50
|
rescue LoadError
|
49
|
-
$stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install"
|
51
|
+
$stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
|
50
52
|
raise
|
51
53
|
end
|
52
54
|
|
@@ -55,9 +57,9 @@ module ActiveModel
|
|
55
57
|
include InstanceMethodsOnActivation
|
56
58
|
|
57
59
|
if options.fetch(:validations, true)
|
58
|
-
validates_confirmation_of :password, if:
|
59
|
-
validates_presence_of :password, :
|
60
|
-
validates_presence_of :password_confirmation, if:
|
60
|
+
validates_confirmation_of :password, if: :should_confirm_password?
|
61
|
+
validates_presence_of :password, on: :create
|
62
|
+
validates_presence_of :password_confirmation, if: :should_confirm_password?
|
61
63
|
|
62
64
|
before_create { raise "Password digest missing on new record" if password_digest.blank? }
|
63
65
|
end
|
@@ -108,6 +110,12 @@ module ActiveModel
|
|
108
110
|
def password_confirmation=(unencrypted_password)
|
109
111
|
@password_confirmation = unencrypted_password
|
110
112
|
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def should_confirm_password?
|
117
|
+
password_confirmation && password.present?
|
118
|
+
end
|
111
119
|
end
|
112
120
|
end
|
113
121
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'active_support/core_ext/
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
2
|
require 'active_support/core_ext/array/conversions'
|
3
3
|
require 'active_support/core_ext/hash/conversions'
|
4
4
|
require 'active_support/core_ext/hash/slice'
|
@@ -79,7 +79,7 @@ module ActiveModel
|
|
79
79
|
require 'builder' unless defined? ::Builder
|
80
80
|
|
81
81
|
options[:indent] ||= 2
|
82
|
-
options[:builder] ||= ::Builder::XmlMarkup.new(:
|
82
|
+
options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
|
83
83
|
|
84
84
|
@builder = options[:builder]
|
85
85
|
@builder.instruct! unless options[:skip_instruct]
|
@@ -88,8 +88,8 @@ module ActiveModel
|
|
88
88
|
root = ActiveSupport::XmlMini.rename_key(root, options)
|
89
89
|
|
90
90
|
args = [root]
|
91
|
-
args << {:
|
92
|
-
args << {:
|
91
|
+
args << { xmlns: options[:namespace] } if options[:namespace]
|
92
|
+
args << { type: options[:type] } if options[:type] && !options[:skip_types]
|
93
93
|
|
94
94
|
@builder.tag!(*args) do
|
95
95
|
add_attributes_and_methods
|
@@ -132,7 +132,7 @@ module ActiveModel
|
|
132
132
|
records = records.to_ary
|
133
133
|
|
134
134
|
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
|
135
|
-
type = options[:skip_types] ? { } : {:
|
135
|
+
type = options[:skip_types] ? { } : { type: "array" }
|
136
136
|
association_name = association.to_s.singularize
|
137
137
|
merged_options[:root] = association_name
|
138
138
|
|
@@ -145,7 +145,7 @@ module ActiveModel
|
|
145
145
|
record_type = {}
|
146
146
|
else
|
147
147
|
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
|
148
|
-
record_type = {:
|
148
|
+
record_type = { type: record_class }
|
149
149
|
end
|
150
150
|
|
151
151
|
record.to_xml merged_options.merge(record_type)
|
@@ -205,7 +205,7 @@ module ActiveModel
|
|
205
205
|
Serializer.new(self, options).serialize(&block)
|
206
206
|
end
|
207
207
|
|
208
|
-
# Sets the model +attributes+ from
|
208
|
+
# Sets the model +attributes+ from an XML string. Returns +self+.
|
209
209
|
#
|
210
210
|
# class Person
|
211
211
|
# include ActiveModel::Serializers::Xml
|
@@ -41,7 +41,7 @@ module ActiveModel
|
|
41
41
|
#
|
42
42
|
# Specify +options+ with additional translating options.
|
43
43
|
def human_attribute_name(attribute, options = {})
|
44
|
-
options = { :
|
44
|
+
options = { count: 1 }.merge!(options)
|
45
45
|
parts = attribute.to_s.split(".")
|
46
46
|
attribute = parts.pop
|
47
47
|
namespace = parts.join("/") unless parts.empty?
|
@@ -46,7 +46,7 @@ module ActiveModel
|
|
46
46
|
include HelperMethods
|
47
47
|
|
48
48
|
attr_accessor :validation_context
|
49
|
-
define_callbacks :validate, :
|
49
|
+
define_callbacks :validate, scope: :name
|
50
50
|
|
51
51
|
class_attribute :_validators
|
52
52
|
self._validators = Hash.new { |h,k| h[k] = [] }
|
@@ -142,7 +142,9 @@ module ActiveModel
|
|
142
142
|
if options.key?(:on)
|
143
143
|
options = options.dup
|
144
144
|
options[:if] = Array(options[:if])
|
145
|
-
options[:if].unshift
|
145
|
+
options[:if].unshift lambda { |o|
|
146
|
+
o.validation_context == options[:on]
|
147
|
+
}
|
146
148
|
end
|
147
149
|
args << options
|
148
150
|
set_callback(:validate, *args, &block)
|
@@ -226,7 +228,6 @@ module ActiveModel
|
|
226
228
|
# Person.validators_on(:name)
|
227
229
|
# # => [
|
228
230
|
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
|
229
|
-
# # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}>
|
230
231
|
# # ]
|
231
232
|
def validators_on(*attributes)
|
232
233
|
attributes.flat_map do |attribute|
|
@@ -3,7 +3,8 @@ module ActiveModel
|
|
3
3
|
module Validations
|
4
4
|
class AcceptanceValidator < EachValidator # :nodoc:
|
5
5
|
def initialize(options)
|
6
|
-
super({ :
|
6
|
+
super({ allow_nil: true, accept: "1" }.merge!(options))
|
7
|
+
setup!(options[:class])
|
7
8
|
end
|
8
9
|
|
9
10
|
def validate_each(record, attribute, value)
|
@@ -12,7 +13,8 @@ module ActiveModel
|
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
|
+
private
|
17
|
+
def setup!(klass)
|
16
18
|
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
17
19
|
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
18
20
|
klass.send(:attr_reader, *attr_readers)
|
@@ -22,7 +22,10 @@ module ActiveModel
|
|
22
22
|
|
23
23
|
included do
|
24
24
|
include ActiveSupport::Callbacks
|
25
|
-
define_callbacks :validation,
|
25
|
+
define_callbacks :validation,
|
26
|
+
terminator: ->(_,result) { result == false },
|
27
|
+
skip_after_callbacks_if_terminated: true,
|
28
|
+
scope: [:kind, :name]
|
26
29
|
end
|
27
30
|
|
28
31
|
module ClassMethods
|
@@ -55,7 +58,9 @@ module ActiveModel
|
|
55
58
|
if options.is_a?(Hash) && options[:on]
|
56
59
|
options[:if] = Array(options[:if])
|
57
60
|
options[:on] = Array(options[:on])
|
58
|
-
options[:if].unshift
|
61
|
+
options[:if].unshift lambda { |o|
|
62
|
+
options[:on].include? o.validation_context
|
63
|
+
}
|
59
64
|
end
|
60
65
|
set_callback(:validation, :before, *args, &block)
|
61
66
|
end
|