activemodel 3.0.0.beta3 → 3.0.0.beta4

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.
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: []