activemodel 3.0.pre → 3.0.0.rc

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