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,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