aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model')
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb196
-rw-r--r--activemodel/lib/active_model/callbacks.rb4
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb78
-rw-r--r--activemodel/lib/active_model/naming.rb27
-rw-r--r--activemodel/lib/active_model/observing.rb3
-rw-r--r--activemodel/lib/active_model/secure_password.rb4
-rw-r--r--activemodel/lib/active_model/serialization.rb29
-rw-r--r--activemodel/lib/active_model/serializers/json.rb24
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb46
-rw-r--r--activemodel/lib/active_model/validations/length.rb2
-rw-r--r--activemodel/lib/active_model/validations/presence.rb6
-rw-r--r--activemodel/lib/active_model/validations/validates.rb6
-rw-r--r--activemodel/lib/active_model/validations/with.rb4
-rw-r--r--activemodel/lib/active_model/validator.rb8
15 files changed, 232 insertions, 207 deletions
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index bdc0eb4a0d..dba059eacd 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/class/attribute'
+require 'active_support/deprecation'
module ActiveModel
class MissingAttributeError < NoMethodError
@@ -56,54 +57,38 @@ module ActiveModel
module AttributeMethods
extend ActiveSupport::Concern
- COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
+ NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
+ CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
class_attribute :attribute_method_matchers, :instance_writer => false
- self.attribute_method_matchers = []
+ self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
module ClassMethods
- # Defines an "attribute" method (like +inheritance_column+ or +table_name+).
- # A new (class) method will be created with the given name. If a value is
- # specified, the new method will return that value (as a string).
- # Otherwise, the given block will be used to compute the value of the
- # method.
- #
- # The original method will be aliased, with the new name being prefixed
- # with "original_". This allows the new method to access the original
- # value.
- #
- # Example:
- #
- # class Person
- #
- # include ActiveModel::AttributeMethods
- #
- # cattr_accessor :primary_key
- # cattr_accessor :inheritance_column
- #
- # define_attr_method :primary_key, "sysid"
- # define_attr_method( :inheritance_column ) do
- # original_inheritance_column + "_id"
- # end
- #
- # end
- #
- # Provides you with:
- #
- # AttributePerson.primary_key
- # # => "sysid"
- # AttributePerson.inheritance_column = 'address'
- # AttributePerson.inheritance_column
- # # => 'address_id'
- def define_attr_method(name, value=nil, &block)
+ def define_attr_method(name, value=nil, deprecation_warning = true, &block) #:nodoc:
+ # This deprecation_warning param is for internal use so that we can silence
+ # the warning from Active Record, because we are implementing more specific
+ # messages there instead.
+ #
+ # It doesn't apply to the original_#{name} method as we want to warn if
+ # people are calling that regardless.
+ if deprecation_warning
+ ActiveSupport::Deprecation.warn("define_attr_method is deprecated and will be removed without replacement.")
+ end
+
sing = singleton_class
sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
- if method_defined?('original_#{name}')
- undef :'original_#{name}'
+ remove_possible_method :'original_#{name}'
+ remove_possible_method :'_original_#{name}'
+ alias_method :'_original_#{name}', :'#{name}'
+ define_method :'original_#{name}' do
+ ActiveSupport::Deprecation.warn(
+ "This method is generated by ActiveModel::AttributeMethods::ClassMethods#define_attr_method, " \
+ "which is deprecated and will be removed."
+ )
+ send(:'_original_#{name}')
end
- alias_method :'original_#{name}', :'#{name}'
eorb
if block_given?
sing.send :define_method, name, &block
@@ -111,7 +96,7 @@ module ActiveModel
# If we can compile the method name, do it. Otherwise use define_method.
# This is an important *optimization*, please don't change it. define_method
# has slower dispatch and consumes more memory.
- if name =~ COMPILABLE_REGEXP
+ if name =~ NAME_COMPILABLE_REGEXP
sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
RUBY
@@ -239,18 +224,7 @@ module ActiveModel
attribute_method_matchers.each do |matcher|
matcher_new = matcher.method_name(new_name).to_s
matcher_old = matcher.method_name(old_name).to_s
-
- if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{matcher_new}(*args)
- send(:#{matcher_old}, *args)
- end
- RUBY
- else
- define_method(matcher_new) do |*args|
- send(matcher_old, *args)
- end
- end
+ define_optimized_call self, matcher_new, matcher_old
end
end
@@ -284,33 +258,15 @@ module ActiveModel
def define_attribute_method(attr_name)
attribute_method_matchers.each do |matcher|
- unless instance_method_already_implemented?(matcher.method_name(attr_name))
- generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}"
+ method_name = matcher.method_name(attr_name)
+
+ unless instance_method_already_implemented?(method_name)
+ generate_method = "define_method_#{matcher.method_missing_target}"
if respond_to?(generate_method)
send(generate_method, attr_name)
else
- method_name = matcher.method_name(attr_name)
-
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- if method_defined?('#{method_name}')
- undef :'#{method_name}'
- end
- RUBY
-
- if method_name.to_s =~ COMPILABLE_REGEXP
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method_name}(*args)
- send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
- end
- RUBY
- else
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- define_method('#{method_name}') do |*args|
- send('#{matcher.method_missing_target}', '#{attr_name}', *args)
- end
- RUBY
- end
+ define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
end
end
end
@@ -336,7 +292,7 @@ module ActiveModel
protected
def instance_method_already_implemented?(method_name)
- method_defined?(method_name)
+ generated_attribute_methods.method_defined?(method_name)
end
private
@@ -349,27 +305,65 @@ module ActiveModel
# used to alleviate the GC, which ultimately also speeds up the app
# significantly (in our case our test suite finishes 10% faster with
# this cache).
- def attribute_method_matchers_cache
+ def attribute_method_matchers_cache #:nodoc:
@attribute_method_matchers_cache ||= {}
end
- def attribute_method_matcher(method_name)
+ def attribute_method_matcher(method_name) #:nodoc:
if attribute_method_matchers_cache.key?(method_name)
attribute_method_matchers_cache[method_name]
else
+ # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
+ # will match every time.
+ matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
match = nil
- attribute_method_matchers.detect { |method| match = method.match(method_name) }
+ matchers.detect { |method| match = method.match(method_name) }
attribute_method_matchers_cache[method_name] = match
end
end
+ # Define a method `name` in `mod` that dispatches to `send`
+ # using the given `extra` args. This fallbacks `define_method`
+ # and `send` if the given names cannot be compiled.
+ def define_optimized_call(mod, name, send, *extra) #:nodoc:
+ if name =~ NAME_COMPILABLE_REGEXP
+ defn = "def #{name}(*args)"
+ else
+ defn = "define_method(:'#{name}') do |*args|"
+ end
+
+ extra = (extra.map(&:inspect) << "*args").join(", ")
+
+ if send =~ CALL_COMPILABLE_REGEXP
+ target = "#{send}(#{extra})"
+ else
+ target = "send(:'#{send}', #{extra})"
+ end
+
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ #{defn}
+ #{target}
+ end
+ RUBY
+ end
+
class AttributeMethodMatcher
attr_reader :prefix, :suffix, :method_missing_target
- AttributeMethodMatch = Struct.new(:target, :attr_name)
+ AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
def initialize(options = {})
options.symbolize_keys!
+
+ if options[:prefix] == '' || options[:suffix] == ''
+ ActiveSupport::Deprecation.warn(
+ "Specifying an empty prefix/suffix for an attribute method is no longer " \
+ "necessary. If the un-prefixed/suffixed version of the method has not been " \
+ "defined when `define_attribute_methods` is called, it will be defined " \
+ "automatically."
+ )
+ end
+
@prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
@regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@@ -378,7 +372,7 @@ module ActiveModel
def match(method_name)
if @regex =~ method_name
- AttributeMethodMatch.new(method_missing_target, $2)
+ AttributeMethodMatch.new(method_missing_target, $2, method_name)
else
nil
end
@@ -387,6 +381,10 @@ module ActiveModel
def method_name(attr_name)
@method_name % attr_name
end
+
+ def plain?
+ prefix.empty? && suffix.empty?
+ end
end
end
@@ -401,13 +399,21 @@ module ActiveModel
# It's also possible to instantiate related objects, so a Client class
# belonging to the clients table with a +master_id+ foreign key can
# instantiate master through Client#master.
- def method_missing(method_id, *args, &block)
- method_name = method_id.to_s
- if match = match_attribute_method?(method_name)
- guard_private_attribute_method!(method_name, args)
- return __send__(match.target, match.attr_name, *args, &block)
+ def method_missing(method, *args, &block)
+ if respond_to_without_attributes?(method, true)
+ super
+ else
+ match = match_attribute_method?(method.to_s)
+ match ? attribute_missing(match, *args, &block) : super
end
- super
+ end
+
+ # attribute_missing is like method_missing, but for attributes. When method_missing is
+ # called we check to see if there is a matching attribute method. If so, we call
+ # attribute_missing to dispatch the attribute. This method can be overloaded to
+ # customise the behaviour.
+ def attribute_missing(match, *args, &block)
+ __send__(match.target, match.attr_name, *args, &block)
end
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -416,15 +422,14 @@ module ActiveModel
alias :respond_to_without_attributes? :respond_to?
def respond_to?(method, include_private_methods = false)
if super
- return true
+ true
elsif !include_private_methods && super(method, true)
# If we're here then we haven't found among non-private methods
# but found among all methods. Which means that the given method is private.
- return false
- elsif match_attribute_method?(method.to_s)
- return true
+ false
+ else
+ !match_attribute_method?(method.to_s).nil?
end
- super
end
protected
@@ -440,13 +445,6 @@ module ActiveModel
match && attribute_method?(match.attr_name) ? match : nil
end
- # prevent method_missing from calling private methods with #send
- def guard_private_attribute_method!(method_name, args)
- if self.class.private_method_defined?(method_name)
- raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args)
- end
- end
-
def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 37d0c9a0b9..15103f1185 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -41,7 +41,7 @@ module ActiveModel
# You can choose not to have all three callbacks by passing a hash to the
# define_model_callbacks method.
#
- # define_model_callbacks :create, :only => :after, :before
+ # define_model_callbacks :create, :only => [:after, :before]
#
# Would only create the after_create and before_create callback methods in your
# class.
@@ -93,7 +93,7 @@ module ActiveModel
:only => [:before, :around, :after]
}.merge(options)
- types = Array.wrap(options.delete(:only))
+ types = Array.wrap(options.delete(:only))
callbacks.each do |callback|
define_callbacks(callback, options)
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 166cccf161..026f077ee7 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -98,7 +98,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed? # => true
def changed?
- !changed_attributes.empty?
+ changed_attributes.any?
end
# List of attributes with unsaved changes.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 7828434927..8337b04c0d 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -180,6 +180,7 @@ module ActiveModel
all? { |k, v| v && v.empty? }
end
alias_method :blank?, :empty?
+
# Returns an xml formatted representation of the Errors hash.
#
# p.errors.add(:name, "can't be blank")
@@ -204,21 +205,14 @@ module ActiveModel
messages.dup
end
- # Adds +message+ to the error messages on +attribute+, which will be returned on a call to
- # <tt>on(attribute)</tt> for the same attribute. More than one error can be added to the same
- # +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
+ # Adds +message+ to the error messages on +attribute+. More than one error can be added to the same
+ # +attribute+.
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
# If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
def add(attribute, message = nil, options = {})
- message ||= :invalid
-
- if message.is_a?(Symbol)
- message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- elsif message.is_a?(Proc)
- message = message.call
- end
+ message = normalize_message(attribute, message, options)
if options[:strict]
raise ActiveModel::StrictValidationFailed, message
end
@@ -243,6 +237,15 @@ module ActiveModel
end
end
+ # Returns true if an error on the attribute with the given message is present, false otherwise.
+ # +message+ is treated the same as for +add+.
+ # p.errors.add :name, :blank
+ # p.errors.added? :name, :blank # => true
+ def added?(attribute, message = nil, options = {})
+ message = normalize_message(attribute, message, options)
+ self[attribute].include? message
+ end
+
# Returns all the full error messages in an array.
#
# class Company
@@ -254,20 +257,22 @@ module ActiveModel
# company.errors.full_messages # =>
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
def full_messages
- map { |attribute, message|
- if attribute == :base
- message
- else
- attr_name = attribute.to_s.gsub('.', '_').humanize
- attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
-
- I18n.t(:"errors.format", {
- :default => "%{attribute} %{message}",
- :attribute => attr_name,
- :message => message
- })
- end
- }
+ map { |attribute, message| full_message(attribute, message) }
+ end
+
+ # Returns a full message for a given attribute.
+ #
+ # company.errors.full_message(:name, "is invalid") # =>
+ # "Name is invalid"
+ def full_message(attribute, message)
+ return message if attribute == :base
+ attr_name = attribute.to_s.gsub('.', '_').humanize
+ attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
+ I18n.t(:"errors.format", {
+ :default => "%{attribute} %{message}",
+ :attribute => attr_name,
+ :message => message
+ })
end
# Translates an error message in its default scope
@@ -297,13 +302,17 @@ module ActiveModel
def generate_message(attribute, type = :invalid, options = {})
type = options.delete(:message) if options[:message].is_a?(Symbol)
- defaults = @base.class.lookup_ancestors.map do |klass|
- [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
- :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
+ if @base.class.respond_to?(:i18n_scope)
+ defaults = @base.class.lookup_ancestors.map do |klass|
+ [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
+ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
+ end
+ else
+ defaults = []
end
defaults << options.delete(:message)
- defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}"
+ defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
defaults << :"errors.attributes.#{attribute}.#{type}"
defaults << :"errors.messages.#{type}"
@@ -322,6 +331,19 @@ module ActiveModel
I18n.translate(key, options)
end
+
+ private
+ def normalize_message(attribute, message, options)
+ message ||= :invalid
+
+ if message.is_a?(Symbol)
+ generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
+ elsif message.is_a?(Proc)
+ message.call
+ else
+ message
+ end
+ end
end
class StrictValidationFailed < StandardError
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index f16459ede2..953d24a3b2 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -12,19 +12,22 @@ module ActiveModel
def initialize(klass, namespace = nil, name = nil)
name ||= klass.name
+
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank?
+
super(name)
- @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace
- @klass = klass
- @singular = _singularize(self).freeze
- @plural = ActiveSupport::Inflector.pluralize(@singular).freeze
- @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
- @human = ActiveSupport::Inflector.humanize(@element).freeze
- @collection = ActiveSupport::Inflector.tableize(self).freeze
+ @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace
+ @klass = klass
+ @singular = _singularize(self).freeze
+ @plural = ActiveSupport::Inflector.pluralize(@singular).freeze
+ @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
+ @human = ActiveSupport::Inflector.humanize(@element).freeze
+ @collection = ActiveSupport::Inflector.tableize(self).freeze
@partial_path = "#{@collection}/#{@element}".freeze
- @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
- @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
- @i18n_key = self.underscore.to_sym
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
+ @i18n_key = self.underscore.to_sym
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -79,7 +82,9 @@ module ActiveModel
# used to retrieve all kinds of naming-related information.
def model_name
@_model_name ||= begin
- namespace = self.parents.detect { |n| n.respond_to?(:_railtie) }
+ namespace = self.parents.detect do |n|
+ n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
+ end
ActiveModel::Name.new(self, namespace)
end
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 7a910d18e7..cd8eb357de 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -187,8 +187,7 @@ module ActiveModel
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- remove_possible_method(:observed_classes)
- define_method(:observed_classes) { models }
+ redefine_method(:observed_classes) { models }
end
# Returns an array of Classes to observe.
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 7a109d9a52..db78864c67 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -32,8 +32,8 @@ module ActiveModel
# User.find_by_name("david").try(:authenticate, "notright") # => nil
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
def has_secure_password
- # Load bcrypt-ruby only when has_secured_password is used to avoid make ActiveModel
- # (and by extension the entire framework) dependent on a binary library.
+ # Load bcrypt-ruby only when has_secure_password is used.
+ # This is to avoid ActiveModel (and by extension the entire framework) being dependent on a binary library.
gem 'bcrypt-ruby', '~> 3.0.0'
require 'bcrypt'
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index b9f6f6cbbf..a4b58ab456 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -78,8 +78,11 @@ module ActiveModel
attribute_names -= Array.wrap(except).map(&:to_s)
end
+ hash = {}
+ attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
+
method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) }
- hash = Hash[(attribute_names + method_names).map { |n| [n, send(n)] }]
+ method_names.each { |n| hash[n] = send(n) }
serializable_add_includes(options) do |association, records, opts|
hash[association] = if records.is_a?(Enumerable)
@@ -93,17 +96,37 @@ module ActiveModel
end
private
+
+ # Hook method defining how an attribute value should be retrieved for
+ # serialization. By default this is assumed to be an instance named after
+ # the attribute. Override this method in subclasses should you need to
+ # retrieve the value for a given attribute differently:
+ #
+ # class MyClass
+ # include ActiveModel::Validations
+ #
+ # def initialize(data = {})
+ # @data = data
+ # end
+ #
+ # def read_attribute_for_serialization(key)
+ # @data[key]
+ # end
+ # end
+ #
+ alias :read_attribute_for_serialization :send
+
# Add associations specified via the <tt>:include</tt> option.
#
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
- def serializable_add_includes(options = {})
+ def serializable_add_includes(options = {}) #:nodoc:
return unless include = options[:include]
unless include.is_a?(Hash)
- include = Hash[Array.wrap(include).map { |n| [n, {}] }]
+ include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
end
include.each do |association, opts|
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 4fbccd7419..c845440120 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -15,7 +15,7 @@ module ActiveModel
self.include_root_in_json = true
end
- # Returns a JSON string representing the model. Some configuration can be
+ # Returns a hash representing the model. Some configuration can be
# passed through +options+.
#
# The option <tt>include_root_in_json</tt> controls the top-level behavior
@@ -42,7 +42,7 @@ module ActiveModel
# The remainder of the examples in this section assume include_root_in_json is set to
# <tt>false</tt>.
#
- # Without any +options+, the returned JSON string will include all the model's
+ # Without any +options+, the returned Hash will include all the model's
# attributes. For example:
#
# user = User.find(1)
@@ -86,21 +86,15 @@ module ActiveModel
# "title": "Welcome to the weblog"},
# {"comments": [{"body": "Don't think too hard"}],
# "title": "So I was thinking"}]}
-
def as_json(options = nil)
- hash = serializable_hash(options)
-
- include_root = include_root_in_json
- if options.try(:key?, :root)
- include_root = options[:root]
+ root = include_root_in_json
+ root = options[:root] if options.try(:key?, :root)
+ if root
+ root = self.class.model_name.element if root == true
+ { root => serializable_hash(options) }
+ else
+ serializable_hash(options)
end
-
- if include_root
- custom_root = options && options[:root]
- hash = { custom_root || self.class.model_name.element => hash }
- end
-
- hash
end
def from_json(json, include_root=include_root_in_json)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 64dda3bcee..d61d9d7119 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -15,10 +15,10 @@ module ActiveModel
class Attribute #:nodoc:
attr_reader :name, :value, :type
- def initialize(name, serializable, raw_value=nil)
+ def initialize(name, serializable, value)
@name, @serializable = name, serializable
- raw_value = raw_value.in_time_zone if raw_value.respond_to?(:in_time_zone)
- @value = raw_value || @serializable.send(name)
+ value = value.in_time_zone if value.respond_to?(:in_time_zone)
+ @value = value
@type = compute_type
end
@@ -49,40 +49,24 @@ module ActiveModel
def initialize(serializable, options = nil)
@serializable = serializable
@options = options ? options.dup : {}
-
- @options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
- @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
end
- # To replicate the behavior in ActiveRecord#attributes, <tt>:except</tt>
- # takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
- # for a N level model but is set for the N+1 level models,
- # then because <tt>:except</tt> is set to a default value, the second
- # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
- # <tt>:only</tt> is set, always delete <tt>:except</tt>.
- def attributes_hash
- attributes = @serializable.attributes
- if options[:only].any?
- attributes.slice(*options[:only])
- elsif options[:except].any?
- attributes.except(*options[:except])
- else
- attributes
- end
+ def serializable_hash
+ @serializable.serializable_hash(@options.except(:include))
end
- def serializable_attributes
- attributes_hash.map do |name, value|
- self.class::Attribute.new(name, @serializable, value)
+ def serializable_collection
+ methods = Array.wrap(options[:methods]).map(&:to_s)
+ serializable_hash.map do |name, value|
+ name = name.to_s
+ if methods.include?(name)
+ self.class::MethodAttribute.new(name, @serializable, value)
+ else
+ self.class::Attribute.new(name, @serializable, value)
+ end
end
end
- def serializable_methods
- Array.wrap(options[:methods]).map do |name|
- self.class::MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s)
- end.compact
- end
-
def serialize
require 'builder' unless defined? ::Builder
@@ -114,7 +98,7 @@ module ActiveModel
end
def add_attributes_and_methods
- (serializable_attributes + serializable_methods).each do |attribute|
+ serializable_collection.each do |attribute|
key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
ActiveSupport::XmlMini.to_tag(key, attribute.value,
options.merge(attribute.decorations))
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index eb7aac709d..6bc928bab7 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -23,7 +23,7 @@ module ActiveModel
keys = CHECKS.keys & options.keys
if keys.empty?
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
end
keys.each do |key|
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 35af7152db..9a643a6f5c 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -25,14 +25,14 @@ module ActiveModel
# This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
#
# Configuration options:
- # * <tt>message</tt> - A custom error message (default is: "can't be blank").
+ # * <tt>:message</tt> - A custom error message (default is: "can't be blank").
# * <tt>:on</tt> - Specifies when this validation is active. Runs in all
# validation contexts by default (+nil+), other options are <tt>:create</tt>
# and <tt>:update</tt>.
- # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
- # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # * <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.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index b85c2453fb..8e09f6ac35 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -27,7 +27,7 @@ module ActiveModel
#
# class EmailValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << (options[:message] || "is not an email") unless
+ # record.errors.add attribute, (options[:message] || "is not an email") unless
# value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
# end
# end
@@ -48,7 +48,7 @@ module ActiveModel
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << "must start with 'the'" unless value =~ /\Athe/i
+ # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
# end
# end
#
@@ -57,7 +57,7 @@ module ActiveModel
#
# Additionally validator classes may be in another namespace and still used within any class.
#
- # validates :name, :'file/title' => true
+ # validates :name, :'film/title' => true
#
# The validators hash can also handle regular expressions, ranges,
# arrays and strings in shortcut form, e.g.
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 83aae206a6..72b8562b93 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -32,7 +32,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
- # record.errors[:base] << "This record is invalid"
+ # record.errors.add :base, "This record is invalid"
# end
# end
#
@@ -56,7 +56,7 @@ module ActiveModel
# if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
# or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
- # * <tt>unless</tt> - Specifies a method, proc or string to call to
+ # * <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>).
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 5304743389..35ec98c822 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -48,8 +48,8 @@ module ActiveModel #:nodoc:
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
- # record.errors[:base] << "This is some custom error message"
- # record.errors[:first_name] << "This is some complex validation"
+ # record.errors.add :base, "This is some custom error message"
+ # record.errors.add :first_name, "This is some complex validation"
# # etc...
# end
# end
@@ -57,7 +57,7 @@ module ActiveModel #:nodoc:
# To add behavior to the initialize method, use the following signature:
#
# class MyValidator < ActiveModel::Validator
- # def initialize(record, options)
+ # def initialize(options)
# super
# @my_custom_field = options[:field_name] || :first_name
# end
@@ -68,7 +68,7 @@ module ActiveModel #:nodoc:
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.'])
+ # record.errors.add attribute, 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.'])
# end
# end
#