From 9acd686753c43612984aaa4002e80113fda2b255 Mon Sep 17 00:00:00 2001 From: snusnu Date: Sat, 20 Feb 2010 08:24:10 +0100 Subject: Adds #key and #to_param to the AMo interface This commit introduces two new methods that every AMo compliant object must implement. Below are the default implementations along with the implied interface contract. # Returns an Enumerable of all (primary) key # attributes or nil if new_record? is true def key new_record? ? nil : [1] end # Returns a string representing the object's key # suitable for use in URLs, or nil if new_record? # is true def to_param key ? key.first.to_s : nil end 1) The #key method Previously rails' record_identifier code, which is used in the #dom_id helper, relied on calling #id on the record to provide a reasonable DOM id. Now with rails3 being all ORM agnostic, it's not safe anymore to assume that every record ever will have an #id as its primary key attribute. Having a #key method available on every AMo object means that #dom_id can be implemented using record.to_model.key # instead of record.id Using this we're able to take composite primary keys into account (e.g. available in datamapper) by implementing #dom_id using a newly added record_key_for_dom_id(record) method. The user can overwrite this method to provide customized versions of the object's key used in #dom_id. Also, dealing with more complex keys that can contain arbitrary strings, means that we need to make sure that we only provide DOM ids that are valid according to the spec. For this reason, this patch sends the key provided through a newly added sanitize_dom_id(candidate_id) method, that makes sure we only produce valid HTML The reason to not just add #dom_id to the AMo interface was that it feels like providing a DOM id should not be a model concern. Adding #dom_id to the AMo interface would force these concern on the model, while it's better left to be implemented in a helper. Now one could say the same is true for #to_param, and actually I think that it doesn't really fit into the model either, but it's used in AR and it's a main part of integrating into the rails router. This is different from #dom_id which is only used in view helpers and can be implemented on top of a semantically more meaningful method like #key. 2) The #to_param method Since the rails router relies on #to_param to be present, AR::Base implements it and returns the id by default, allowing the user to overwrite the method if desired. Now with different ORMs integrating into rails, every ORM railtie needs to implement it's own #to_param implementation while already providing code to be AMo compliant. Since the whole point of AMo compliance seems to be to integrate any ORM seamlessly into rails, it seems fair that all we really need to do as another ORM, is to be AMo compliant. By including #to_param into the official interface, we can make sure that this code can be centralized in the various AMo compliance layers, and not be added separately by every ORM railtie. 3) All specs pass --- activemodel/lib/active_model/lint.rb | 23 +++++++++++++++++++++++ activemodel/lib/active_model/naming.rb | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 7bf0ad712d..c97286e61c 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -13,6 +13,29 @@ module ActiveModel module Lint module Tests + + # == Responds to key + # + # Returns an Enumerable of all (primary) key attributes + # or nil if model.new_record? is true + def test_key + assert model.respond_to?(:key), "The model should respond to key" + def model.new_record?() true end + assert model.key.nil? + def model.new_record?() false end + assert model.key.respond_to?(:each) + end + + # == Responds to to_param + # + # Returns a string representing the object's key suitable for use in URLs + # or nil if model.new_record? is true + def test_to_param + assert model.respond_to?(:to_param), "The model should respond to to_param" + def model.new_record?() true end + assert model.to_param.nil? + end + # == Responds to valid? # # Returns a boolean that specifies whether the object is in a valid or invalid diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 89e8f8b1ea..39512a427b 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -41,7 +41,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" -- cgit v1.2.3 From f81c6bc0404ba2a03eed0ec6c08bbac45661305f Mon Sep 17 00:00:00 2001 From: snusnu Date: Sun, 21 Feb 2010 03:05:28 +0100 Subject: AMo #key is now #to_key and CI is probably happy Obviously #key is a too common name to be included in the AMo interface, #to_key fits better and also relates nicely to #to_param. Thx wycats, koz and josevalim for the suggestion. AR's #to_key implementation now takes customized primary keys into account and there's a testcase for that too. The #to_param AMo lint makes no assumptions on how the method behaves in the presence of composite primary keys. It leaves the decision wether to provide a default, or to raise and thus signal to the user that implementing this method will need his special attention, up to the implementers. All AMo cares about is that #to_param is implemented and returns nil in case of a new_record?. The default CompliantObject used in lint_test provides a naive default implementation that just joins all key attributes with '-'. The #to_key default implementation in lint_test's CompliantObject now returns [id] instead of [1]. This was previously causing the (wrong) tests I added for AR's #to_key implementation to pass. The #to_key tests added with this patch should be better. The CI failure was caused by my lack of knowledge about the test:isolated task. The tests for the record_identifier code in action_controller are using fake non AR models and I forgot to stub the #to_key method over there. This issue didn't come up when running the test task, only test:isolated revealed it. This patch fixes that. All tests pass isolated or not, well, apart from one previously unpended test in action_controller that is unrelated to my patch. --- activemodel/lib/active_model/lint.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index c97286e61c..98413ac9fb 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -14,22 +14,28 @@ module ActiveModel module Lint module Tests - # == Responds to key + # == Responds to to_key # # Returns an Enumerable of all (primary) key attributes # or nil if model.new_record? is true - def test_key - assert model.respond_to?(:key), "The model should respond to key" + def test_to_key + assert model.respond_to?(:to_key), "The model should respond to to_key" def model.new_record?() true end - assert model.key.nil? + assert model.to_key.nil? def model.new_record?() false end - assert model.key.respond_to?(:each) + assert model.to_key.respond_to?(:each) end # == Responds to to_param # # Returns a string representing the object's key suitable for use in URLs - # or nil if model.new_record? is true + # or nil if model.new_record? is true. + # + # 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 + # a new_record?, 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.new_record?() true end -- cgit v1.2.3 From 9dd67fce25d3993a0ee494506ba246a45d395e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 21 Feb 2010 08:47:37 +0100 Subject: Add to_key and to_param methods to ActiveModel::Conversion. --- activemodel/lib/active_model/conversion.rb | 45 +++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index c14a07c7dc..78dc34197b 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 + # + # # Always a new record, since it's not persisted in the DB. + # def new_record? + # true + # end # end - # - # Returns self to the :to_model method. - # - # If your model does not act like an Active Model object, then you should define - # :to_model 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 + # :to_model 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 new_record? is true + def to_key + new_record? ? nil : [id] + end + + # Returns a string representing the object's key suitable for use in URLs, + # or nil if new_record? is true + def to_param + to_key ? to_key.join('-') : nil + end end end -- cgit v1.2.3 From 250c8092461f5e6bf62751b313f6605a37fd1b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 21 Feb 2010 11:09:21 +0100 Subject: Require persisted? in ActiveModel::Lint and remove new_record? and destroyed? methods. ActionPack does not care if the resource is new or if it was destroyed, it cares only if it's persisted somewhere or not. --- activemodel/lib/active_model/conversion.rb | 12 ++++++------ activemodel/lib/active_model/lint.rb | 25 ++++++++++--------------- 2 files changed, 16 insertions(+), 21 deletions(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 78dc34197b..585c20dcdf 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -8,9 +8,9 @@ module ActiveModel # class ContactMessage # include ActiveModel::Conversion # - # # Always a new record, since it's not persisted in the DB. - # def new_record? - # true + # # ContactMessage are never persisted in the DB + # def persisted? + # false # end # end # @@ -30,13 +30,13 @@ module ActiveModel self end - # Returns an Enumerable of all (primary) key attributes or nil if new_record? is true + # Returns an Enumerable of all (primary) key attributes or nil if persisted? is fakse def to_key - new_record? ? nil : [id] + persisted? ? [id] : nil end # Returns a string representing the object's key suitable for use in URLs, - # or nil if new_record? is true + # or nil if persisted? is false def to_param to_key ? to_key.join('-') : nil end diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 98413ac9fb..0e62e131a3 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -17,28 +17,28 @@ module ActiveModel # == Responds to to_key # # Returns an Enumerable of all (primary) key attributes - # or nil if model.new_record? is true + # 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.new_record?() true end + def model.persisted?() false end assert model.to_key.nil? - def model.new_record?() false end + def model.persisted?() true end assert model.to_key.respond_to?(:each) end # == Responds to to_param # # Returns a string representing the object's key suitable for use in URLs - # or nil if model.new_record? is true. + # 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 - # a new_record?, then to_param should always return nil. + # 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.new_record?() true end + def model.persisted?() false end assert model.to_param.nil? end @@ -51,21 +51,16 @@ module ActiveModel assert_boolean model.valid?, "valid?" end - # == Responds to new_record? + # == Responds to persisted? # # 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 -- cgit v1.2.3 From 8f97e9d19abf02b33c5f7c0c1f1d5daf13e28893 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 18 Feb 2010 22:28:48 +0700 Subject: Add validators reflection so you can do 'Person.validators' and 'Person.validators_on(:name)'. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activemodel/lib/active_model/validations.rb | 21 ++++++++++++++++++--- activemodel/lib/active_model/validations/with.rb | 9 +++++++++ activemodel/lib/active_model/validator.rb | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 03733a9c89..7f6748a660 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,9 +121,20 @@ 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) 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) -- cgit v1.2.3 From f7b0a857e97304a5daeb47313759b9bf0d7e2fc9 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 25 Feb 2010 09:32:29 -0800 Subject: Use Object#singleton_class instead of #metaclass. Prefer Ruby's choice. --- activemodel/lib/active_model/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 200a6afbf0..143eb87f54 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -86,7 +86,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 -- cgit v1.2.3 From 2ba604950603302fd78bcefecfe043efaa8169dc Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 25 Feb 2010 13:07:48 -0800 Subject: Accept array of attributes as arg also, like 2.3 --- activemodel/lib/active_model/validations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 7f6748a660..ba8648f8c9 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -137,7 +137,7 @@ module ActiveModel def _merge_attributes(attr_names) options = attr_names.extract_options! - options.merge(:attributes => attr_names) + options.merge(:attributes => attr_names.flatten) end end -- cgit v1.2.3 From 32afcdcb889e651b13e161a04c4d6d2ea78a652f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 28 Feb 2010 11:39:39 +0100 Subject: ActiveModel::Lint should not require our models to respond to id. --- activemodel/lib/active_model/lint.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 0e62e131a3..13ddb622d1 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -22,8 +22,6 @@ module ActiveModel assert model.respond_to?(:to_key), "The model should respond to to_key" def model.persisted?() false end assert model.to_key.nil? - def model.persisted?() true end - assert model.to_key.respond_to?(:each) end # == Responds to to_param -- cgit v1.2.3 From ae8070e21be405331a1ad97f59c4e43d45f10eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 28 Feb 2010 11:43:13 +0100 Subject: Add missing information about attributes method. --- activemodel/lib/active_model/attribute_methods.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'activemodel/lib/active_model') diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 143eb87f54..588976f1d4 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 attributes 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 -- cgit v1.2.3 From a4111bbca0884e4a748ab32ba7d7b550ec8d9186 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 1 Mar 2010 23:03:07 -0500 Subject: Update versions of all components to normalize them to new format --- activemodel/lib/active_model/version.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activemodel/lib/active_model') 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 -- cgit v1.2.3