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,5 +1,3 @@
1
- require "active_support/test_case"
2
-
3
1
  module ActiveModel #:nodoc:
4
2
  class TestCase < ActiveSupport::TestCase #:nodoc:
5
3
  def with_kcode(kcode)
@@ -0,0 +1,64 @@
1
+ require 'active_support/core_ext/hash/reverse_merge'
2
+
3
+ module ActiveModel
4
+
5
+ # == Active Model Translation
6
+ #
7
+ # Provides integration between your object and the Rails internationalization
8
+ # (i18n) framework.
9
+ #
10
+ # A minimal implementation could be:
11
+ #
12
+ # class TranslatedPerson
13
+ # extend ActiveModel::Translation
14
+ # end
15
+ #
16
+ # TranslatedPerson.human_attribute_name('my_attribute')
17
+ # #=> "My attribute"
18
+ #
19
+ # This also provides the required class methods for hooking into the
20
+ # Rails internationalization API, including being able to define a
21
+ # class based i18n_scope and lookup_ancestors to find translations in
22
+ # parent classes.
23
+ module Translation
24
+ include ActiveModel::Naming
25
+
26
+ # Returns the i18n_scope for the class. Overwrite if you want custom lookup.
27
+ def i18n_scope
28
+ :activemodel
29
+ end
30
+
31
+ # When localizing a string, it goes through the lookup returned by this
32
+ # method, which is used in ActiveModel::Name#human,
33
+ # ActiveModel::Errors#full_messages and
34
+ # ActiveModel::Translation#human_attribute_name.
35
+ def lookup_ancestors
36
+ self.ancestors.select { |x| x.respond_to?(:model_name) }
37
+ end
38
+
39
+ # Transforms attribute names into a more human format, such as "First name"
40
+ # instead of "first_name".
41
+ #
42
+ # Person.human_attribute_name("first_name") # => "First name"
43
+ #
44
+ # Specify +options+ with additional translating options.
45
+ def human_attribute_name(attribute, options = {})
46
+ defaults = lookup_ancestors.map do |klass|
47
+ :"#{self.i18n_scope}.attributes.#{klass.model_name.underscore}.#{attribute}"
48
+ end
49
+
50
+ defaults << :"attributes.#{attribute}"
51
+ defaults << options.delete(:default) if options[:default]
52
+ defaults << attribute.to_s.humanize
53
+
54
+ options.reverse_merge! :count => 1, :default => defaults
55
+ I18n.translate(defaults.shift, options)
56
+ end
57
+
58
+ # Model.human_name is deprecated. Use Model.model_name.human instead.
59
+ def human_name(*args)
60
+ ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,5])
61
+ model_name.human(*args)
62
+ end
63
+ end
64
+ end
@@ -1,23 +1,105 @@
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'
2
4
  require 'active_support/core_ext/hash/keys'
5
+ require 'active_model/errors'
6
+ require 'active_model/validations/callbacks'
3
7
 
4
8
  module ActiveModel
9
+
10
+ # == Active Model Validations
11
+ #
12
+ # Provides a full validation framework to your objects.
13
+ #
14
+ # A minimal implementation could be:
15
+ #
16
+ # class Person
17
+ # include ActiveModel::Validations
18
+ #
19
+ # attr_accessor :first_name, :last_name
20
+ #
21
+ # validates_each :first_name, :last_name do |record, attr, value|
22
+ # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
23
+ # end
24
+ # end
25
+ #
26
+ # Which provides you with the full standard validation stack that you
27
+ # know from ActiveRecord.
28
+ #
29
+ # person = Person.new
30
+ # person.valid?
31
+ # #=> true
32
+ # person.invalid?
33
+ # #=> false
34
+ # person.first_name = 'zoolander'
35
+ # person.valid?
36
+ # #=> false
37
+ # person.invalid?
38
+ # #=> true
39
+ # person.errors
40
+ # #=> #<OrderedHash {:first_name=>["starts with z."]}>
41
+ #
42
+ # Note that ActiveModel::Validations automatically adds an +errors+ method
43
+ # to your instances initialized with a new ActiveModel::Errors object, so
44
+ # there is no need for you to do this manually.
45
+ #
5
46
  module Validations
6
47
  extend ActiveSupport::Concern
7
48
  include ActiveSupport::Callbacks
8
49
 
9
50
  included do
51
+ extend ActiveModel::Translation
52
+
53
+ extend HelperMethods
54
+ include HelperMethods
55
+
56
+ attr_accessor :validation_context
10
57
  define_callbacks :validate, :scope => :name
58
+
59
+ class_attribute :_validators
60
+ self._validators = Hash.new { |h,k| h[k] = [] }
11
61
  end
12
62
 
13
63
  module ClassMethods
64
+ # Validates each attribute against a block.
65
+ #
66
+ # class Person
67
+ # include ActiveModel::Validations
68
+ #
69
+ # attr_accessor :first_name, :last_name
70
+ #
71
+ # validates_each :first_name, :last_name do |record, attr, value|
72
+ # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
73
+ # end
74
+ # end
75
+ #
76
+ # Options:
77
+ # * <tt>:on</tt> - Specifies when this validation is active (default is
78
+ # <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
79
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
80
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
81
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
82
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
83
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
84
+ # proc or string should return or evaluate to a true or false value.
85
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
86
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
87
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
88
+ # method, proc or string should return or evaluate to a true or false value.
89
+ def validates_each(*attr_names, &block)
90
+ options = attr_names.extract_options!.symbolize_keys
91
+ validates_with BlockValidator, options.merge(:attributes => attr_names.flatten), &block
92
+ end
93
+
14
94
  # Adds a validation method or block to the class. This is useful when
15
- # overriding the +validate+ instance method becomes too unwieldly and
95
+ # overriding the +validate+ instance method becomes too unwieldy and
16
96
  # you're looking for more descriptive declaration of your validations.
17
97
  #
18
98
  # This can be done with a symbol pointing to a method:
19
99
  #
20
- # class Comment < ActiveRecord::Base
100
+ # class Comment
101
+ # include ActiveModel::Validations
102
+ #
21
103
  # validate :must_be_friends
22
104
  #
23
105
  # def must_be_friends
@@ -25,9 +107,11 @@ module ActiveModel
25
107
  # end
26
108
  # end
27
109
  #
28
- # Or with a block which is passed the current record to be validated:
110
+ # Or with a block which is passed with the current record to be validated:
111
+ #
112
+ # class Comment
113
+ # include ActiveModel::Validations
29
114
  #
30
- # class Comment < ActiveRecord::Base
31
115
  # validate do |comment|
32
116
  # comment.must_be_friends
33
117
  # end
@@ -37,48 +121,37 @@ module ActiveModel
37
121
  # end
38
122
  # end
39
123
  #
40
- # This usage applies to +validate_on_create+ and +validate_on_update as well+.
41
-
42
- # Validates each attribute against a block.
43
- #
44
- # class Person < ActiveRecord::Base
45
- # validates_each :first_name, :last_name do |record, attr, value|
46
- # record.errors.add attr, 'starts with z.' if value[0] == ?z
47
- # end
48
- # end
49
- #
50
- # Options:
51
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
52
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
53
- # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
54
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
55
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
56
- # method, proc or string should return or evaluate to a true or false value.
57
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
58
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
59
- # method, proc or string should return or evaluate to a true or false value.
60
- def validates_each(*attrs)
61
- options = attrs.extract_options!.symbolize_keys
62
- attrs = attrs.flatten
63
-
64
- # Declare the validation.
65
- validate options do |record|
66
- attrs.each do |attr|
67
- value = record.send(:read_attribute_for_validation, attr)
68
- next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
69
- yield record, attr, value
70
- end
71
- end
72
- end
73
-
74
124
  def validate(*args, &block)
75
125
  options = args.last
76
126
  if options.is_a?(Hash) && options.key?(:on)
77
- options[:if] = Array(options[:if])
78
- options[:if] << "@_on_validate == :#{options[:on]}"
127
+ options[:if] = Array.wrap(options[:if])
128
+ options[:if] << "validation_context == :#{options[:on]}"
79
129
  end
80
130
  set_callback(:validate, *args, &block)
81
131
  end
132
+
133
+ # List all validators that are being used to validate the model using
134
+ # +validates_with+ method.
135
+ def validators
136
+ _validators.values.flatten.uniq
137
+ end
138
+
139
+ # List all validators that being used to validate a specific attribute.
140
+ def validators_on(attribute)
141
+ _validators[attribute.to_sym]
142
+ end
143
+
144
+ # Check if method is an attribute method or not.
145
+ def attribute_method?(attribute)
146
+ method_defined?(attribute)
147
+ end
148
+
149
+ # Copy validators on inheritance.
150
+ def inherited(base)
151
+ dup = _validators.dup
152
+ base._validators = dup.each { |k, v| dup[k] = v.dup }
153
+ super
154
+ end
82
155
  end
83
156
 
84
157
  # Returns the Errors object that holds all information about attribute error messages.
@@ -86,39 +159,48 @@ module ActiveModel
86
159
  @errors ||= Errors.new(self)
87
160
  end
88
161
 
89
- # Runs all the specified validations and returns true if no errors were added otherwise false.
90
- def valid?
162
+ # Runs all the specified validations and returns true if no errors were added
163
+ # otherwise false. Context can optionally be supplied to define which callbacks
164
+ # to test against (the context is defined on the validations using :on).
165
+ def valid?(context = nil)
166
+ current_context, self.validation_context = validation_context, context
91
167
  errors.clear
92
- _run_validate_callbacks
93
- errors.empty?
168
+ run_validations!
169
+ ensure
170
+ self.validation_context = current_context
94
171
  end
95
172
 
96
- # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
97
- def invalid?
98
- !valid?
173
+ # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added,
174
+ # false otherwise.
175
+ def invalid?(context = nil)
176
+ !valid?(context)
99
177
  end
100
178
 
101
- protected
102
- # Hook method defining how an attribute value should be retieved. By default this is assumed
103
- # to be an instance named after the attribute. Override this method in subclasses should you
104
- # need to retrieve the value for a given attribute differently e.g.
105
- # class MyClass
106
- # include ActiveModel::Validations
107
- #
108
- # def initialize(data = {})
109
- # @data = data
110
- # end
111
- #
112
- # private
113
- #
114
- # def read_attribute_for_validation(key)
115
- # @data[key]
116
- # end
117
- # end
118
- #
119
- def read_attribute_for_validation(key)
120
- send(key)
121
- end
179
+ # Hook method defining how an attribute value should be retrieved. By default
180
+ # this is assumed to be an instance named after the attribute. Override this
181
+ # method in subclasses should you need to retrieve the value for a given
182
+ # attribute differently:
183
+ #
184
+ # class MyClass
185
+ # include ActiveModel::Validations
186
+ #
187
+ # def initialize(data = {})
188
+ # @data = data
189
+ # end
190
+ #
191
+ # def read_attribute_for_validation(key)
192
+ # @data[key]
193
+ # end
194
+ # end
195
+ #
196
+ alias :read_attribute_for_validation :send
197
+
198
+ protected
199
+
200
+ def run_validations!
201
+ _run_validate_callbacks
202
+ errors.empty?
203
+ end
122
204
  end
123
205
  end
124
206
 
@@ -1,47 +1,67 @@
1
1
  module ActiveModel
2
+
3
+ # == Active Model Acceptance Validator
2
4
  module Validations
3
- module ClassMethods
4
- # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
5
+ class AcceptanceValidator < EachValidator
6
+ def initialize(options)
7
+ super(options.reverse_merge(:allow_nil => true, :accept => "1"))
8
+ end
9
+
10
+ def validate_each(record, attribute, value)
11
+ unless value == options[:accept]
12
+ record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
13
+ end
14
+ end
15
+
16
+ def setup(klass)
17
+ # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility
18
+ # as instance_methods returns symbols unlike 1.8 which returns strings.
19
+ attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
20
+ attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
21
+ klass.send(:attr_reader, *attr_readers)
22
+ klass.send(:attr_writer, *attr_writers)
23
+ end
24
+ end
25
+
26
+ module HelperMethods
27
+ # Encapsulates the pattern of wanting to validate the acceptance of a
28
+ # terms of service check box (or similar agreement). Example:
5
29
  #
6
30
  # class Person < ActiveRecord::Base
7
31
  # validates_acceptance_of :terms_of_service
8
32
  # validates_acceptance_of :eula, :message => "must be abided"
9
33
  # end
10
34
  #
11
- # If the database column does not exist, the +terms_of_service+ attribute is entirely virtual. This check is
12
- # performed only if +terms_of_service+ is not +nil+ and by default on save.
35
+ # If the database column does not exist, the +terms_of_service+ attribute
36
+ # is entirely virtual. This check is performed only if +terms_of_service+
37
+ # is not +nil+ and by default on save.
13
38
  #
14
39
  # Configuration options:
15
- # * <tt>:message</tt> - A custom error message (default is: "must be accepted").
16
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
17
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is true).
18
- # * <tt>:accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
19
- # makes it easy to relate to an HTML checkbox. This should be set to +true+ if you are validating a database
20
- # column, since the attribute is typecast from "1" to +true+ before validation.
21
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
22
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
23
- # method, proc or string should return or evaluate to a true or false value.
24
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
25
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
26
- # method, proc or string should return or evaluate to a true or false value.
40
+ # * <tt>:message</tt> - A custom error message (default is: "must be
41
+ # accepted").
42
+ # * <tt>:on</tt> - Specifies when this validation is active (default is
43
+ # <tt>:save</tt>, other options are <tt>:create</tt> and
44
+ # <tt>:update</tt>).
45
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
46
+ # is true).
47
+ # * <tt>:accept</tt> - Specifies value that is considered accepted.
48
+ # The default value is a string "1", which makes it easy to relate to
49
+ # an HTML checkbox. This should be set to +true+ if you are validating
50
+ # a database column, since the attribute is typecast from "1" to +true+
51
+ # before validation.
52
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
53
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
54
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
55
+ # method, proc or string should return or evaluate to a true or false
56
+ # value.
57
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
58
+ # determine if the validation should not occur (for example,
59
+ # <tt>:unless => :skip_validation</tt>, or
60
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
61
+ # The method, proc or string should return or evaluate to a true or
62
+ # false value.
27
63
  def validates_acceptance_of(*attr_names)
28
- configuration = { :allow_nil => true, :accept => "1" }
29
- configuration.update(attr_names.extract_options!)
30
-
31
- db_cols = begin
32
- column_names
33
- rescue Exception # To ignore both statement and connection errors
34
- []
35
- end
36
-
37
- names = attr_names.reject { |name| db_cols.include?(name.to_s) }
38
- attr_accessor(*names)
39
-
40
- validates_each(attr_names,configuration) do |record, attr_name, value|
41
- unless value == configuration[:accept]
42
- record.errors.add(attr_name, :accepted, :default => configuration[:message])
43
- end
44
- end
64
+ validates_with AcceptanceValidator, _merge_attributes(attr_names)
45
65
  end
46
66
  end
47
67
  end
@@ -0,0 +1,57 @@
1
+ require 'active_support/callbacks'
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ module Callbacks
6
+ # == Active Model Validation callbacks
7
+ #
8
+ # Provides an interface for any class to have <tt>before_validation</tt> and
9
+ # <tt>after_validation</tt> callbacks.
10
+ #
11
+ # First, extend ActiveModel::Callbacks from the class you are creating:
12
+ #
13
+ # class MyModel
14
+ # include ActiveModel::Validations::Callbacks
15
+ #
16
+ # before_validation :do_stuff_before_validation
17
+ # after_validation :do_tuff_after_validation
18
+ # end
19
+ #
20
+ # Like other before_* callbacks if <tt>before_validation</tt> returns false
21
+ # then <tt>valid?</tt> will not be called.
22
+ extend ActiveSupport::Concern
23
+
24
+ included do
25
+ include ActiveSupport::Callbacks
26
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
27
+ end
28
+
29
+ module ClassMethods
30
+ def before_validation(*args, &block)
31
+ options = args.last
32
+ if options.is_a?(Hash) && options[:on]
33
+ options[:if] = Array.wrap(options[:if])
34
+ options[:if] << "self.validation_context == :#{options[:on]}"
35
+ end
36
+ set_callback(:validation, :before, *args, &block)
37
+ end
38
+
39
+ def after_validation(*args, &block)
40
+ options = args.extract_options!
41
+ options[:prepend] = true
42
+ options[:if] = Array.wrap(options[:if])
43
+ options[:if] << "!halted && value != false"
44
+ options[:if] << "self.validation_context == :#{options[:on]}" if options[:on]
45
+ set_callback(:validation, :after, *(args << options), &block)
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ # Overwrite run validations to include callbacks.
52
+ def run_validations!
53
+ _run_validation_callbacks { super }
54
+ end
55
+ end
56
+ end
57
+ end