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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -64
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +61 -24
  5. data/lib/active_model.rb +21 -11
  6. data/lib/active_model/attribute_methods.rb +150 -125
  7. data/lib/active_model/callbacks.rb +49 -34
  8. data/lib/active_model/conversion.rb +39 -19
  9. data/lib/active_model/deprecated_mass_assignment_security.rb +21 -0
  10. data/lib/active_model/dirty.rb +48 -32
  11. data/lib/active_model/errors.rb +176 -88
  12. data/lib/active_model/forbidden_attributes_protection.rb +27 -0
  13. data/lib/active_model/lint.rb +42 -55
  14. data/lib/active_model/locale/en.yml +3 -1
  15. data/lib/active_model/model.rb +97 -0
  16. data/lib/active_model/naming.rb +191 -51
  17. data/lib/active_model/railtie.rb +11 -1
  18. data/lib/active_model/secure_password.rb +55 -25
  19. data/lib/active_model/serialization.rb +51 -27
  20. data/lib/active_model/serializers/json.rb +83 -46
  21. data/lib/active_model/serializers/xml.rb +46 -12
  22. data/lib/active_model/test_case.rb +0 -12
  23. data/lib/active_model/translation.rb +9 -10
  24. data/lib/active_model/validations.rb +154 -52
  25. data/lib/active_model/validations/absence.rb +31 -0
  26. data/lib/active_model/validations/acceptance.rb +10 -22
  27. data/lib/active_model/validations/callbacks.rb +78 -25
  28. data/lib/active_model/validations/clusivity.rb +41 -0
  29. data/lib/active_model/validations/confirmation.rb +13 -23
  30. data/lib/active_model/validations/exclusion.rb +26 -55
  31. data/lib/active_model/validations/format.rb +44 -34
  32. data/lib/active_model/validations/inclusion.rb +22 -52
  33. data/lib/active_model/validations/length.rb +48 -49
  34. data/lib/active_model/validations/numericality.rb +30 -32
  35. data/lib/active_model/validations/presence.rb +12 -22
  36. data/lib/active_model/validations/validates.rb +68 -36
  37. data/lib/active_model/validations/with.rb +28 -23
  38. data/lib/active_model/validator.rb +22 -22
  39. data/lib/active_model/version.rb +4 -4
  40. metadata +23 -24
  41. data/lib/active_model/mass_assignment_security.rb +0 -237
  42. data/lib/active_model/mass_assignment_security/permission_set.rb +0 -40
  43. data/lib/active_model/mass_assignment_security/sanitizer.rb +0 -59
  44. data/lib/active_model/observer_array.rb +0 -147
  45. data/lib/active_model/observing.rb +0 -252
@@ -1,8 +1,8 @@
1
- require 'active_support/core_ext/array/wrap'
2
1
  require 'active_support/core_ext/class/attribute_accessors'
3
2
  require 'active_support/core_ext/array/conversions'
4
3
  require 'active_support/core_ext/hash/conversions'
5
4
  require 'active_support/core_ext/hash/slice'
5
+ require 'active_support/core_ext/time/acts_like'
6
6
 
7
7
  module ActiveModel
8
8
  module Serializers
@@ -21,7 +21,11 @@ module ActiveModel
21
21
 
22
22
  def initialize(name, serializable, value)
23
23
  @name, @serializable = name, serializable
24
- value = value.in_time_zone if value.respond_to?(:in_time_zone)
24
+
25
+ if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
26
+ value = value.in_time_zone
27
+ end
28
+
25
29
  @value = value
26
30
  @type = compute_type
27
31
  end
@@ -60,7 +64,7 @@ module ActiveModel
60
64
  end
61
65
 
62
66
  def serializable_collection
63
- methods = Array.wrap(options[:methods]).map(&:to_s)
67
+ methods = Array(options[:methods]).map(&:to_s)
64
68
  serializable_hash.map do |name, value|
65
69
  name = name.to_s
66
70
  if methods.include?(name)
@@ -115,12 +119,18 @@ module ActiveModel
115
119
  end
116
120
  end
117
121
 
118
- # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
122
+ # TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
119
123
  def add_associations(association, records, opts)
120
124
  merged_options = opts.merge(options.slice(:builder, :indent))
121
125
  merged_options[:skip_instruct] = true
122
126
 
123
- if records.is_a?(Enumerable)
127
+ [:skip_types, :dasherize, :camelize].each do |key|
128
+ merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
129
+ end
130
+
131
+ if records.respond_to?(:to_ary)
132
+ records = records.to_ary
133
+
124
134
  tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
125
135
  type = options[:skip_types] ? { } : {:type => "array"}
126
136
  association_name = association.to_s.singularize
@@ -155,7 +165,7 @@ module ActiveModel
155
165
 
156
166
  def add_procs
157
167
  if procs = options.delete(:procs)
158
- Array.wrap(procs).each do |proc|
168
+ Array(procs).each do |proc|
159
169
  if proc.arity == 1
160
170
  proc.call(options)
161
171
  else
@@ -169,8 +179,8 @@ module ActiveModel
169
179
  # Returns XML representing the model. Configuration can be
170
180
  # passed through +options+.
171
181
  #
172
- # Without any +options+, the returned XML string will include all the model's
173
- # attributes. For example:
182
+ # Without any +options+, the returned XML string will include all the
183
+ # model's attributes.
174
184
  #
175
185
  # user = User.find(1)
176
186
  # user.to_xml
@@ -180,21 +190,45 @@ module ActiveModel
180
190
  # <id type="integer">1</id>
181
191
  # <name>David</name>
182
192
  # <age type="integer">16</age>
183
- # <created-at type="datetime">2011-01-30T22:29:23Z</created-at>
193
+ # <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
184
194
  # </user>
185
195
  #
186
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
187
- # included, and work similar to the +attributes+ method.
196
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
197
+ # attributes included, and work similar to the +attributes+ method.
188
198
  #
189
199
  # To include the result of some method calls on the model use <tt>:methods</tt>.
190
200
  #
191
201
  # To include associations use <tt>:include</tt>.
192
202
  #
193
- # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml.
203
+ # For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
194
204
  def to_xml(options = {}, &block)
195
205
  Serializer.new(self, options).serialize(&block)
196
206
  end
197
207
 
208
+ # Sets the model +attributes+ from a JSON string. Returns +self+.
209
+ #
210
+ # class Person
211
+ # include ActiveModel::Serializers::Xml
212
+ #
213
+ # attr_accessor :name, :age, :awesome
214
+ #
215
+ # def attributes=(hash)
216
+ # hash.each do |key, value|
217
+ # instance_variable_set("@#{key}", value)
218
+ # end
219
+ # end
220
+ #
221
+ # def attributes
222
+ # instance_values
223
+ # end
224
+ # end
225
+ #
226
+ # xml = { name: 'bob', age: 22, awesome:true }.to_xml
227
+ # person = Person.new
228
+ # person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
229
+ # person.name # => "bob"
230
+ # person.age # => 22
231
+ # person.awesome # => true
198
232
  def from_xml(xml)
199
233
  self.attributes = Hash.from_xml(xml).values.first
200
234
  self
@@ -1,16 +1,4 @@
1
1
  module ActiveModel #:nodoc:
2
2
  class TestCase < ActiveSupport::TestCase #:nodoc:
3
- def with_kcode(kcode)
4
- if RUBY_VERSION < '1.9'
5
- orig_kcode, $KCODE = $KCODE, kcode
6
- begin
7
- yield
8
- ensure
9
- $KCODE = orig_kcode
10
- end
11
- else
12
- yield
13
- end
14
- end
15
3
  end
16
4
  end
@@ -1,8 +1,6 @@
1
- require 'active_support/core_ext/hash/reverse_merge'
2
-
3
1
  module ActiveModel
4
2
 
5
- # == Active Model Translation
3
+ # == Active \Model \Translation
6
4
  #
7
5
  # Provides integration between your object and the Rails internationalization
8
6
  # (i18n) framework.
@@ -43,19 +41,20 @@ module ActiveModel
43
41
  #
44
42
  # Specify +options+ with additional translating options.
45
43
  def human_attribute_name(attribute, options = {})
46
- defaults = []
44
+ options = { :count => 1 }.merge!(options)
47
45
  parts = attribute.to_s.split(".")
48
46
  attribute = parts.pop
49
47
  namespace = parts.join("/") unless parts.empty?
48
+ attributes_scope = "#{self.i18n_scope}.attributes"
50
49
 
51
50
  if namespace
52
- lookup_ancestors.each do |klass|
53
- defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
51
+ defaults = lookup_ancestors.map do |klass|
52
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
54
53
  end
55
- defaults << :"#{self.i18n_scope}.attributes.#{namespace}.#{attribute}"
54
+ defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
56
55
  else
57
- lookup_ancestors.each do |klass|
58
- defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
56
+ defaults = lookup_ancestors.map do |klass|
57
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
59
58
  end
60
59
  end
61
60
 
@@ -63,7 +62,7 @@ module ActiveModel
63
62
  defaults << options.delete(:default) if options[:default]
64
63
  defaults << attribute.humanize
65
64
 
66
- options.reverse_merge! :count => 1, :default => defaults
65
+ options[:default] = defaults
67
66
  I18n.translate(defaults.shift, options)
68
67
  end
69
68
  end
@@ -1,15 +1,10 @@
1
1
  require 'active_support/core_ext/array/extract_options'
2
- require 'active_support/core_ext/array/wrap'
3
- require 'active_support/core_ext/class/attribute'
4
2
  require 'active_support/core_ext/hash/keys'
5
3
  require 'active_support/core_ext/hash/except'
6
- require 'active_model/errors'
7
- require 'active_model/validations/callbacks'
8
- require 'active_model/validator'
9
4
 
10
5
  module ActiveModel
11
6
 
12
- # == Active Model Validations
7
+ # == Active \Model Validations
13
8
  #
14
9
  # Provides a full validation framework to your objects.
15
10
  #
@@ -35,17 +30,16 @@ module ActiveModel
35
30
  # person.first_name = 'zoolander'
36
31
  # person.valid? # => false
37
32
  # person.invalid? # => true
38
- # person.errors # => #<OrderedHash {:first_name=>["starts with z."]}>
39
- #
40
- # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+ method
41
- # to your instances initialized with a new <tt>ActiveModel::Errors</tt> object, so
42
- # there is no need for you to do this manually.
33
+ # person.errors.messages # => {first_name:["starts with z."]}
43
34
  #
35
+ # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
36
+ # method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
37
+ # object, so there is no need for you to do this manually.
44
38
  module Validations
45
39
  extend ActiveSupport::Concern
46
- include ActiveSupport::Callbacks
47
40
 
48
41
  included do
42
+ extend ActiveModel::Callbacks
49
43
  extend ActiveModel::Translation
50
44
 
51
45
  extend HelperMethods
@@ -66,27 +60,27 @@ module ActiveModel
66
60
  #
67
61
  # attr_accessor :first_name, :last_name
68
62
  #
69
- # validates_each :first_name, :last_name do |record, attr, value|
63
+ # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
70
64
  # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
71
65
  # end
72
66
  # end
73
67
  #
74
68
  # Options:
75
69
  # * <tt>:on</tt> - Specifies the context where this validation is active
76
- # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
70
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
77
71
  # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
78
72
  # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
79
73
  # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
80
- # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
81
- # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
82
- # proc or string should return or evaluate to a true or false value.
83
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
84
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
85
- # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
86
- # method, proc or string should return or evaluate to a true or false value.
74
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
75
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
76
+ # proc or string should return or evaluate to a +true+ or +false+ value.
77
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
78
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
79
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
80
+ # method, proc or string should return or evaluate to a +true+ or +false+
81
+ # value.
87
82
  def validates_each(*attr_names, &block)
88
- options = attr_names.extract_options!.symbolize_keys
89
- validates_with BlockValidator, options.merge(:attributes => attr_names.flatten), &block
83
+ validates_with BlockValidator, _merge_attributes(attr_names), &block
90
84
  end
91
85
 
92
86
  # Adds a validation method or block to the class. This is useful when
@@ -101,7 +95,7 @@ module ActiveModel
101
95
  # validate :must_be_friends
102
96
  #
103
97
  # def must_be_friends
104
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
98
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
105
99
  # end
106
100
  # end
107
101
  #
@@ -115,7 +109,7 @@ module ActiveModel
115
109
  # end
116
110
  #
117
111
  # def must_be_friends
118
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
112
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
119
113
  # end
120
114
  # end
121
115
  #
@@ -125,15 +119,29 @@ module ActiveModel
125
119
  # include ActiveModel::Validations
126
120
  #
127
121
  # validate do
128
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
122
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
129
123
  # end
130
124
  # end
131
125
  #
126
+ # Options:
127
+ # * <tt>:on</tt> - Specifies the context where this validation is active
128
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
129
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
130
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
131
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
132
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
133
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
134
+ # proc or string should return or evaluate to a +true+ or +false+ value.
135
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
136
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
137
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
138
+ # method, proc or string should return or evaluate to a +true+ or +false+
139
+ # value.
132
140
  def validate(*args, &block)
133
141
  options = args.extract_options!
134
142
  if options.key?(:on)
135
143
  options = options.dup
136
- options[:if] = Array.wrap(options[:if])
144
+ options[:if] = Array(options[:if])
137
145
  options[:if].unshift("validation_context == :#{options[:on]}")
138
146
  end
139
147
  args << options
@@ -142,53 +150,121 @@ module ActiveModel
142
150
 
143
151
  # List all validators that are being used to validate the model using
144
152
  # +validates_with+ method.
153
+ #
154
+ # class Person
155
+ # include ActiveModel::Validations
156
+ #
157
+ # validates_with MyValidator
158
+ # validates_with OtherValidator, on: :create
159
+ # validates_with StrictValidator, strict: true
160
+ # end
161
+ #
162
+ # Person.validators
163
+ # # => [
164
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
165
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
166
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
167
+ # # ]
145
168
  def validators
146
169
  _validators.values.flatten.uniq
147
170
  end
148
171
 
149
- # List all validators that being used to validate a specific attribute.
172
+ # List all validators that are being used to validate a specific attribute.
173
+ #
174
+ # class Person
175
+ # include ActiveModel::Validations
176
+ #
177
+ # attr_accessor :name , :age
178
+ #
179
+ # validates_presence_of :name
180
+ # validates_inclusion_of :age, in: 0..99
181
+ # end
182
+ #
183
+ # Person.validators_on(:name)
184
+ # # => [
185
+ # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
186
+ # # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}>
187
+ # # ]
150
188
  def validators_on(*attributes)
151
- attributes.map do |attribute|
189
+ attributes.flat_map do |attribute|
152
190
  _validators[attribute.to_sym]
153
- end.flatten
191
+ end
154
192
  end
155
193
 
156
- # Check if method is an attribute method or not.
194
+ # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
195
+ #
196
+ # class Person
197
+ # include ActiveModel::Validations
198
+ #
199
+ # attr_accessor :name
200
+ # end
201
+ #
202
+ # User.attribute_method?(:name) # => true
203
+ # User.attribute_method?(:age) # => false
157
204
  def attribute_method?(attribute)
158
205
  method_defined?(attribute)
159
206
  end
160
207
 
161
208
  # Copy validators on inheritance.
162
- def inherited(base)
209
+ def inherited(base) #:nodoc:
163
210
  dup = _validators.dup
164
211
  base._validators = dup.each { |k, v| dup[k] = v.dup }
165
212
  super
166
213
  end
167
214
  end
168
215
 
169
- # Clean the +Errors+ object if instance is duped
170
- def initialize_dup(other) # :nodoc:
216
+ # Clean the +Errors+ object if instance is duped.
217
+ def initialize_dup(other) #:nodoc:
171
218
  @errors = nil
172
- super if defined?(super)
173
- end
174
-
175
- # Backport dup from 1.9 so that #initialize_dup gets called
176
- unless Object.respond_to?(:initialize_dup, true)
177
- def dup # :nodoc:
178
- copy = super
179
- copy.initialize_dup(self)
180
- copy
181
- end
219
+ super
182
220
  end
183
221
 
184
- # Returns the +Errors+ object that holds all information about attribute error messages.
222
+ # Returns the +Errors+ object that holds all information about attribute
223
+ # error messages.
224
+ #
225
+ # class Person
226
+ # include ActiveModel::Validations
227
+ #
228
+ # attr_accessor :name
229
+ # validates_presence_of :name
230
+ # end
231
+ #
232
+ # person = Person.new
233
+ # person.valid? # => false
234
+ # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
185
235
  def errors
186
236
  @errors ||= Errors.new(self)
187
237
  end
188
238
 
189
- # Runs all the specified validations and returns true if no errors were added
190
- # otherwise false. Context can optionally be supplied to define which callbacks
191
- # to test against (the context is defined on the validations using :on).
239
+ # Runs all the specified validations and returns +true+ if no errors were
240
+ # added otherwise +false+.
241
+ #
242
+ # class Person
243
+ # include ActiveModel::Validations
244
+ #
245
+ # attr_accessor :name
246
+ # validates_presence_of :name
247
+ # end
248
+ #
249
+ # person = Person.new
250
+ # person.name = ''
251
+ # person.valid? # => false
252
+ # person.name = 'david'
253
+ # person.valid? # => true
254
+ #
255
+ # Context can optionally be supplied to define which callbacks to test
256
+ # against (the context is defined on the validations using <tt>:on</tt>).
257
+ #
258
+ # class Person
259
+ # include ActiveModel::Validations
260
+ #
261
+ # attr_accessor :name
262
+ # validates_presence_of :name, on: :new
263
+ # end
264
+ #
265
+ # person = Person.new
266
+ # person.valid? # => true
267
+ # person.valid?(:new) # => false
192
268
  def valid?(context = nil)
193
269
  current_context, self.validation_context = validation_context, context
194
270
  errors.clear
@@ -197,8 +273,35 @@ module ActiveModel
197
273
  self.validation_context = current_context
198
274
  end
199
275
 
200
- # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added,
201
- # false otherwise.
276
+ # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
277
+ # added, +false+ otherwise.
278
+ #
279
+ # class Person
280
+ # include ActiveModel::Validations
281
+ #
282
+ # attr_accessor :name
283
+ # validates_presence_of :name
284
+ # end
285
+ #
286
+ # person = Person.new
287
+ # person.name = ''
288
+ # person.invalid? # => true
289
+ # person.name = 'david'
290
+ # person.invalid? # => false
291
+ #
292
+ # Context can optionally be supplied to define which callbacks to test
293
+ # against (the context is defined on the validations using <tt>:on</tt>).
294
+ #
295
+ # class Person
296
+ # include ActiveModel::Validations
297
+ #
298
+ # attr_accessor :name
299
+ # validates_presence_of :name, on: :new
300
+ # end
301
+ #
302
+ # person = Person.new
303
+ # person.invalid? # => false
304
+ # person.invalid?(:new) # => true
202
305
  def invalid?(context = nil)
203
306
  !valid?(context)
204
307
  end
@@ -219,12 +322,11 @@ module ActiveModel
219
322
  # @data[key]
220
323
  # end
221
324
  # end
222
- #
223
325
  alias :read_attribute_for_validation :send
224
326
 
225
327
  protected
226
328
 
227
- def run_validations!
329
+ def run_validations! #:nodoc:
228
330
  run_callbacks :validate
229
331
  errors.empty?
230
332
  end