aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rwxr-xr-xactivemodel/Rakefile10
-rw-r--r--activemodel/activemodel.gemspec10
-rw-r--r--activemodel/examples/validations.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb11
-rw-r--r--activemodel/lib/active_model/conversion.rb45
-rw-r--r--activemodel/lib/active_model/lint.rb40
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb23
-rw-r--r--activemodel/lib/active_model/validations/with.rb9
-rw-r--r--activemodel/lib/active_model/validator.rb18
-rw-r--r--activemodel/lib/active_model/version.rb5
-rw-r--r--activemodel/test/cases/conversion_test.rb25
-rw-r--r--activemodel/test/cases/helper.rb3
-rw-r--r--activemodel/test/cases/lint_test.rb10
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb26
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations_test.rb27
-rw-r--r--activemodel/test/models/contact.rb8
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