activemodel 3.0.pre → 3.0.0.rc

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. data/CHANGELOG +44 -1
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +184 -0
  4. data/lib/active_model.rb +29 -19
  5. data/lib/active_model/attribute_methods.rb +167 -46
  6. data/lib/active_model/callbacks.rb +134 -0
  7. data/lib/active_model/conversion.rb +41 -1
  8. data/lib/active_model/deprecated_error_methods.rb +1 -1
  9. data/lib/active_model/dirty.rb +56 -12
  10. data/lib/active_model/errors.rb +205 -46
  11. data/lib/active_model/lint.rb +53 -17
  12. data/lib/active_model/locale/en.yml +26 -23
  13. data/lib/active_model/mass_assignment_security.rb +160 -0
  14. data/lib/active_model/mass_assignment_security/permission_set.rb +40 -0
  15. data/lib/active_model/mass_assignment_security/sanitizer.rb +23 -0
  16. data/lib/active_model/naming.rb +70 -5
  17. data/lib/active_model/observing.rb +40 -16
  18. data/lib/active_model/railtie.rb +2 -0
  19. data/lib/active_model/serialization.rb +59 -0
  20. data/lib/active_model/serializers/json.rb +17 -11
  21. data/lib/active_model/serializers/xml.rb +66 -123
  22. data/lib/active_model/test_case.rb +0 -2
  23. data/lib/active_model/translation.rb +64 -0
  24. data/lib/active_model/validations.rb +150 -68
  25. data/lib/active_model/validations/acceptance.rb +53 -33
  26. data/lib/active_model/validations/callbacks.rb +57 -0
  27. data/lib/active_model/validations/confirmation.rb +41 -23
  28. data/lib/active_model/validations/exclusion.rb +18 -13
  29. data/lib/active_model/validations/format.rb +28 -24
  30. data/lib/active_model/validations/inclusion.rb +18 -13
  31. data/lib/active_model/validations/length.rb +67 -65
  32. data/lib/active_model/validations/numericality.rb +83 -58
  33. data/lib/active_model/validations/presence.rb +10 -8
  34. data/lib/active_model/validations/validates.rb +110 -0
  35. data/lib/active_model/validations/with.rb +90 -23
  36. data/lib/active_model/validator.rb +186 -0
  37. data/lib/active_model/version.rb +3 -2
  38. metadata +79 -20
  39. data/README +0 -21
  40. data/lib/active_model/state_machine.rb +0 -70
  41. data/lib/active_model/state_machine/event.rb +0 -62
  42. data/lib/active_model/state_machine/machine.rb +0 -75
  43. data/lib/active_model/state_machine/state.rb +0 -47
  44. data/lib/active_model/state_machine/state_transition.rb +0 -40
  45. data/lib/active_model/validations_repair_helper.rb +0 -35
@@ -1,8 +1,16 @@
1
1
  require 'active_support/core_ext/object/blank'
2
2
 
3
3
  module ActiveModel
4
+
5
+ # == Active Model Presence Validator
4
6
  module Validations
5
- module ClassMethods
7
+ class PresenceValidator < EachValidator
8
+ def validate(record)
9
+ record.errors.add_on_blank(attributes, options)
10
+ end
11
+ end
12
+
13
+ module HelperMethods
6
14
  # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
7
15
  #
8
16
  # class Person < ActiveRecord::Base
@@ -28,13 +36,7 @@ module ActiveModel
28
36
  # The method, proc or string should return or evaluate to a true or false value.
29
37
  #
30
38
  def validates_presence_of(*attr_names)
31
- configuration = attr_names.extract_options!
32
-
33
- # can't use validates_each here, because it cannot cope with nonexistent attributes,
34
- # while errors.add_on_empty can
35
- validate configuration do |record|
36
- record.errors.add_on_blank(attr_names, configuration[:message])
37
- end
39
+ validates_with PresenceValidator, _merge_attributes(attr_names)
38
40
  end
39
41
  end
40
42
  end
@@ -0,0 +1,110 @@
1
+ require 'active_support/core_ext/hash/slice'
2
+
3
+ module ActiveModel
4
+
5
+ # == Active Model validates method
6
+ module Validations
7
+ module ClassMethods
8
+ # This method is a shortcut to all default validators and any custom
9
+ # validator classes ending in 'Validator'. Note that Rails default
10
+ # validators can be overridden inside specific classes by creating
11
+ # custom validator classes in their place such as PresenceValidator.
12
+ #
13
+ # Examples of using the default rails validators:
14
+ #
15
+ # validates :terms, :acceptance => true
16
+ # validates :password, :confirmation => true
17
+ # validates :username, :exclusion => { :in => %w(admin superuser) }
18
+ # validates :email, :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }
19
+ # validates :age, :inclusion => { :in => 0..9 }
20
+ # validates :first_name, :length => { :maximum => 30 }
21
+ # validates :age, :numericality => true
22
+ # validates :username, :presence => true
23
+ # validates :username, :uniqueness => true
24
+ #
25
+ # The power of the +validates+ method comes when using custom validators
26
+ # and default validators in one call for a given attribute e.g.
27
+ #
28
+ # class EmailValidator < ActiveModel::EachValidator
29
+ # def validate_each(record, attribute, value)
30
+ # record.errors[attribute] << (options[:message] || "is not an email") unless
31
+ # value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
32
+ # end
33
+ # end
34
+ #
35
+ # class Person
36
+ # include ActiveModel::Validations
37
+ # attr_accessor :name, :email
38
+ #
39
+ # validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
40
+ # validates :email, :presence => true, :email => true
41
+ # end
42
+ #
43
+ # Validator classes my also exist within the class being validated
44
+ # allowing custom modules of validators to be included as needed e.g.
45
+ #
46
+ # class Film
47
+ # include ActiveModel::Validations
48
+ #
49
+ # class TitleValidator < ActiveModel::EachValidator
50
+ # def validate_each(record, attribute, value)
51
+ # record.errors[attribute] << "must start with 'the'" unless =~ /^the/i
52
+ # end
53
+ # end
54
+ #
55
+ # validates :name, :title => true
56
+ # end
57
+ #
58
+ # The validators hash can also handle regular expressions, ranges and arrays:
59
+ #
60
+ # validates :email, :format => /@/
61
+ # validates :gender, :inclusion => %w(male female)
62
+ # validates :password, :length => 6..20
63
+ #
64
+ # Finally, the options :if, :unless, :on, :allow_blank and :allow_nil can be given
65
+ # to one specific validator:
66
+ #
67
+ # validates :password, :presence => { :if => :password_required? }, :confirmation => true
68
+ #
69
+ # Or to all at the same time:
70
+ #
71
+ # validates :password, :presence => true, :confirmation => true, :if => :password_required?
72
+ #
73
+ def validates(*attributes)
74
+ defaults = attributes.extract_options!
75
+ validations = defaults.slice!(:if, :unless, :on, :allow_blank, :allow_nil)
76
+
77
+ raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
78
+ raise ArgumentError, "Attribute names must be symbols" if attributes.any?{ |attribute| !attribute.is_a?(Symbol) }
79
+ raise ArgumentError, "You need to supply at least one validation" if validations.empty?
80
+
81
+ defaults.merge!(:attributes => attributes)
82
+
83
+ validations.each do |key, options|
84
+ begin
85
+ validator = const_get("#{key.to_s.camelize}Validator")
86
+ rescue NameError
87
+ raise ArgumentError, "Unknown validator: '#{key}'"
88
+ end
89
+
90
+ validates_with(validator, defaults.merge(_parse_validates_options(options)))
91
+ end
92
+ end
93
+
94
+ protected
95
+
96
+ def _parse_validates_options(options) #:nodoc:
97
+ case options
98
+ when TrueClass
99
+ {}
100
+ when Hash
101
+ options
102
+ when Regexp
103
+ { :with => options }
104
+ when Range, Array
105
+ { :in => options }
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,15 +1,24 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- module ClassMethods
3
+ module HelperMethods
4
+ private
5
+ def _merge_attributes(attr_names)
6
+ options = attr_names.extract_options!
7
+ options.merge(:attributes => attr_names.flatten)
8
+ end
9
+ end
4
10
 
5
- # Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions.
11
+ module ClassMethods
12
+ # Passes the record off to the class or classes specified and allows them
13
+ # to add errors based on more complex conditions.
6
14
  #
7
- # class Person < ActiveRecord::Base
15
+ # class Person
16
+ # include ActiveModel::Validations
8
17
  # validates_with MyValidator
9
18
  # end
10
19
  #
11
- # class MyValidator < ActiveRecord::Validator
12
- # def validate
20
+ # class MyValidator < ActiveModel::Validator
21
+ # def validate(record)
13
22
  # if some_complex_logic
14
23
  # record.errors[:base] << "This record is invalid"
15
24
  # end
@@ -23,42 +32,100 @@ module ActiveModel
23
32
  #
24
33
  # You may also pass it multiple classes, like so:
25
34
  #
26
- # class Person < ActiveRecord::Base
35
+ # class Person
36
+ # include ActiveModel::Validations
27
37
  # validates_with MyValidator, MyOtherValidator, :on => :create
28
38
  # end
29
39
  #
30
40
  # Configuration options:
31
- # * <tt>on</tt> - Specifies when this validation is active (<tt>:create</tt> or <tt>:update</tt>
32
- # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
33
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
41
+ # * <tt>on</tt> - Specifies when this validation is active
42
+ # (<tt>:create</tt> or <tt>:update</tt>
43
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine
44
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
45
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
34
46
  # The method, proc or string should return or evaluate to a true or false value.
35
- # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
36
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
47
+ # * <tt>unless</tt> - Specifies a method, proc or string to call to
48
+ # determine if the validation should not occur
49
+ # (e.g. <tt>:unless => :skip_validation</tt>, or
50
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
37
51
  # The method, proc or string should return or evaluate to a true or false value.
38
52
  #
39
- # If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>:
53
+ # If you pass any additional configuration options, they will be passed
54
+ # to the class and available as <tt>options</tt>:
40
55
  #
41
- # class Person < ActiveRecord::Base
56
+ # class Person
57
+ # include ActiveModel::Validations
42
58
  # validates_with MyValidator, :my_custom_key => "my custom value"
43
59
  # end
44
60
  #
45
- # class MyValidator < ActiveRecord::Validator
46
- # def validate
61
+ # class MyValidator < ActiveModel::Validator
62
+ # def validate(record)
47
63
  # options[:my_custom_key] # => "my custom value"
48
64
  # end
49
65
  # end
50
66
  #
51
- def validates_with(*args)
52
- configuration = args.extract_options!
67
+ def validates_with(*args, &block)
68
+ options = args.extract_options!
69
+ args.each do |klass|
70
+ validator = klass.new(options, &block)
71
+ validator.setup(self) if validator.respond_to?(:setup)
53
72
 
54
- validate configuration do |record|
55
- args.each do |klass|
56
- klass.new(record, configuration.except(:on, :if, :unless)).validate
73
+ if validator.respond_to?(:attributes) && !validator.attributes.empty?
74
+ validator.attributes.each do |attribute|
75
+ _validators[attribute.to_sym] << validator
76
+ end
77
+ else
78
+ _validators[nil] << validator
57
79
  end
80
+
81
+ validate(validator, options)
58
82
  end
59
83
  end
60
84
  end
61
- end
62
- end
63
-
64
85
 
86
+ # Passes the record off to the class or classes specified and allows them
87
+ # to add errors based on more complex conditions.
88
+ #
89
+ # class Person
90
+ # include ActiveModel::Validations
91
+ #
92
+ # validates :instance_validations
93
+ #
94
+ # def instance_validations
95
+ # validates_with MyValidator
96
+ # end
97
+ # end
98
+ #
99
+ # Please consult the class method documentation for more information on
100
+ # creating your own validator.
101
+ #
102
+ # You may also pass it multiple classes, like so:
103
+ #
104
+ # class Person
105
+ # include ActiveModel::Validations
106
+ #
107
+ # validates :instance_validations, :on => :create
108
+ #
109
+ # def instance_validations
110
+ # validates_with MyValidator, MyOtherValidator
111
+ # end
112
+ # end
113
+ #
114
+ # Standard configuration options (:on, :if and :unless), which are
115
+ # available on the class version of validates_with, should instead be
116
+ # placed on the <tt>validates</tt> method as these are applied and tested
117
+ # in the callback
118
+ #
119
+ # If you pass any additional configuration options, they will be passed
120
+ # to the class and available as <tt>options</tt>, please refer to the
121
+ # class version of this method for more information
122
+ #
123
+ def validates_with(*args, &block)
124
+ options = args.extract_options!
125
+ args.each do |klass|
126
+ validator = klass.new(options, &block)
127
+ validator.validate(self)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,186 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require "active_support/core_ext/module/anonymous"
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ module ActiveModel #:nodoc:
6
+
7
+ # == Active Model Validator
8
+ #
9
+ # A simple base class that can be used along with
10
+ # +ActiveModel::Validations::ClassMethods.validates_with+
11
+ #
12
+ # class Person
13
+ # include ActiveModel::Validations
14
+ # validates_with MyValidator
15
+ # end
16
+ #
17
+ # class MyValidator < ActiveModel::Validator
18
+ # def validate(record)
19
+ # if some_complex_logic
20
+ # record.errors[:base] = "This record is invalid"
21
+ # end
22
+ # end
23
+ #
24
+ # private
25
+ # def some_complex_logic
26
+ # # ...
27
+ # end
28
+ # end
29
+ #
30
+ # Any class that inherits from ActiveModel::Validator must implement a method
31
+ # called <tt>validate</tt> which accepts a <tt>record</tt>.
32
+ #
33
+ # class Person
34
+ # include ActiveModel::Validations
35
+ # validates_with MyValidator
36
+ # end
37
+ #
38
+ # class MyValidator < ActiveModel::Validator
39
+ # def validate(record)
40
+ # record # => The person instance being validated
41
+ # options # => Any non-standard options passed to validates_with
42
+ # end
43
+ # end
44
+ #
45
+ # To cause a validation error, you must add to the <tt>record<tt>'s errors directly
46
+ # from within the validators message
47
+ #
48
+ # class MyValidator < ActiveModel::Validator
49
+ # def validate(record)
50
+ # record.errors[:base] << "This is some custom error message"
51
+ # record.errors[:first_name] << "This is some complex validation"
52
+ # # etc...
53
+ # end
54
+ # end
55
+ #
56
+ # To add behavior to the initialize method, use the following signature:
57
+ #
58
+ # class MyValidator < ActiveModel::Validator
59
+ # def initialize(record, options)
60
+ # super
61
+ # @my_custom_field = options[:field_name] || :first_name
62
+ # end
63
+ # end
64
+ #
65
+ # The easiest way to add custom validators for validating individual attributes
66
+ # is with the convenient ActiveModel::EachValidator for example:
67
+ #
68
+ # class TitleValidator < ActiveModel::EachValidator
69
+ # def validate_each(record, attribute, value)
70
+ # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless ['Mr.', 'Mrs.', 'Dr.'].include?(value)
71
+ # end
72
+ # end
73
+ #
74
+ # This can now be used in combination with the +validates+ method
75
+ # (see ActiveModel::Validations::ClassMethods.validates for more on this)
76
+ #
77
+ # class Person
78
+ # include ActiveModel::Validations
79
+ # attr_accessor :title
80
+ #
81
+ # validates :title, :presence => true, :title => true
82
+ # end
83
+ #
84
+ # Validator may also define a +setup+ instance method which will get called
85
+ # with the class that using that validator as it's argument. This can be
86
+ # useful when there are prerequisites such as an attr_accessor being present
87
+ # for example:
88
+ #
89
+ # class MyValidator < ActiveModel::Validator
90
+ # def setup(klass)
91
+ # klass.send :attr_accessor, :custom_attribute
92
+ # end
93
+ # end
94
+ #
95
+ # This setup method is only called when used with validation macros or the
96
+ # class level <tt>validates_with</tt> method.
97
+ #
98
+ class Validator
99
+ attr_reader :options
100
+
101
+ # Returns the kind of the validator.
102
+ #
103
+ # == Examples
104
+ #
105
+ # PresenceValidator.kind #=> :presence
106
+ # UniquenessValidator.kind #=> :uniqueness
107
+ #
108
+ def self.kind
109
+ @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
110
+ end
111
+
112
+ # Accepts options that will be made available through the +options+ reader.
113
+ def initialize(options)
114
+ @options = options
115
+ end
116
+
117
+ # Return the kind for this validator.
118
+ def kind
119
+ self.class.kind
120
+ end
121
+
122
+ # Override this method in subclasses with validation logic, adding errors
123
+ # to the records +errors+ array where necessary.
124
+ def validate(record)
125
+ raise NotImplementedError
126
+ end
127
+ end
128
+
129
+ # EachValidator is a validator which iterates through the attributes given
130
+ # in the options hash invoking the validate_each method passing in the
131
+ # record, attribute and value.
132
+ #
133
+ # All Active Model validations are built on top of this Validator.
134
+ class EachValidator < Validator
135
+ attr_reader :attributes
136
+
137
+ # Returns a new validator instance. All options will be available via the
138
+ # +options+ reader, however the <tt>:attributes</tt> option will be removed
139
+ # and instead be made available through the +attributes+ reader.
140
+ def initialize(options)
141
+ @attributes = Array.wrap(options.delete(:attributes))
142
+ raise ":attributes cannot be blank" if @attributes.empty?
143
+ super
144
+ check_validity!
145
+ end
146
+
147
+ # Performs validation on the supplied record. By default this will call
148
+ # +validates_each+ to determine validity therefore subclasses should
149
+ # override +validates_each+ with validation logic.
150
+ def validate(record)
151
+ attributes.each do |attribute|
152
+ value = record.read_attribute_for_validation(attribute)
153
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
154
+ validate_each(record, attribute, value)
155
+ end
156
+ end
157
+
158
+ # Override this method in subclasses with the validation logic, adding
159
+ # errors to the records +errors+ array where necessary.
160
+ def validate_each(record, attribute, value)
161
+ raise NotImplementedError
162
+ end
163
+
164
+ # Hook method that gets called by the initializer allowing verification
165
+ # that the arguments supplied are valid. You could for example raise an
166
+ # ArgumentError when invalid options are supplied.
167
+ def check_validity!
168
+ end
169
+ end
170
+
171
+ # BlockValidator is a special EachValidator which receives a block on initialization
172
+ # and call this block for each attribute being validated. +validates_each+ uses this
173
+ # Validator.
174
+ class BlockValidator < EachValidator
175
+ def initialize(options, &block)
176
+ @block = block
177
+ super
178
+ end
179
+
180
+ private
181
+
182
+ def validate_each(record, attribute, value)
183
+ @block.call(record, attribute, value)
184
+ end
185
+ end
186
+ end