activemodel 3.2.22.5 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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