activemodel 3.0.0.beta3 → 3.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ *Rails 3.0.0 [beta 4] (June 8th, 2010)*
2
+
3
+ * JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
4
+
5
+
1
6
  *Rails 3.0.0 [beta 3] (April 13th, 2010)*
2
7
 
3
8
  * No changes
@@ -80,7 +80,7 @@ module ActiveModel
80
80
  #
81
81
  # end
82
82
  #
83
- # Provivdes you with:
83
+ # Provides you with:
84
84
  #
85
85
  # AttributePerson.primary_key
86
86
  # # => "sysid"
@@ -124,11 +124,12 @@ module ActiveModel
124
124
  @previously_changed
125
125
  end
126
126
 
127
+ # Map of change <tt>attr => original value</tt>.
128
+ def changed_attributes
129
+ @changed_attributes ||= {}
130
+ end
131
+
127
132
  private
128
- # Map of change <tt>attr => original value</tt>.
129
- def changed_attributes
130
- @changed_attributes ||= {}
131
- end
132
133
 
133
134
  # Handle <tt>*_changed?</tt> for +method_missing+.
134
135
  def attribute_changed?(attr)
@@ -170,13 +170,13 @@ module ActiveModel
170
170
  end
171
171
  end
172
172
 
173
- # Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
174
- # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
175
- # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
176
- # If no +messsage+ is supplied, :invalid is assumed.
173
+ # Adds +message+ to the error messages on +attribute+, which will be returned on a call to
174
+ # <tt>on(attribute)</tt> for the same attribute. More than one error can be added to the same
175
+ # +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
176
+ # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
177
177
  #
178
- # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
179
- # If +message+ is a Proc, it will be called, allowing for things like Time.now to be used within an error
178
+ # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
179
+ # If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
180
180
  def add(attribute, message = nil, options = {})
181
181
  message ||= :invalid
182
182
  message = generate_message(attribute, message, options) if message.is_a?(Symbol)
@@ -223,7 +223,7 @@ module ActiveModel
223
223
  else
224
224
  attr_name = attribute.to_s.gsub('.', '_').humanize
225
225
  attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
226
- options = { :default => "{{attribute}} {{message}}", :attribute => attr_name }
226
+ options = { :default => "%{attribute} %{message}", :attribute => attr_name }
227
227
 
228
228
  messages.each do |m|
229
229
  full_messages << I18n.t(:"errors.format", options.merge(:message => m))
@@ -1,7 +1,7 @@
1
1
  en:
2
2
  errors:
3
3
  # The default format use in full error messages.
4
- format: "{{attribute}} {{message}}"
4
+ format: "%{attribute} %{message}"
5
5
 
6
6
  # The values :model, :attribute and :value are always available for interpolation
7
7
  # The value :count is available when applicable. Can be used for pluralization.
@@ -13,14 +13,15 @@ en:
13
13
  accepted: "must be accepted"
14
14
  empty: "can't be empty"
15
15
  blank: "can't be blank"
16
- too_long: "is too long (maximum is {{count}} characters)"
17
- too_short: "is too short (minimum is {{count}} characters)"
18
- wrong_length: "is the wrong length (should be {{count}} characters)"
16
+ too_long: "is too long (maximum is %{count} characters)"
17
+ too_short: "is too short (minimum is %{count} characters)"
18
+ wrong_length: "is the wrong length (should be %{count} characters)"
19
19
  not_a_number: "is not a number"
20
- greater_than: "must be greater than {{count}}"
21
- greater_than_or_equal_to: "must be greater than or equal to {{count}}"
22
- equal_to: "must be equal to {{count}}"
23
- less_than: "must be less than {{count}}"
24
- less_than_or_equal_to: "must be less than or equal to {{count}}"
20
+ not_an_integer: "must be an integer"
21
+ greater_than: "must be greater than %{count}"
22
+ greater_than_or_equal_to: "must be greater than or equal to %{count}"
23
+ equal_to: "must be equal to %{count}"
24
+ less_than: "must be less than %{count}"
25
+ less_than_or_equal_to: "must be less than or equal to %{count}"
25
26
  odd: "must be odd"
26
27
  even: "must be even"
@@ -1,17 +1,13 @@
1
- require 'observer'
2
1
  require 'singleton'
3
2
  require 'active_support/core_ext/array/wrap'
4
3
  require 'active_support/core_ext/module/aliasing'
5
4
  require 'active_support/core_ext/string/inflections'
5
+ require 'active_support/core_ext/string/conversions'
6
6
 
7
7
  module ActiveModel
8
8
  module Observing
9
9
  extend ActiveSupport::Concern
10
10
 
11
- included do
12
- extend Observable
13
- end
14
-
15
11
  module ClassMethods
16
12
  # Activates the observers assigned. Examples:
17
13
  #
@@ -41,6 +37,26 @@ module ActiveModel
41
37
  observers.each { |o| instantiate_observer(o) }
42
38
  end
43
39
 
40
+ def add_observer(observer)
41
+ unless observer.respond_to? :update
42
+ raise ArgumentError, "observer needs to respond to `update'"
43
+ end
44
+ @observer_instances ||= []
45
+ @observer_instances << observer
46
+ end
47
+
48
+ def notify_observers(*arg)
49
+ if defined? @observer_instances
50
+ for observer in @observer_instances
51
+ observer.update(*arg)
52
+ end
53
+ end
54
+ end
55
+
56
+ def count_observers
57
+ @observer_instances.size
58
+ end
59
+
44
60
  protected
45
61
  def instantiate_observer(observer) #:nodoc:
46
62
  # string/symbol
@@ -56,7 +72,6 @@ module ActiveModel
56
72
  # Notify observers when the observed class is subclassed.
57
73
  def inherited(subclass)
58
74
  super
59
- changed
60
75
  notify_observers :observed_class_inherited, subclass
61
76
  end
62
77
  end
@@ -70,7 +85,6 @@ module ActiveModel
70
85
  # notify_observers(:after_save)
71
86
  # end
72
87
  def notify_observers(method)
73
- self.class.changed
74
88
  self.class.notify_observers(method, self)
75
89
  end
76
90
  end
@@ -1,5 +1,5 @@
1
1
  require 'active_support/json'
2
- require 'active_support/core_ext/class/attribute_accessors'
2
+ require 'active_support/core_ext/class/attribute'
3
3
 
4
4
  module ActiveModel
5
5
  module Serializers
@@ -10,7 +10,8 @@ module ActiveModel
10
10
  included do
11
11
  extend ActiveModel::Naming
12
12
 
13
- cattr_accessor :include_root_in_json, :instance_writer => true
13
+ class_attribute :include_root_in_json
14
+ self.include_root_in_json = true
14
15
  end
15
16
 
16
17
  # Returns a JSON string representing the model. Some configuration is
@@ -79,7 +80,11 @@ module ActiveModel
79
80
  # "title": "So I was thinking"}]}
80
81
  def encode_json(encoder)
81
82
  hash = serializable_hash(encoder.options)
82
- hash = { self.class.model_name.element => hash } if include_root_in_json
83
+ if include_root_in_json
84
+ custom_root = encoder.options && encoder.options[:root]
85
+ hash = { custom_root || self.class.model_name.element => hash }
86
+ end
87
+
83
88
  ActiveSupport::JSON.encode(hash)
84
89
  end
85
90
 
@@ -88,7 +93,9 @@ module ActiveModel
88
93
  end
89
94
 
90
95
  def from_json(json)
91
- self.attributes = ActiveSupport::JSON.decode(json)
96
+ hash = ActiveSupport::JSON.decode(json)
97
+ hash = hash.values.first if include_root_in_json
98
+ self.attributes = hash
92
99
  self
93
100
  end
94
101
  end
@@ -1,6 +1,8 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
2
  require 'active_support/core_ext/class/attribute_accessors'
3
+ require 'active_support/core_ext/array/conversions'
3
4
  require 'active_support/core_ext/hash/conversions'
5
+ require 'active_support/core_ext/hash/slice'
4
6
 
5
7
  module ActiveModel
6
8
  module Serializers
@@ -12,68 +14,31 @@ module ActiveModel
12
14
  class Attribute #:nodoc:
13
15
  attr_reader :name, :value, :type
14
16
 
15
- def initialize(name, serializable)
17
+ def initialize(name, serializable, raw_value=nil)
16
18
  @name, @serializable = name, serializable
19
+ @value = value || @serializable.send(name)
17
20
  @type = compute_type
18
- @value = compute_value
19
21
  end
20
22
 
21
- # There is a significant speed improvement if the value
22
- # does not need to be escaped, as <tt>tag!</tt> escapes all values
23
- # to ensure that valid XML is generated. For known binary
24
- # values, it is at least an order of magnitude faster to
25
- # Base64 encode binary values and directly put them in the
26
- # output XML than to pass the original value or the Base64
27
- # encoded value to the <tt>tag!</tt> method. It definitely makes
28
- # no sense to Base64 encode the value and then give it to
29
- # <tt>tag!</tt>, since that just adds additional overhead.
30
- def needs_encoding?
31
- ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
32
- end
33
-
34
- def decorations(include_types = true)
23
+ def decorations
35
24
  decorations = {}
36
-
37
- if type == :binary
38
- decorations[:encoding] = 'base64'
39
- end
40
-
41
- if include_types && type != :string
42
- decorations[:type] = type
43
- end
44
-
45
- if value.nil?
46
- decorations[:nil] = true
47
- end
48
-
25
+ decorations[:encoding] = 'base64' if type == :binary
26
+ decorations[:type] = type unless type == :string
27
+ decorations[:nil] = true if value.nil?
49
28
  decorations
50
29
  end
51
30
 
52
- protected
53
- def compute_type
54
- value = @serializable.send(name)
55
- type = Hash::XML_TYPE_NAMES[value.class.name]
56
- type ||= :string if value.respond_to?(:to_str)
57
- type ||= :yaml
58
- type
59
- end
60
-
61
- def compute_value
62
- value = @serializable.send(name)
31
+ protected
63
32
 
64
- if formatter = Hash::XML_FORMATTING[type.to_s]
65
- value ? formatter.call(value) : nil
66
- else
67
- value
68
- end
69
- end
33
+ def compute_type
34
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
35
+ type ||= :string if value.respond_to?(:to_str)
36
+ type ||= :yaml
37
+ type
38
+ end
70
39
  end
71
40
 
72
41
  class MethodAttribute < Attribute #:nodoc:
73
- protected
74
- def compute_type
75
- Hash::XML_TYPE_NAMES[@serializable.send(name).class.name] || :string
76
- end
77
42
  end
78
43
 
79
44
  attr_reader :options
@@ -92,104 +57,78 @@ module ActiveModel
92
57
  # then because <tt>:except</tt> is set to a default value, the second
93
58
  # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
94
59
  # <tt>:only</tt> is set, always delete <tt>:except</tt>.
95
- def serializable_attribute_names
96
- attribute_names = @serializable.attributes.keys.sort
97
-
60
+ def attributes_hash
61
+ attributes = @serializable.attributes
98
62
  if options[:only].any?
99
- attribute_names &= options[:only]
63
+ attributes.slice(*options[:only])
100
64
  elsif options[:except].any?
101
- attribute_names -= options[:except]
65
+ attributes.except(*options[:except])
66
+ else
67
+ attributes
102
68
  end
103
-
104
- attribute_names
105
69
  end
106
70
 
107
71
  def serializable_attributes
108
- serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) }
72
+ attributes_hash.map do |name, value|
73
+ self.class::Attribute.new(name, @serializable, value)
74
+ end
109
75
  end
110
76
 
111
- def serializable_method_attributes
77
+ def serializable_methods
112
78
  Array.wrap(options[:methods]).inject([]) do |methods, name|
113
- methods << MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s)
79
+ methods << self.class::MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s)
114
80
  methods
115
81
  end
116
82
  end
117
83
 
118
84
  def serialize
119
- args = [root]
120
-
121
- if options[:namespace]
122
- args << {:xmlns => options[:namespace]}
123
- end
85
+ require 'builder' unless defined? ::Builder
124
86
 
125
- if options[:type]
126
- args << {:type => options[:type]}
127
- end
87
+ options[:indent] ||= 2
88
+ options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
128
89
 
129
- builder.tag!(*args) do
130
- add_attributes
131
- procs = options.delete(:procs)
132
- options[:procs] = procs
133
- add_procs
134
- yield builder if block_given?
135
- end
136
- end
90
+ @builder = options[:builder]
91
+ @builder.instruct! unless options[:skip_instruct]
137
92
 
138
- private
139
- def builder
140
- @builder ||= begin
141
- require 'builder' unless defined? ::Builder
142
- options[:indent] ||= 2
143
- builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
93
+ root = (options[:root] || @serializable.class.model_name.element).to_s
94
+ root = ActiveSupport::XmlMini.rename_key(root, options)
144
95
 
145
- unless options[:skip_instruct]
146
- builder.instruct!
147
- options[:skip_instruct] = true
148
- end
149
-
150
- builder
151
- end
152
- end
153
-
154
- def root
155
- root = (options[:root] || @serializable.class.model_name.singular).to_s
156
- reformat_name(root)
157
- end
96
+ args = [root]
97
+ args << {:xmlns => options[:namespace]} if options[:namespace]
98
+ args << {:type => options[:type]} if options[:type] && !options[:skip_types]
158
99
 
159
- def dasherize?
160
- !options.has_key?(:dasherize) || options[:dasherize]
100
+ @builder.tag!(*args) do
101
+ add_attributes_and_methods
102
+ add_extra_behavior
103
+ add_procs
104
+ yield @builder if block_given?
161
105
  end
106
+ end
162
107
 
163
- def camelize?
164
- options.has_key?(:camelize) && options[:camelize]
165
- end
108
+ private
166
109
 
167
- def reformat_name(name)
168
- name = name.camelize if camelize?
169
- dasherize? ? name.dasherize : name
170
- end
110
+ def add_extra_behavior
111
+ end
171
112
 
172
- def add_attributes
173
- (serializable_attributes + serializable_method_attributes).each do |attribute|
174
- builder.tag!(
175
- reformat_name(attribute.name),
176
- attribute.value.to_s,
177
- attribute.decorations(!options[:skip_types])
178
- )
179
- end
113
+ def add_attributes_and_methods
114
+ (serializable_attributes + serializable_methods).each do |attribute|
115
+ key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
116
+ ActiveSupport::XmlMini.to_tag(key, attribute.value,
117
+ options.merge(attribute.decorations))
180
118
  end
119
+ end
181
120
 
182
- def add_procs
183
- if procs = options.delete(:procs)
184
- [ *procs ].each do |proc|
185
- if proc.arity > 1
186
- proc.call(options, @serializable)
187
- else
188
- proc.call(options)
189
- end
121
+ def add_procs
122
+ if procs = options.delete(:procs)
123
+ Array.wrap(procs).each do |proc|
124
+ if proc.arity == 1
125
+ proc.call(options)
126
+ else
127
+ proc.call(options, @serializable)
190
128
  end
191
129
  end
192
130
  end
131
+ end
193
132
  end
194
133
 
195
134
  def to_xml(options = {}, &block)
@@ -29,7 +29,7 @@ module ActiveModel
29
29
  # person.invalid?
30
30
  # #=> false
31
31
  # person.first_name = 'zoolander'
32
- # person.valid?
32
+ # person.valid?
33
33
  # #=> false
34
34
  # person.invalid?
35
35
  # #=> true
@@ -46,8 +46,14 @@ module ActiveModel
46
46
 
47
47
  included do
48
48
  extend ActiveModel::Translation
49
+
50
+ extend HelperMethods
51
+ include HelperMethods
52
+
49
53
  define_callbacks :validate, :scope => :name
50
54
 
55
+ attr_accessor :validation_context
56
+
51
57
  class_attribute :_validators
52
58
  self._validators = Hash.new { |h,k| h[k] = [] }
53
59
  end
@@ -117,7 +123,7 @@ module ActiveModel
117
123
  options = args.last
118
124
  if options.is_a?(Hash) && options.key?(:on)
119
125
  options[:if] = Array.wrap(options[:if])
120
- options[:if] << "@_on_validate == :#{options[:on]}"
126
+ options[:if] << "validation_context == :#{options[:on]}"
121
127
  end
122
128
  set_callback(:validate, *args, &block)
123
129
  end
@@ -133,11 +139,8 @@ module ActiveModel
133
139
  _validators[attribute.to_sym]
134
140
  end
135
141
 
136
- private
137
-
138
- def _merge_attributes(attr_names)
139
- options = attr_names.extract_options!
140
- options.merge(:attributes => attr_names.flatten)
142
+ def attribute_method?(attribute)
143
+ method_defined?(attribute)
141
144
  end
142
145
  end
143
146
 
@@ -147,15 +150,20 @@ module ActiveModel
147
150
  end
148
151
 
149
152
  # Runs all the specified validations and returns true if no errors were added otherwise false.
150
- def valid?
153
+ # Context can optionally be supplied to define which callbacks to test against (the context is
154
+ # defined on the validations using :on).
155
+ def valid?(context = nil)
156
+ current_context, self.validation_context = validation_context, context
151
157
  errors.clear
152
158
  _run_validate_callbacks
153
159
  errors.empty?
160
+ ensure
161
+ self.validation_context = current_context
154
162
  end
155
163
 
156
164
  # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
157
- def invalid?
158
- !valid?
165
+ def invalid?(context = nil)
166
+ !valid?(context)
159
167
  end
160
168
 
161
169
  # Hook method defining how an attribute value should be retieved. By default this is assumed
@@ -14,12 +14,14 @@ module ActiveModel
14
14
  def setup(klass)
15
15
  # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility
16
16
  # as instance_methods returns symbols unlike 1.8 which returns strings.
17
- new_attributes = attributes.reject { |name| klass.instance_methods.map(&:to_s).include?("#{name}=") }
18
- klass.send(:attr_accessor, *new_attributes)
17
+ attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
18
+ attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
19
+ klass.send(:attr_reader, *attr_readers)
20
+ klass.send(:attr_writer, *attr_writers)
19
21
  end
20
22
  end
21
23
 
22
- module ClassMethods
24
+ module HelperMethods
23
25
  # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
24
26
  #
25
27
  # class Person < ActiveRecord::Base
@@ -12,7 +12,7 @@ module ActiveModel
12
12
  end
13
13
  end
14
14
 
15
- module ClassMethods
15
+ module HelperMethods
16
16
  # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
17
17
  #
18
18
  # Model:
@@ -12,13 +12,13 @@ module ActiveModel
12
12
  end
13
13
  end
14
14
 
15
- module ClassMethods
15
+ module HelperMethods
16
16
  # Validates that the value of the specified attribute is not in a particular enumerable object.
17
17
  #
18
18
  # class Person < ActiveRecord::Base
19
19
  # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
20
20
  # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
21
- # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
21
+ # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
22
22
  # end
23
23
  #
24
24
  # Configuration options:
@@ -24,7 +24,7 @@ module ActiveModel
24
24
  end
25
25
  end
26
26
 
27
- module ClassMethods
27
+ module HelperMethods
28
28
  # Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
29
29
  # You can require that the attribute matches the regular expression:
30
30
  #
@@ -12,13 +12,13 @@ module ActiveModel
12
12
  end
13
13
  end
14
14
 
15
- module ClassMethods
15
+ module HelperMethods
16
16
  # Validates whether the value of the specified attribute is available in a particular enumerable object.
17
17
  #
18
18
  # class Person < ActiveRecord::Base
19
19
  # validates_inclusion_of :gender, :in => %w( m f )
20
20
  # validates_inclusion_of :age, :in => 0..99
21
- # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
21
+ # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
22
22
  # end
23
23
  #
24
24
  # Configuration options:
@@ -51,7 +51,7 @@ module ActiveModel
51
51
  end
52
52
  end
53
53
 
54
- module ClassMethods
54
+ module HelperMethods
55
55
 
56
56
  # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
57
57
  #
@@ -74,9 +74,9 @@ module ActiveModel
74
74
  # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
75
75
  # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
76
76
  # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
77
- # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)").
78
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
79
- # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)").
77
+ # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %{count} characters)").
78
+ # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
79
+ # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %{count} characters)").
80
80
  # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
81
81
  # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
82
82
  # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@@ -25,11 +25,18 @@ module ActiveModel
25
25
 
26
26
  return if options[:allow_nil] && raw_value.nil?
27
27
 
28
- unless value = parse_raw_value(raw_value, options)
28
+ unless value = parse_raw_value_as_a_number(raw_value)
29
29
  record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message])
30
30
  return
31
31
  end
32
32
 
33
+ if options[:only_integer]
34
+ unless value = parse_raw_value_as_an_integer(raw_value)
35
+ record.errors.add(attr_name, :not_an_integer, :value => raw_value, :default => options[:message])
36
+ return
37
+ end
38
+ end
39
+
33
40
  options.slice(*CHECKS.keys).each do |option, option_value|
34
41
  case option
35
42
  when :odd, :even
@@ -44,14 +51,15 @@ module ActiveModel
44
51
  record.errors.add(attr_name, option, :default => options[:message], :value => value, :count => option_value)
45
52
  end
46
53
  end
47
- end
54
+ end
48
55
  end
49
56
 
50
57
  protected
51
58
 
52
- def parse_raw_value(raw_value, options)
53
- if options[:only_integer]
54
- raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
59
+ def parse_raw_value_as_a_number(raw_value)
60
+ case raw_value
61
+ when /\A0[xX]/
62
+ nil
55
63
  else
56
64
  begin
57
65
  Kernel.Float(raw_value)
@@ -61,9 +69,13 @@ module ActiveModel
61
69
  end
62
70
  end
63
71
 
72
+ def parse_raw_value_as_an_integer(raw_value)
73
+ raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
74
+ end
75
+
64
76
  end
65
77
 
66
- module ClassMethods
78
+ module HelperMethods
67
79
  # Validates whether the value of the specified attribute is numeric by trying to convert it to
68
80
  # a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
69
81
  # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
@@ -8,7 +8,7 @@ module ActiveModel
8
8
  end
9
9
  end
10
10
 
11
- module ClassMethods
11
+ module HelperMethods
12
12
  # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
13
13
  #
14
14
  # class Person < ActiveRecord::Base
@@ -1,5 +1,13 @@
1
1
  module ActiveModel
2
2
  module Validations
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
10
+
3
11
  module ClassMethods
4
12
 
5
13
  # Passes the record off to the class or classes specified and allows them
@@ -75,5 +83,50 @@ module ActiveModel
75
83
  end
76
84
  end
77
85
  end
86
+
87
+ # Passes the record off to the class or classes specified and allows them
88
+ # to add errors based on more complex conditions.
89
+ #
90
+ # class Person
91
+ # include ActiveModel::Validations
92
+ #
93
+ # validates :instance_validations
94
+ #
95
+ # def instance_validations
96
+ # validates_with MyValidator
97
+ # end
98
+ # end
99
+ #
100
+ # Please consult the class method documentation for more information on
101
+ # creating your own validator.
102
+ #
103
+ # You may also pass it multiple classes, like so:
104
+ #
105
+ # class Person
106
+ # include ActiveModel::Validations
107
+ #
108
+ # validates :instance_validations, :on => :create
109
+ #
110
+ # def instance_validations
111
+ # validates_with MyValidator, MyOtherValidator
112
+ # end
113
+ # end
114
+ #
115
+ # Standard configuration options (:on, :if and :unless), which are
116
+ # available on the class version of validates_with, should instead be
117
+ # placed on the <tt>validates</tt> method as these are applied and tested
118
+ # in the callback
119
+ #
120
+ # If you pass any additional configuration options, they will be passed
121
+ # to the class and available as <tt>options</tt>, please refer to the
122
+ # class version of this method for more information
123
+ #
124
+ def validates_with(*args, &block)
125
+ options = args.extract_options!
126
+ args.each do |klass|
127
+ validator = klass.new(options, &block)
128
+ validator.validate(self)
129
+ end
130
+ end
78
131
  end
79
132
  end
@@ -88,6 +88,9 @@ module ActiveModel #:nodoc:
88
88
  # klass.send :attr_accessor, :custom_attribute
89
89
  # end
90
90
  # end
91
+ #
92
+ # This setup method is only called when used with validation macros or the
93
+ # class level <tt>validates_with</tt> method.
91
94
  #
92
95
  class Validator
93
96
  attr_reader :options
@@ -3,7 +3,7 @@ module ActiveModel
3
3
  MAJOR = 3
4
4
  MINOR = 0
5
5
  TINY = 0
6
- BUILD = "beta3"
6
+ BUILD = "beta4"
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
9
9
  end
metadata CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 3
7
7
  - 0
8
8
  - 0
9
- - beta3
10
- version: 3.0.0.beta3
9
+ - beta4
10
+ version: 3.0.0.beta4
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Heinemeier Hansson
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-04-13 00:00:00 -07:00
18
+ date: 2010-06-08 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -29,10 +29,38 @@ dependencies:
29
29
  - 3
30
30
  - 0
31
31
  - 0
32
- - beta3
33
- version: 3.0.0.beta3
32
+ - beta4
33
+ version: 3.0.0.beta4
34
34
  type: :runtime
35
35
  version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: builder
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 2
45
+ - 1
46
+ - 2
47
+ version: 2.1.2
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: i18n
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ - 4
60
+ - 1
61
+ version: 0.4.1
62
+ type: :runtime
63
+ version_requirements: *id003
36
64
  description: A toolkit for building modeling frameworks like Active Record and Active Resource. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.
37
65
  email: david@loudthinking.com
38
66
  executables: []