diff options
Diffstat (limited to 'activemodel/lib/active_model')
-rw-r--r-- | activemodel/lib/active_model/dirty.rb | 14 | ||||
-rw-r--r-- | activemodel/lib/active_model/lint.rb | 96 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/numericality.rb | 21 | ||||
-rw-r--r-- | activemodel/lib/active_model/version.rb | 9 |
4 files changed, 139 insertions, 1 deletions
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 624c3647ca..735c61df74 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -72,12 +72,26 @@ module ActiveModel changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h } end + # Map of attributes that were changed when the model was saved. + # person.name # => 'bob' + # person.name = 'robert' + # person.save + # person.previous_changes # => {'name' => ['bob, 'robert']} + def previous_changes + previously_changed_attributes + end + private # Map of change <tt>attr => original value</tt>. def changed_attributes @changed_attributes ||= {} end + # Map of fields that were changed when the model was saved + def previously_changed_attributes + @previously_changed || {} + end + # Handle <tt>*_changed?</tt> for +method_missing+. def attribute_changed?(attr) changed_attributes.include?(attr) diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb new file mode 100644 index 0000000000..46af8ca9de --- /dev/null +++ b/activemodel/lib/active_model/lint.rb @@ -0,0 +1,96 @@ +require "test/unit" +require "test/unit/ui/console/testrunner" + +# You can test whether an object is compliant with the ActiveModel API by +# calling ActiveModel::Compliance.test(object). It will emit a Test::Unit +# output that tells you whether your object is fully compliant, or if not, +# which aspects of the API are not implemented. +# +# These tests do not attempt to determine the semantic correctness of the +# returned values. For instance, you could implement valid? to always +# return true, and the tests would pass. It is up to you to ensure that +# the values are semantically meaningful. +# +# Objects you pass in are expected to return a compliant object from a +# call to to_model. It is perfectly fine for to_model to return self. + +module ActiveModel + module Lint + def self.test(object, verbosity = 2, output = STDOUT) + test_class = Class.new(::Test::Unit::TestCase) do + include Test + + define_method(:setup) do + assert object.respond_to?(:to_model), "The object should respond_to :to_model" + @object = object.to_model + super + end + end + + ::Test::Unit::UI::Console::TestRunner.new(test_class, verbosity, output).start + end + + module Test + def assert_boolean(name, result) + assert result == true || result == false, "#{name} should be a boolean" + end + + # valid? + # ------ + # + # Returns a boolean that specifies whether the object is in a valid or invalid + # state. + def test_valid? + assert @object.respond_to?(:valid?), "The model should respond to valid?" + assert_boolean "valid?", @object.valid? + end + + # new_record? + # ----------- + # + # Returns a boolean that specifies whether the object has been persisted yet. + # This is used when calculating the URL for an object. If the object is + # not persisted, a form for that object, for instance, will be POSTed to the + # collection. If it is persisted, a form for the object will put PUTed to the + # URL for the object. + def test_new_record? + assert @object.respond_to?(:new_record?), "The model should respond to new_record?" + assert_boolean "new_record?", @object.new_record? + end + + def test_destroyed? + assert @object.respond_to?(:new_record?), "The model should respond to destroyed?" + assert_boolean "destroyed?", @object.destroyed? + end + + # errors + # ------ + # + # Returns an object that has :[] and :full_messages defined on it. See below + # for more details. + def setup + assert @object.respond_to?(:errors), "The model should respond to errors" + @errors = @object.errors + end + + # This module tests the #errors object + module Errors + # Returns an Array of Strings that are the errors for the attribute in + # question. If localization is used, the Strings should be localized + # for the current locale. If no error is present, this method should + # return an empty Array. + def test_errors_aref + assert @errors[:hello].is_a?(Array), "errors#[] should return an Array" + end + + # Returns an Array of all error messages for the object. Each message + # should contain information about the field, if applicable. + def test_errors_full_messages + assert @errors.full_messages.is_a?(Array), "errors#full_messages should return an Array" + end + end + + include Errors + end + end +end
\ No newline at end of file diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index ada6e28594..32dbcd82d0 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -31,6 +31,21 @@ module ActiveModel # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. + # + # The following checks can also be supplied with a proc or a symbol which corresponds to a method: + # * <tt>:greater_than</tt> + # * <tt>:greater_than_or_equal_to</tt> + # * <tt>:equal_to</tt> + # * <tt>:less_than</tt> + # * <tt>:less_than_or_equal_to</tt> + # + # class Person < ActiveRecord::Base + # validates_numericality_of :width, :less_than => Proc.new { |person| person.height } + # validates_numericality_of :width, :greater_than => :minimum_weight + # end + # + # + def validates_numericality_of(*attr_names) configuration = { :only_integer => false, :allow_nil => false } configuration.update(attr_names.extract_options!) @@ -38,7 +53,8 @@ module ActiveModel numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys (numericality_options - [ :odd, :even ]).each do |option| - raise ArgumentError, ":#{option} must be a number" unless configuration[option].is_a?(Numeric) + value = configuration[option] + raise ArgumentError, ":#{option} must be a number, a symbol or a proc" unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol) end validates_each(attr_names,configuration) do |record, attr_name, value| @@ -74,6 +90,9 @@ module ActiveModel record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message]) end else + configuration[option] = configuration[option].call(record) if configuration[option].is_a? Proc + configuration[option] = record.method(configuration[option]).call if configuration[option].is_a? Symbol + unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]] record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) end diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb new file mode 100644 index 0000000000..0c233b7b4f --- /dev/null +++ b/activemodel/lib/active_model/version.rb @@ -0,0 +1,9 @@ +module ActiveModel + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 0 + TINY = "pre" + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end |