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,13 +1,12 @@
1
- require 'active_support/core_ext/array/wrap'
2
- require 'active_support/callbacks'
1
+ require 'active_support/core_ext/array/extract_options'
3
2
 
4
3
  module ActiveModel
5
- # == Active Model Callbacks
4
+ # == Active \Model \Callbacks
6
5
  #
7
6
  # Provides an interface for any class to have Active Record like callbacks.
8
7
  #
9
8
  # Like the Active Record methods, the callback chain is aborted as soon as
10
- # one of the methods in the chain returns false.
9
+ # one of the methods in the chain returns +false+.
11
10
  #
12
11
  # First, extend ActiveModel::Callbacks from the class you are creating:
13
12
  #
@@ -19,9 +18,10 @@ module ActiveModel
19
18
  #
20
19
  # define_model_callbacks :create, :update
21
20
  #
22
- # This will provide all three standard callbacks (before, around and after) for
23
- # both the :create and :update methods. To implement, you need to wrap the methods
24
- # you want callbacks on in a block so that the callbacks get a chance to fire:
21
+ # This will provide all three standard callbacks (before, around and after)
22
+ # for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
23
+ # you need to wrap the methods you want callbacks on in a block so that the
24
+ # callbacks get a chance to fire:
25
25
  #
26
26
  # def create
27
27
  # run_callbacks :create do
@@ -29,8 +29,8 @@ module ActiveModel
29
29
  # end
30
30
  # end
31
31
  #
32
- # Then in your class, you can use the +before_create+, +after_create+ and +around_create+
33
- # methods, just as you would in an Active Record module.
32
+ # Then in your class, you can use the +before_create+, +after_create+ and
33
+ # +around_create+ methods, just as you would in an Active Record module.
34
34
  #
35
35
  # before_create :action_before_create
36
36
  #
@@ -38,39 +38,52 @@ module ActiveModel
38
38
  # # Your code here
39
39
  # end
40
40
  #
41
+ # When defining an around callback remember to yield to the block, otherwise
42
+ # it won't be executed:
43
+ #
44
+ # around_create :log_status
45
+ #
46
+ # def log_status
47
+ # puts 'going to call the block...'
48
+ # yield
49
+ # puts 'block successfully called.'
50
+ # end
51
+ #
41
52
  # You can choose not to have all three callbacks by passing a hash to the
42
- # define_model_callbacks method.
53
+ # +define_model_callbacks+ method.
43
54
  #
44
- # define_model_callbacks :create, :only => [:after, :before]
55
+ # define_model_callbacks :create, only: [:after, :before]
45
56
  #
46
- # Would only create the after_create and before_create callback methods in your
47
- # class.
57
+ # Would only create the +after_create+ and +before_create+ callback methods in
58
+ # your class.
48
59
  module Callbacks
49
- def self.extended(base)
60
+ def self.extended(base) #:nodoc:
50
61
  base.class_eval do
51
62
  include ActiveSupport::Callbacks
52
63
  end
53
64
  end
54
65
 
55
- # define_model_callbacks accepts the same options define_callbacks does, in case
56
- # you want to overwrite a default. Besides that, it also accepts an :only option,
57
- # where you can choose if you want all types (before, around or after) or just some.
66
+ # define_model_callbacks accepts the same options +define_callbacks+ does,
67
+ # in case you want to overwrite a default. Besides that, it also accepts an
68
+ # <tt>:only</tt> option, where you can choose if you want all types (before,
69
+ # around or after) or just some.
58
70
  #
59
- # define_model_callbacks :initializer, :only => :after
71
+ # define_model_callbacks :initializer, only: :after
60
72
  #
61
- # Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on
62
- # that method call. To get around this you can call the define_model_callbacks
73
+ # Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
74
+ # on that method call. To get around this you can call the define_model_callbacks
63
75
  # method as many times as you need.
64
76
  #
65
- # define_model_callbacks :create, :only => :after
66
- # define_model_callbacks :update, :only => :before
67
- # define_model_callbacks :destroy, :only => :around
77
+ # define_model_callbacks :create, only: :after
78
+ # define_model_callbacks :update, only: :before
79
+ # define_model_callbacks :destroy, only: :around
68
80
  #
69
- # Would create +after_create+, +before_update+ and +around_destroy+ methods only.
81
+ # Would create +after_create+, +before_update+ and +around_destroy+ methods
82
+ # only.
70
83
  #
71
- # You can pass in a class to before_<type>, after_<type> and around_<type>, in which
72
- # case the callback will call that class's <action>_<type> method passing the object
73
- # that the callback is being called on.
84
+ # You can pass in a class to before_<type>, after_<type> and around_<type>,
85
+ # in which case the callback will call that class's <action>_<type> method
86
+ # passing the object that the callback is being called on.
74
87
  #
75
88
  # class MyModel
76
89
  # extend ActiveModel::Callbacks
@@ -84,16 +97,16 @@ module ActiveModel
84
97
  # # obj is the MyModel instance that the callback is being called on
85
98
  # end
86
99
  # end
87
- #
88
100
  def define_model_callbacks(*callbacks)
89
101
  options = callbacks.extract_options!
90
102
  options = {
91
- :terminator => "result == false",
92
- :scope => [:kind, :name],
93
- :only => [:before, :around, :after]
94
- }.merge(options)
103
+ :terminator => "result == false",
104
+ :skip_after_callbacks_if_terminated => true,
105
+ :scope => [:kind, :name],
106
+ :only => [:before, :around, :after]
107
+ }.merge!(options)
95
108
 
96
- types = Array.wrap(options.delete(:only))
109
+ types = Array(options.delete(:only))
97
110
 
98
111
  callbacks.each do |callback|
99
112
  define_callbacks(callback, options)
@@ -104,6 +117,8 @@ module ActiveModel
104
117
  end
105
118
  end
106
119
 
120
+ private
121
+
107
122
  def _define_before_model_callback(klass, callback) #:nodoc:
108
123
  klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
109
124
  def self.before_#{callback}(*args, &block)
@@ -125,7 +140,7 @@ module ActiveModel
125
140
  def self.after_#{callback}(*args, &block)
126
141
  options = args.extract_options!
127
142
  options[:prepend] = true
128
- options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
143
+ options[:if] = Array(options[:if]) << "value != false"
129
144
  set_callback(:#{callback}, :after, *(args << options), &block)
130
145
  end
131
146
  CALLBACK
@@ -1,8 +1,5 @@
1
- require 'active_support/concern'
2
- require 'active_support/inflector'
3
-
4
1
  module ActiveModel
5
- # == Active Model Conversions
2
+ # == Active \Model Conversions
6
3
  #
7
4
  # Handles default conversions: to_model, to_key, to_param, and to_partial_path.
8
5
  #
@@ -18,17 +15,23 @@ module ActiveModel
18
15
  # end
19
16
  #
20
17
  # cm = ContactMessage.new
21
- # cm.to_model == self # => true
22
- # cm.to_key # => nil
23
- # cm.to_param # => nil
24
- # cm.to_path # => "contact_messages/contact_message"
25
- #
18
+ # cm.to_model == cm # => true
19
+ # cm.to_key # => nil
20
+ # cm.to_param # => nil
21
+ # cm.to_partial_path # => "contact_messages/contact_message"
26
22
  module Conversion
27
23
  extend ActiveSupport::Concern
28
24
 
29
25
  # If your object is already designed to implement all of the Active Model
30
26
  # you can use the default <tt>:to_model</tt> implementation, which simply
31
- # returns self.
27
+ # returns +self+.
28
+ #
29
+ # class Person
30
+ # include ActiveModel::Conversion
31
+ # end
32
+ #
33
+ # person = Person.new
34
+ # person.to_model == person # => true
32
35
  #
33
36
  # If your model does not act like an Active Model object, then you should
34
37
  # define <tt>:to_model</tt> yourself returning a proxy object that wraps
@@ -37,29 +40,46 @@ module ActiveModel
37
40
  self
38
41
  end
39
42
 
40
- # Returns an Enumerable of all key attributes if any is set, regardless
41
- # if the object is persisted or not.
43
+ # Returns an Enumerable of all key attributes if any is set, regardless if
44
+ # the object is persisted or not. If there no key attributes, returns +nil+.
45
+ #
46
+ # class Person < ActiveRecord::Base
47
+ # end
42
48
  #
43
- # Note the default implementation uses persisted? just because all objects
44
- # in Ruby 1.8.x responds to <tt>:id</tt>.
49
+ # person = Person.create
50
+ # person.to_key # => [1]
45
51
  def to_key
46
- persisted? ? [id] : nil
52
+ key = respond_to?(:id) && id
53
+ key ? [key] : nil
47
54
  end
48
55
 
49
- # Returns a string representing the object's key suitable for use in URLs,
50
- # or nil if <tt>persisted?</tt> is false.
56
+ # Returns a +string+ representing the object's key suitable for use in URLs,
57
+ # or +nil+ if <tt>persisted?</tt> is +false+.
58
+ #
59
+ # class Person < ActiveRecord::Base
60
+ # end
61
+ #
62
+ # person = Person.create
63
+ # person.to_param # => "1"
51
64
  def to_param
52
65
  persisted? ? to_key.join('-') : nil
53
66
  end
54
67
 
55
- # Returns a string identifying the path associated with the object.
68
+ # Returns a +string+ identifying the path associated with the object.
56
69
  # ActionPack uses this to find a suitable partial to represent the object.
70
+ #
71
+ # class Person
72
+ # include ActiveModel::Conversion
73
+ # end
74
+ #
75
+ # person = Person.new
76
+ # person.to_partial_path # => "people/person"
57
77
  def to_partial_path
58
78
  self.class._to_partial_path
59
79
  end
60
80
 
61
81
  module ClassMethods #:nodoc:
62
- # Provide a class level cache for the to_path. This is an
82
+ # Provide a class level cache for #to_partial_path. This is an
63
83
  # internal method and should not be accessed directly.
64
84
  def _to_partial_path #:nodoc:
65
85
  @_to_partial_path ||= begin
@@ -0,0 +1,21 @@
1
+ module ActiveModel
2
+ module DeprecatedMassAssignmentSecurity # :nodoc:
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods # :nodoc:
6
+ def attr_protected(*args)
7
+ raise "`attr_protected` is extracted out of Rails into a gem. " \
8
+ "Please use new recommended protection model for params" \
9
+ "(strong_parameters) or add `protected_attributes` to your " \
10
+ "Gemfile to use old one."
11
+ end
12
+
13
+ def attr_accessible(*args)
14
+ raise "`attr_accessible` is extracted out of Rails into a gem. " \
15
+ "Please use new recommended protection model for params" \
16
+ "(strong_parameters) or add `protected_attributes` to your " \
17
+ "Gemfile to use old one."
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,23 +1,22 @@
1
- require 'active_model/attribute_methods'
2
1
  require 'active_support/hash_with_indifferent_access'
3
2
  require 'active_support/core_ext/object/duplicable'
4
3
 
5
4
  module ActiveModel
6
- # == Active Model Dirty
5
+ # == Active \Model \Dirty
7
6
  #
8
7
  # Provides a way to track changes in your object in the same way as
9
8
  # Active Record does.
10
9
  #
11
10
  # The requirements for implementing ActiveModel::Dirty are:
12
11
  #
13
- # * <tt>include ActiveModel::Dirty</tt> in your object
12
+ # * <tt>include ActiveModel::Dirty</tt> in your object.
14
13
  # * Call <tt>define_attribute_methods</tt> passing each method you want to
15
- # track
14
+ # track.
16
15
  # * Call <tt>attr_name_will_change!</tt> before each change to the tracked
17
- # attribute
16
+ # attribute.
18
17
  #
19
18
  # If you wish to also track previous changes on save or update, you need to
20
- # add
19
+ # add:
21
20
  #
22
21
  # @previously_changed = changes
23
22
  #
@@ -26,10 +25,9 @@ module ActiveModel
26
25
  # A minimal implementation could be:
27
26
  #
28
27
  # class Person
29
- #
30
28
  # include ActiveModel::Dirty
31
29
  #
32
- # define_attribute_methods [:name]
30
+ # define_attribute_methods :name
33
31
  #
34
32
  # def name
35
33
  # @name
@@ -44,46 +42,49 @@ module ActiveModel
44
42
  # @previously_changed = changes
45
43
  # @changed_attributes.clear
46
44
  # end
47
- #
48
45
  # end
49
46
  #
50
- # == Examples:
51
- #
52
47
  # A newly instantiated object is unchanged:
48
+ #
53
49
  # person = Person.find_by_name('Uncle Bob')
54
50
  # person.changed? # => false
55
51
  #
56
52
  # Change the name:
53
+ #
57
54
  # person.name = 'Bob'
58
55
  # person.changed? # => true
59
56
  # person.name_changed? # => true
60
- # person.name_was # => 'Uncle Bob'
61
- # person.name_change # => ['Uncle Bob', 'Bob']
57
+ # person.name_was # => "Uncle Bob"
58
+ # person.name_change # => ["Uncle Bob", "Bob"]
62
59
  # person.name = 'Bill'
63
- # person.name_change # => ['Uncle Bob', 'Bill']
60
+ # person.name_change # => ["Uncle Bob", "Bill"]
64
61
  #
65
62
  # Save the changes:
63
+ #
66
64
  # person.save
67
65
  # person.changed? # => false
68
66
  # person.name_changed? # => false
69
67
  #
70
68
  # Assigning the same value leaves the attribute unchanged:
69
+ #
71
70
  # person.name = 'Bill'
72
71
  # person.name_changed? # => false
73
72
  # person.name_change # => nil
74
73
  #
75
74
  # Which attributes have changed?
75
+ #
76
76
  # person.name = 'Bob'
77
- # person.changed # => ['name']
78
- # person.changes # => { 'name' => ['Bill', 'Bob'] }
77
+ # person.changed # => ["name"]
78
+ # person.changes # => {"name" => ["Bill", "Bob"]}
79
79
  #
80
80
  # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
81
- # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
82
- # in-place attributes.
81
+ # to mark that the attribute is changing. Otherwise ActiveModel can't track
82
+ # changes to in-place attributes.
83
83
  #
84
84
  # person.name_will_change!
85
+ # person.name_change # => ["Bill", "Bill"]
85
86
  # person.name << 'y'
86
- # person.name_change # => ['Bill', 'Billy']
87
+ # person.name_change # => ["Bill", "Billy"]
87
88
  module Dirty
88
89
  extend ActiveSupport::Concern
89
90
  include ActiveModel::AttributeMethods
@@ -93,40 +94,50 @@ module ActiveModel
93
94
  attribute_method_affix :prefix => 'reset_', :suffix => '!'
94
95
  end
95
96
 
96
- # Returns true if any attribute have unsaved changes, false otherwise.
97
+ # Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
98
+ #
97
99
  # person.changed? # => false
98
100
  # person.name = 'bob'
99
101
  # person.changed? # => true
100
102
  def changed?
101
- changed_attributes.any?
103
+ changed_attributes.present?
102
104
  end
103
105
 
104
- # List of attributes with unsaved changes.
106
+ # Returns an array with the name of the attributes with unsaved changes.
107
+ #
105
108
  # person.changed # => []
106
109
  # person.name = 'bob'
107
- # person.changed # => ['name']
110
+ # person.changed # => ["name"]
108
111
  def changed
109
112
  changed_attributes.keys
110
113
  end
111
114
 
112
- # Map of changed attrs => [original value, new value].
115
+ # Returns a hash of changed attributes indicating their original
116
+ # and new values like <tt>attr => [original value, new value]</tt>.
117
+ #
113
118
  # person.changes # => {}
114
119
  # person.name = 'bob'
115
- # person.changes # => { 'name' => ['bill', 'bob'] }
120
+ # person.changes # => { "name" => ["bill", "bob"] }
116
121
  def changes
117
- HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
122
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
118
123
  end
119
124
 
120
- # Map of attributes that were changed when the model was saved.
121
- # person.name # => 'bob'
125
+ # Returns a hash of attributes that were changed before the model was saved.
126
+ #
127
+ # person.name # => "bob"
122
128
  # person.name = 'robert'
123
129
  # person.save
124
- # person.previous_changes # => {'name' => ['bob, 'robert']}
130
+ # person.previous_changes # => {"name" => ["bob", "robert"]}
125
131
  def previous_changes
126
132
  @previously_changed
127
133
  end
128
134
 
129
- # Map of change <tt>attr => original value</tt>.
135
+ # Returns a hash of the attributes with unsaved changes indicating their original
136
+ # values like <tt>attr => original value</tt>.
137
+ #
138
+ # person.name # => "bob"
139
+ # person.name = 'robert'
140
+ # person.changed_attributes # => {"name" => "bob"}
130
141
  def changed_attributes
131
142
  @changed_attributes ||= {}
132
143
  end
@@ -150,18 +161,23 @@ module ActiveModel
150
161
 
151
162
  # Handle <tt>*_will_change!</tt> for +method_missing+.
152
163
  def attribute_will_change!(attr)
164
+ return if attribute_changed?(attr)
165
+
153
166
  begin
154
167
  value = __send__(attr)
155
168
  value = value.duplicable? ? value.clone : value
156
169
  rescue TypeError, NoMethodError
157
170
  end
158
171
 
159
- changed_attributes[attr] = value unless changed_attributes.include?(attr)
172
+ changed_attributes[attr] = value
160
173
  end
161
174
 
162
175
  # Handle <tt>reset_*!</tt> for +method_missing+.
163
176
  def reset_attribute!(attr)
164
- __send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
177
+ if attribute_changed?(attr)
178
+ __send__("#{attr}=", changed_attributes[attr])
179
+ changed_attributes.delete(attr)
180
+ end
165
181
  end
166
182
  end
167
183
  end