diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2010-03-12 16:00:01 +0000 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2010-03-12 16:00:01 +0000 |
commit | e68bfaf1fe1a7890a67af6f444281185f507cf9e (patch) | |
tree | 5e73caccdcdd65d0ac97f9eb92195928f30925f2 /activemodel | |
parent | ef6462c73003b28c8e060a06120abb9cd67b6d52 (diff) | |
parent | 16846553b8866eab2aa3b128a2a23a221a25f7e3 (diff) | |
download | rails-e68bfaf1fe1a7890a67af6f444281185f507cf9e.tar.gz rails-e68bfaf1fe1a7890a67af6f444281185f507cf9e.tar.bz2 rails-e68bfaf1fe1a7890a67af6f444281185f507cf9e.zip |
Merge remote branch 'mainstream/master'
Conflicts:
activerecord/lib/active_record/base.rb
railties/lib/rails/configuration.rb
railties/lib/rails/log_subscriber.rb
Diffstat (limited to 'activemodel')
-rwxr-xr-x | activemodel/Rakefile | 10 | ||||
-rw-r--r-- | activemodel/activemodel.gemspec | 10 | ||||
-rw-r--r-- | activemodel/examples/validations.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/attribute_methods.rb | 11 | ||||
-rw-r--r-- | activemodel/lib/active_model/conversion.rb | 45 | ||||
-rw-r--r-- | activemodel/lib/active_model/lint.rb | 40 | ||||
-rw-r--r-- | activemodel/lib/active_model/naming.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations.rb | 23 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/with.rb | 9 | ||||
-rw-r--r-- | activemodel/lib/active_model/validator.rb | 18 | ||||
-rw-r--r-- | activemodel/lib/active_model/version.rb | 5 | ||||
-rw-r--r-- | activemodel/test/cases/conversion_test.rb | 25 | ||||
-rw-r--r-- | activemodel/test/cases/helper.rb | 3 | ||||
-rw-r--r-- | activemodel/test/cases/lint_test.rb | 10 | ||||
-rw-r--r-- | activemodel/test/cases/validations/presence_validation_test.rb | 26 | ||||
-rw-r--r-- | activemodel/test/cases/validations/with_validation_test.rb | 1 | ||||
-rw-r--r-- | activemodel/test/cases/validations_test.rb | 27 | ||||
-rw-r--r-- | activemodel/test/models/contact.rb | 8 |
18 files changed, 213 insertions, 62 deletions
diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 14c02f183f..d4a00c5073 100755 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -1,19 +1,11 @@ dir = File.dirname(__FILE__) -require "#{dir}/lib/active_model/version" - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'activemodel' -PKG_VERSION = ActiveModel::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" -RELEASE_NAME = "REL #{PKG_VERSION}" - require 'rake/testtask' task :default => :test Rake::TestTask.new do |t| - t.libs << "#{dir}/test" + t.libs << "test" t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort t.warning = true end diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index b4fbe26d97..eb9ef7b7c0 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'activemodel' - s.version = '3.0.0.beta1' - s.summary = "A toolkit for building other modeling frameworks like ActiveRecord" - s.description = %q{Extracts common modeling concerns from ActiveRecord to share between similar frameworks like ActiveResource.} + s.version = version + s.summary = 'A toolkit for building modeling frameworks (part of Rails).' + s.description = 'A toolkit for building modeling frameworks like Active Record and Active Resource. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' s.required_ruby_version = '>= 1.8.7' s.author = "David Heinemeier Hansson" @@ -13,7 +15,7 @@ Gem::Specification.new do |s| s.has_rdoc = true - s.add_dependency('activesupport', '= 3.0.0.beta1') + s.add_dependency('activesupport', version) s.require_path = 'lib' s.files = Dir["CHANGELOG", "MIT-LICENSE", "README", "lib/**/*"] diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb index b039897ea5..0b2706076f 100644 --- a/activemodel/examples/validations.rb +++ b/activemodel/examples/validations.rb @@ -16,7 +16,7 @@ class Person @persisted = true end - def new_record? + def persisted? @persisted end end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index f7fb66bdfc..c1334069fa 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -21,7 +21,6 @@ module ActiveModel # A minimal implementation could be: # # class Person - # # include ActiveModel::AttributeMethods # # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' @@ -44,9 +43,13 @@ module ActiveModel # def reset_attribute_to_default!(attr) # send("#{attr}=", "Default Name") # end - # # end - # + # + # Please notice that whenever you include ActiveModel::AtributeMethods in your class, + # it requires you to implement a <tt>attributes</tt> methods which returns a hash with + # each attribute name in your model as hash key and the attribute value as hash value. + # Hash keys must be a string. + # module AttributeMethods extend ActiveSupport::Concern @@ -85,7 +88,7 @@ module ActiveModel # AttributePerson.inheritance_column # # => 'address_id' def define_attr_method(name, value=nil, &block) - sing = metaclass + sing = singleton_class sing.send :alias_method, "original_#{name}", name if block_given? sing.send :define_method, name, &block diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index c14a07c7dc..585c20dcdf 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -1,19 +1,44 @@ module ActiveModel - # If your object is already designed to implement all of the Active Model featurs - # include this module in your Class. - # - # class MyClass + # Handle default conversions: to_model, to_key and to_param. + # + # == Example + # + # Let's take for example this non persisted object. + # + # class ContactMessage # include ActiveModel::Conversion + # + # # ContactMessage are never persisted in the DB + # def persisted? + # false + # end # end - # - # Returns self to the <tt>:to_model</tt> method. - # - # If your model does not act like an Active Model object, then you should define - # <tt>:to_model</tt> yourself returning a proxy object that wraps your object - # with Active Model compliant methods. + # + # cm = ContactMessage.new + # cm.to_model == self #=> true + # cm.to_key #=> nil + # cm.to_param #=> nil + # module Conversion + # If your object is already designed to implement all of the Active Model you can use + # the default to_model implementation, which simply returns self. + # + # If your model does not act like an Active Model object, then you should define + # <tt>:to_model</tt> yourself returning a proxy object that wraps your object + # with Active Model compliant methods. def to_model self end + + # Returns an Enumerable of all (primary) key attributes or nil if persisted? is fakse + def to_key + persisted? ? [id] : nil + end + + # Returns a string representing the object's key suitable for use in URLs, + # or nil if persisted? is false + def to_param + to_key ? to_key.join('-') : nil + end end end diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 7bf0ad712d..13ddb622d1 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -13,6 +13,33 @@ module ActiveModel module Lint module Tests + + # == Responds to <tt>to_key</tt> + # + # Returns an Enumerable of all (primary) key attributes + # or nil if model.persisted? is false + def test_to_key + assert model.respond_to?(:to_key), "The model should respond to to_key" + def model.persisted?() false end + assert model.to_key.nil? + end + + # == Responds to <tt>to_param</tt> + # + # Returns a string representing the object's key suitable for use in URLs + # or nil if model.persisted? is false. + # + # Implementers can decide to either raise an exception or provide a default + # in case the record uses a composite primary key. There are no tests for this + # behavior in lint because it doesn't make sense to force any of the possible + # implementation strategies on the implementer. However, if the resource is + # not persisted?, then to_param should always return nil. + def test_to_param + assert model.respond_to?(:to_param), "The model should respond to to_param" + def model.persisted?() false end + assert model.to_param.nil? + end + # == Responds to <tt>valid?</tt> # # Returns a boolean that specifies whether the object is in a valid or invalid @@ -22,21 +49,16 @@ module ActiveModel assert_boolean model.valid?, "valid?" end - # == Responds to <tt>new_record?</tt> + # == Responds to <tt>persisted?</tt> # # 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 model.respond_to?(:new_record?), "The model should respond to new_record?" - assert_boolean model.new_record?, "new_record?" - end - - def test_destroyed? - assert model.respond_to?(:destroyed?), "The model should respond to destroyed?" - assert_boolean model.destroyed?, "destroyed?" + def test_persisted? + assert model.respond_to?(:persisted?), "The model should respond to persisted?" + assert_boolean model.persisted?, "persisted?" end # == Naming diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index b9fb5fe0c8..8cdd3d2fe8 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -42,7 +42,7 @@ module ActiveModel # To implement, just extend ActiveModel::Naming in your object: # # class BookCover - # exten ActiveModel::Naming + # extend ActiveModel::Naming # end # # BookCover.model_name #=> "BookCover" diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 03733a9c89..ba8648f8c9 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/keys' require 'active_model/errors' @@ -45,6 +46,9 @@ module ActiveModel included do extend ActiveModel::Translation define_callbacks :validate, :scope => :name + + class_attribute :_validators + self._validators = Hash.new { |h,k| h[k] = [] } end module ClassMethods @@ -117,12 +121,23 @@ module ActiveModel end set_callback(:validate, *args, &block) end - - private - + + # List all validators that being used to validate the model using +validates_with+ + # method. + def validators + _validators.values.flatten.uniq + end + + # List all validators that being used to validate a specific attribute. + def validators_on(attribute) + _validators[attribute.to_sym] + end + + private + def _merge_attributes(attr_names) options = attr_names.extract_options! - options.merge(:attributes => attr_names) + options.merge(:attributes => attr_names.flatten) end end diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index db563876af..83d3ea80d6 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -62,6 +62,15 @@ module ActiveModel args.each do |klass| validator = klass.new(options, &block) validator.setup(self) if validator.respond_to?(:setup) + + if validator.respond_to?(:attributes) && !validator.attributes.empty? + validator.attributes.each do |attribute| + _validators[attribute.to_sym] << validator + end + else + _validators[nil] << validator + end + validate(validator, options) end end diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index ad9729de00..b61f0cb266 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/module/anonymous" + module ActiveModel #:nodoc: # A simple base class that can be used along with # +ActiveModel::Validations::ClassMethods.validates_with+ @@ -88,11 +90,27 @@ module ActiveModel #:nodoc: class Validator attr_reader :options + # Returns the kind of the validator. + # + # == Examples + # + # PresenceValidator.kind #=> :presence + # UniquenessValidator.kind #=> :uniqueness + # + def self.kind + @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous? + end + # Accepts options that will be made availible through the +options+ reader. def initialize(options) @options = options end + # Return the kind for this validator. + def kind + self.class.kind + end + # Override this method in subclasses with validation logic, adding errors # to the records +errors+ array where necessary. def validate(record) diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index d51423ae1b..85d0eed180 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -2,8 +2,9 @@ module ActiveModel module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb new file mode 100644 index 0000000000..7669bf5f65 --- /dev/null +++ b/activemodel/test/cases/conversion_test.rb @@ -0,0 +1,25 @@ +require 'cases/helper' +require 'models/contact' + +class ConversionTest < ActiveModel::TestCase + test "to_model default implementation returns self" do + contact = Contact.new + assert_equal contact, contact.to_model + end + + test "to_key default implementation returns nil for new records" do + assert_nil Contact.new.to_key + end + + test "to_key default implementation returns the id in an array for persisted records" do + assert_equal [1], Contact.new(:id => 1).to_key + end + + test "to_param default implementation returns nil for new records" do + assert_nil Contact.new.to_param + end + + test "to_param default implementation returns a string of ids for persisted records" do + assert_equal "1", Contact.new(:id => 1).to_param + end +end
\ No newline at end of file diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 8bcbe54651..8578ab7dbd 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -1,5 +1,8 @@ require File.expand_path('../../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'config' require 'active_model' diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb index 63804004ee..68372160cd 100644 --- a/activemodel/test/cases/lint_test.rb +++ b/activemodel/test/cases/lint_test.rb @@ -1,18 +1,14 @@ -require "cases/helper" +require 'cases/helper' class LintTest < ActiveModel::TestCase include ActiveModel::Lint::Tests class CompliantModel extend ActiveModel::Naming - - def to_model - self - end + include ActiveModel::Conversion def valid?() true end - def new_record?() true end - def destroyed?() true end + def persisted?() false end def errors obj = Object.new diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb index 8b9795a90c..c4d787dadb 100644 --- a/activemodel/test/cases/validations/presence_validation_test.rb +++ b/activemodel/test/cases/validations/presence_validation_test.rb @@ -10,6 +10,12 @@ require 'models/custom_reader' class PresenceValidationTest < ActiveModel::TestCase include ActiveModel::TestsDatabase + teardown do + Topic.reset_callbacks(:validate) + Person.reset_callbacks(:validate) + CustomReader.reset_callbacks(:validate) + end + def test_validate_presences Topic.validates_presence_of(:title, :content) @@ -27,17 +33,21 @@ class PresenceValidationTest < ActiveModel::TestCase t.content = "like stuff" assert t.save - ensure - Topic.reset_callbacks(:validate) + end + + test 'accepts array arguments' do + Topic.validates_presence_of %w(title content) + t = Topic.new + assert !t.valid? + assert_equal ["can't be blank"], t.errors[:title] + assert_equal ["can't be blank"], t.errors[:content] end def test_validates_acceptance_of_with_custom_error_using_quotes - Person.validates_presence_of :karma, :message=> "This string contains 'single' and \"double\" quotes" + Person.validates_presence_of :karma, :message => "This string contains 'single' and \"double\" quotes" p = Person.new assert !p.valid? assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last - ensure - Person.reset_callbacks(:validate) end def test_validates_presence_of_for_ruby_class @@ -50,10 +60,8 @@ class PresenceValidationTest < ActiveModel::TestCase p.karma = "Cold" assert p.valid? - ensure - Person.reset_callbacks(:validate) end - + def test_validates_presence_of_for_ruby_class_with_custom_reader CustomReader.validates_presence_of :karma @@ -64,7 +72,5 @@ class PresenceValidationTest < ActiveModel::TestCase p[:karma] = "Cold" assert p.valid? - ensure - CustomReader.reset_callbacks(:validate) end end diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 66b072ea38..92df4dd6cd 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -9,6 +9,7 @@ class ValidatesWithTest < ActiveRecord::TestCase def teardown Topic.reset_callbacks(:validate) + Topic._validators.clear end ERROR_MESSAGE = "Validation error from validator" diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index eb100d1c35..9fedd84c73 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -10,6 +10,10 @@ require 'models/custom_reader' class ValidationsTest < ActiveModel::TestCase include ActiveModel::TestsDatabase + def setup + Topic._validators.clear + end + # Most of the tests mess with the validations of Topic, so lets repair it all the time. # Other classes we mess with will be dealt with in the specific tests def teardown @@ -220,4 +224,27 @@ class ValidationsTest < ActiveModel::TestCase assert !t.valid? assert ["NO BLANKS HERE"], t.errors[:title] end + + def test_list_of_validators_for_model + Topic.validates_presence_of :title + Topic.validates_length_of :title, :minimum => 2 + + assert_equal 2, Topic.validators.count + assert_equal [:presence, :length], Topic.validators.map(&:kind) + end + + def test_list_of_validators_on_an_attribute + Topic.validates_presence_of :title, :content + Topic.validates_length_of :title, :minimum => 2 + + assert_equal 2, Topic.validators_on(:title).count + assert_equal [:presence, :length], Topic.validators_on(:title).map(&:kind) + assert_equal 1, Topic.validators_on(:content).count + assert_equal [:presence], Topic.validators_on(:content).map(&:kind) + end + + def test_accessing_instance_of_validator_on_an_attribute + Topic.validates_length_of :title, :minimum => 10 + assert_equal 10, Topic.validators_on(:title).first.options[:minimum] + end end diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb index f9fb0af027..a9009fbdef 100644 --- a/activemodel/test/models/contact.rb +++ b/activemodel/test/models/contact.rb @@ -1,7 +1,13 @@ class Contact - attr_accessor :name, :age, :created_at, :awesome, :preferences + include ActiveModel::Conversion + + attr_accessor :id, :name, :age, :created_at, :awesome, :preferences def initialize(options = {}) options.each { |name, value| send("#{name}=", value) } end + + def persisted? + id + end end |