Rails 2.3.5
Upgrade to Rails 2.3.5. Also work around this bug: https://rails.lighthouseapp.com/projects/8994/tickets/3524 created by the aforementioned Rails release.
This commit is contained in:
parent
a6429f8c22
commit
e3832c6f79
187 changed files with 2316 additions and 891 deletions
|
@ -275,9 +275,10 @@ module ActiveRecord
|
|||
# You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
|
||||
# aware of, mostly involving the saving of associated objects.
|
||||
#
|
||||
# Unless you enable the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
|
||||
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association,
|
||||
# in which case the members are always saved.
|
||||
# Unless you set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
|
||||
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
|
||||
# to +true+ will _always_ save the members, whereas setting it to +false+ will
|
||||
# _never_ save the members.
|
||||
#
|
||||
# === One-to-one associations
|
||||
#
|
||||
|
@ -874,7 +875,9 @@ module ActiveRecord
|
|||
# if the real class name is Person, you'll have to specify it with this option.
|
||||
# [:conditions]
|
||||
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
||||
# SQL fragment, such as <tt>rank = 5</tt>.
|
||||
# SQL fragment, such as <tt>rank = 5</tt>. Record creation from the association is scoped if a hash
|
||||
# is used. <tt>has_one :account, :conditions => {:enabled => true}</tt> will create an enabled account with <tt>@company.create_account</tt>
|
||||
# or <tt>@company.build_account</tt>.
|
||||
# [:order]
|
||||
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
|
||||
# such as <tt>last_name, first_name DESC</tt>.
|
||||
|
@ -1324,8 +1327,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
||||
ids = (new_value || []).reject { |nid| nid.blank? }
|
||||
send("#{reflection.name}=", reflection.klass.find(ids))
|
||||
ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i)
|
||||
send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1408,7 +1411,7 @@ module ActiveRecord
|
|||
if reflection.options.include?(:dependent)
|
||||
# Add polymorphic type if the :as option is present
|
||||
dependent_conditions = []
|
||||
dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}"
|
||||
dependent_conditions << "#{reflection.primary_key_name} = \#{record.#{reflection.name}.send(:owner_quoted_id)}"
|
||||
dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
|
||||
dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions]
|
||||
dependent_conditions << extra_conditions if extra_conditions
|
||||
|
|
|
@ -210,15 +210,14 @@ module ActiveRecord
|
|||
# Forwards any missing method call to the \target.
|
||||
def method_missing(method, *args)
|
||||
if load_target
|
||||
unless @target.respond_to?(method)
|
||||
message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
|
||||
raise NoMethodError, message
|
||||
end
|
||||
|
||||
if block_given?
|
||||
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
||||
if @target.respond_to?(method)
|
||||
if block_given?
|
||||
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
||||
else
|
||||
@target.send(method, *args)
|
||||
end
|
||||
else
|
||||
@target.send(method, *args)
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,8 +24,8 @@ module ActiveRecord
|
|||
|
||||
def has_primary_key?
|
||||
return @has_primary_key unless @has_primary_key.nil?
|
||||
@has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? &&
|
||||
ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]))
|
||||
@has_primary_key = (@owner.connection.supports_primary_key? &&
|
||||
@owner.connection.primary_key(@reflection.options[:join_table]))
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
@ -8,18 +8,21 @@ module ActiveRecord
|
|||
|
||||
def create(attrs = {}, replace_existing = true)
|
||||
new_record(replace_existing) do |reflection|
|
||||
attrs = merge_with_conditions(attrs)
|
||||
reflection.create_association(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def create!(attrs = {}, replace_existing = true)
|
||||
new_record(replace_existing) do |reflection|
|
||||
attrs = merge_with_conditions(attrs)
|
||||
reflection.create_association!(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def build(attrs = {}, replace_existing = true)
|
||||
new_record(replace_existing) do |reflection|
|
||||
attrs = merge_with_conditions(attrs)
|
||||
reflection.build_association(attrs)
|
||||
end
|
||||
end
|
||||
|
@ -119,6 +122,12 @@ module ActiveRecord
|
|||
|
||||
record
|
||||
end
|
||||
|
||||
def merge_with_conditions(attrs={})
|
||||
attrs ||= {}
|
||||
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
||||
attrs
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -159,7 +159,7 @@ module ActiveRecord
|
|||
def add_autosave_association_callbacks(reflection)
|
||||
save_method = "autosave_associated_records_for_#{reflection.name}"
|
||||
validation_method = "validate_associated_records_for_#{reflection.name}"
|
||||
validate validation_method
|
||||
force_validation = (reflection.options[:validate] == true || reflection.options[:autosave] == true)
|
||||
|
||||
case reflection.macro
|
||||
when :has_many, :has_and_belongs_to_many
|
||||
|
@ -170,7 +170,10 @@ module ActiveRecord
|
|||
after_create save_method
|
||||
after_update save_method
|
||||
|
||||
define_method(validation_method) { validate_collection_association(reflection) }
|
||||
if force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false)
|
||||
define_method(validation_method) { validate_collection_association(reflection) }
|
||||
validate validation_method
|
||||
end
|
||||
else
|
||||
case reflection.macro
|
||||
when :has_one
|
||||
|
@ -180,7 +183,11 @@ module ActiveRecord
|
|||
define_method(save_method) { save_belongs_to_association(reflection) }
|
||||
before_save save_method
|
||||
end
|
||||
define_method(validation_method) { validate_single_association(reflection) }
|
||||
|
||||
if force_validation
|
||||
define_method(validation_method) { validate_single_association(reflection) }
|
||||
validate validation_method
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -224,10 +231,8 @@ module ActiveRecord
|
|||
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
||||
# turned on for the association specified by +reflection+.
|
||||
def validate_single_association(reflection)
|
||||
if reflection.options[:validate] == true || reflection.options[:autosave] == true
|
||||
if (association = association_instance_get(reflection.name)) && !association.target.nil?
|
||||
association_valid?(reflection, association)
|
||||
end
|
||||
if (association = association_instance_get(reflection.name)) && !association.target.nil?
|
||||
association_valid?(reflection, association)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -235,7 +240,7 @@ module ActiveRecord
|
|||
# <tt>:autosave</tt> is turned on for the association specified by
|
||||
# +reflection+.
|
||||
def validate_collection_association(reflection)
|
||||
if reflection.options[:validate] != false && association = association_instance_get(reflection.name)
|
||||
if association = association_instance_get(reflection.name)
|
||||
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
||||
records.each { |record| association_valid?(reflection, record) }
|
||||
end
|
||||
|
@ -244,16 +249,15 @@ module ActiveRecord
|
|||
|
||||
# Returns whether or not the association is valid and applies any errors to
|
||||
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
||||
# enabled records if they're marked_for_destruction?.
|
||||
# enabled records if they're marked_for_destruction? or destroyed.
|
||||
def association_valid?(reflection, association)
|
||||
return true if association.destroyed? || association.marked_for_destruction?
|
||||
|
||||
unless valid = association.valid?
|
||||
if reflection.options[:autosave]
|
||||
unless association.marked_for_destruction?
|
||||
association.errors.each_error do |attribute, error|
|
||||
error = error.dup
|
||||
error.attribute = "#{reflection.name}_#{attribute}"
|
||||
errors.add(error) unless errors.on(error.attribute)
|
||||
end
|
||||
association.errors.each_error do |attribute, error|
|
||||
attribute = "#{reflection.name}.#{attribute}"
|
||||
errors.add(attribute, error.dup) unless errors.on(attribute)
|
||||
end
|
||||
else
|
||||
errors.add(reflection.name)
|
||||
|
@ -283,9 +287,11 @@ module ActiveRecord
|
|||
|
||||
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
||||
records.each do |record|
|
||||
next if record.destroyed?
|
||||
|
||||
if autosave && record.marked_for_destruction?
|
||||
association.destroy(record)
|
||||
elsif @new_record_before_save || record.new_record?
|
||||
elsif autosave != false && (@new_record_before_save || record.new_record?)
|
||||
if autosave
|
||||
association.send(:insert_record, record, false, false)
|
||||
else
|
||||
|
@ -311,14 +317,17 @@ module ActiveRecord
|
|||
# This all happens inside a transaction, _if_ the Transactions module is included into
|
||||
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
||||
def save_has_one_association(reflection)
|
||||
if (association = association_instance_get(reflection.name)) && !association.target.nil?
|
||||
if (association = association_instance_get(reflection.name)) && !association.target.nil? && !association.destroyed?
|
||||
autosave = reflection.options[:autosave]
|
||||
|
||||
if autosave && association.marked_for_destruction?
|
||||
association.destroy
|
||||
elsif new_record? || association.new_record? || association[reflection.primary_key_name] != id || autosave
|
||||
association[reflection.primary_key_name] = id
|
||||
association.save(!autosave)
|
||||
else
|
||||
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
||||
if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
|
||||
association[reflection.primary_key_name] = key
|
||||
association.save(!autosave)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -332,12 +341,12 @@ module ActiveRecord
|
|||
# This all happens inside a transaction, _if_ the Transactions module is included into
|
||||
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
||||
def save_belongs_to_association(reflection)
|
||||
if association = association_instance_get(reflection.name)
|
||||
if (association = association_instance_get(reflection.name)) && !association.destroyed?
|
||||
autosave = reflection.options[:autosave]
|
||||
|
||||
if autosave && association.marked_for_destruction?
|
||||
association.destroy
|
||||
else
|
||||
elsif autosave != false
|
||||
association.save(!autosave) if association.new_record? || autosave
|
||||
|
||||
if association.updated?
|
||||
|
@ -352,4 +361,4 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2567,6 +2567,7 @@ module ActiveRecord #:nodoc:
|
|||
# options, use <tt>#destroy</tt>.
|
||||
def delete
|
||||
self.class.delete(id) unless new_record?
|
||||
@destroyed = true
|
||||
freeze
|
||||
end
|
||||
|
||||
|
@ -2581,6 +2582,7 @@ module ActiveRecord #:nodoc:
|
|||
)
|
||||
end
|
||||
|
||||
@destroyed = true
|
||||
freeze
|
||||
end
|
||||
|
||||
|
@ -2840,6 +2842,11 @@ module ActiveRecord #:nodoc:
|
|||
@attributes.frozen?
|
||||
end
|
||||
|
||||
# Returns +true+ if the record has been destroyed.
|
||||
def destroyed?
|
||||
@destroyed
|
||||
end
|
||||
|
||||
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
||||
# attributes will be marked as read only since they cannot be saved.
|
||||
def readonly?
|
||||
|
|
|
@ -7,7 +7,8 @@ module MysqlCompat #:nodoc:
|
|||
raise 'Mysql not loaded' unless defined?(::Mysql)
|
||||
|
||||
target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
|
||||
return if target.instance_methods.include?('all_hashes')
|
||||
return if target.instance_methods.include?('all_hashes') ||
|
||||
target.instance_methods.include?(:all_hashes)
|
||||
|
||||
# Ruby driver has a version string and returns null values in each_hash
|
||||
# C driver >= 2.7 returns null values in each_hash
|
||||
|
@ -63,12 +64,15 @@ module ActiveRecord
|
|||
raise
|
||||
end
|
||||
end
|
||||
|
||||
MysqlCompat.define_all_hashes_method!
|
||||
|
||||
mysql = Mysql.init
|
||||
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
|
||||
|
||||
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
|
||||
default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
|
||||
options = [host, username, password, database, port, socket, default_flags]
|
||||
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -434,7 +434,7 @@ end
|
|||
# Any fixture labeled "DEFAULTS" is safely ignored.
|
||||
|
||||
class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
||||
MAX_ID = 2 ** 31 - 1
|
||||
MAX_ID = 2 ** 30 - 1
|
||||
DEFAULT_FILTER_RE = /\.ya?ml$/
|
||||
|
||||
@@all_cached_fixtures = {}
|
||||
|
|
|
@ -23,16 +23,6 @@ module ActiveRecord
|
|||
# p2.first_name = "should fail"
|
||||
# p2.save # Raises a ActiveRecord::StaleObjectError
|
||||
#
|
||||
# Optimistic locking will also check for stale data when objects are destroyed. Example:
|
||||
#
|
||||
# p1 = Person.find(1)
|
||||
# p2 = Person.find(1)
|
||||
#
|
||||
# p1.first_name = "Michael"
|
||||
# p1.save
|
||||
#
|
||||
# p2.destroy # Raises a ActiveRecord::StaleObjectError
|
||||
#
|
||||
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
||||
# or otherwise apply the business logic needed to resolve the conflict.
|
||||
#
|
||||
|
@ -49,7 +39,6 @@ module ActiveRecord
|
|||
base.lock_optimistically = true
|
||||
|
||||
base.alias_method_chain :update, :lock
|
||||
base.alias_method_chain :destroy, :lock
|
||||
base.alias_method_chain :attributes_from_column_definition, :lock
|
||||
|
||||
class << base
|
||||
|
@ -109,28 +98,6 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def destroy_with_lock #:nodoc:
|
||||
return destroy_without_lock unless locking_enabled?
|
||||
|
||||
unless new_record?
|
||||
lock_col = self.class.locking_column
|
||||
previous_value = send(lock_col).to_i
|
||||
|
||||
affected_rows = connection.delete(
|
||||
"DELETE FROM #{self.class.quoted_table_name} " +
|
||||
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " +
|
||||
"AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}",
|
||||
"#{self.class.name} Destroy"
|
||||
)
|
||||
|
||||
unless affected_rows == 1
|
||||
raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object"
|
||||
end
|
||||
end
|
||||
|
||||
freeze
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
DEFAULT_LOCKING_COLUMN = 'lock_version'
|
||||
|
||||
|
|
|
@ -1,25 +1,3 @@
|
|||
# Copyright (c) 2006 Shugo Maeda <shugo@ruby-lang.org>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject
|
||||
# to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
module ActiveRecord
|
||||
module Locking
|
||||
# Locking::Pessimistic provides support for row-level locking using
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
module ActiveRecord
|
||||
module NestedAttributes #:nodoc:
|
||||
class TooManyRecords < ActiveRecordError
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false
|
||||
base.reject_new_nested_attributes_procs = {}
|
||||
base.class_inheritable_accessor :nested_attributes_options, :instance_writer => false
|
||||
base.nested_attributes_options = {}
|
||||
end
|
||||
|
||||
# == Nested Attributes
|
||||
|
@ -62,10 +65,10 @@ module ActiveRecord
|
|||
# accepts_nested_attributes_for :avatar, :allow_destroy => true
|
||||
# end
|
||||
#
|
||||
# Now, when you add the <tt>_delete</tt> key to the attributes hash, with a
|
||||
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
|
||||
# value that evaluates to +true+, you will destroy the associated model:
|
||||
#
|
||||
# member.avatar_attributes = { :id => '2', :_delete => '1' }
|
||||
# member.avatar_attributes = { :id => '2', :_destroy => '1' }
|
||||
# member.avatar.marked_for_destruction? # => true
|
||||
# member.save
|
||||
# member.avatar #=> nil
|
||||
|
@ -85,14 +88,14 @@ module ActiveRecord
|
|||
# the attribute hash.
|
||||
#
|
||||
# For each hash that does _not_ have an <tt>id</tt> key a new record will
|
||||
# be instantiated, unless the hash also contains a <tt>_delete</tt> key
|
||||
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
|
||||
# that evaluates to +true+.
|
||||
#
|
||||
# params = { :member => {
|
||||
# :name => 'joe', :posts_attributes => [
|
||||
# { :title => 'Kari, the awesome Ruby documentation browser!' },
|
||||
# { :title => 'The egalitarian assumption of the modern citizen' },
|
||||
# { :title => '', :_delete => '1' } # this will be ignored
|
||||
# { :title => '', :_destroy => '1' } # this will be ignored
|
||||
# ]
|
||||
# }}
|
||||
#
|
||||
|
@ -123,6 +126,22 @@ module ActiveRecord
|
|||
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
|
||||
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
|
||||
#
|
||||
# Alternatively, :reject_if also accepts a symbol for using methods:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_many :posts
|
||||
# accepts_nested_attributes_for :posts, :reject_if => :new_record?
|
||||
# end
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_many :posts
|
||||
# accepts_nested_attributes_for :posts, :reject_if => :reject_posts
|
||||
#
|
||||
# def reject_posts(attributed)
|
||||
# attributed['title].blank?
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If the hash contains an <tt>id</tt> key that matches an already
|
||||
# associated record, the matching record will be modified:
|
||||
#
|
||||
|
@ -140,7 +159,7 @@ module ActiveRecord
|
|||
# By default the associated records are protected from being destroyed. If
|
||||
# you want to destroy any of the associated records through the attributes
|
||||
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
|
||||
# option. This will allow you to also use the <tt>_delete</tt> key to
|
||||
# option. This will allow you to also use the <tt>_destroy</tt> key to
|
||||
# destroy existing records:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
|
@ -149,7 +168,7 @@ module ActiveRecord
|
|||
# end
|
||||
#
|
||||
# params = { :member => {
|
||||
# :posts_attributes => [{ :id => '2', :_delete => '1' }]
|
||||
# :posts_attributes => [{ :id => '2', :_destroy => '1' }]
|
||||
# }}
|
||||
#
|
||||
# member.attributes = params['member']
|
||||
|
@ -172,14 +191,23 @@ module ActiveRecord
|
|||
# Supported options:
|
||||
# [:allow_destroy]
|
||||
# If true, destroys any members from the attributes hash with a
|
||||
# <tt>_delete</tt> key and a value that evaluates to +true+
|
||||
# <tt>_destroy</tt> key and a value that evaluates to +true+
|
||||
# (eg. 1, '1', true, or 'true'). This option is off by default.
|
||||
# [:reject_if]
|
||||
# Allows you to specify a Proc that checks whether a record should be
|
||||
# built for a certain attribute hash. The hash is passed to the Proc
|
||||
# and the Proc should return either +true+ or +false+. When no Proc
|
||||
# is specified a record will be built for all attribute hashes that
|
||||
# do not have a <tt>_delete</tt> that evaluates to true.
|
||||
# Allows you to specify a Proc or a Symbol pointing to a method
|
||||
# that checks whether a record should be built for a certain attribute
|
||||
# hash. The hash is passed to the supplied Proc or the method
|
||||
# and it should return either +true+ or +false+. When no :reject_if
|
||||
# is specified, a record will be built for all attribute hashes that
|
||||
# do not have a <tt>_destroy</tt> value that evaluates to true.
|
||||
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
|
||||
# that will reject a record where all the attributes are blank.
|
||||
# [:limit]
|
||||
# Allows you to specify the maximum number of the associated records that
|
||||
# can be processes with the nested attributes. If the size of the
|
||||
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
|
||||
# exception is raised. If omitted, any number associations can be processed.
|
||||
# Note that the :limit option is only applicable to one-to-many associations.
|
||||
#
|
||||
# Examples:
|
||||
# # creates avatar_attributes=
|
||||
|
@ -189,7 +217,7 @@ module ActiveRecord
|
|||
def accepts_nested_attributes_for(*attr_names)
|
||||
options = { :allow_destroy => false }
|
||||
options.update(attr_names.extract_options!)
|
||||
options.assert_valid_keys(:allow_destroy, :reject_if)
|
||||
options.assert_valid_keys(:allow_destroy, :reject_if, :limit)
|
||||
|
||||
attr_names.each do |association_name|
|
||||
if reflection = reflect_on_association(association_name)
|
||||
|
@ -201,16 +229,18 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
reflection.options[:autosave] = true
|
||||
self.reject_new_nested_attributes_procs[association_name.to_sym] = options[:reject_if]
|
||||
self.nested_attributes_options[association_name.to_sym] = options
|
||||
|
||||
# def pirate_attributes=(attributes)
|
||||
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, false)
|
||||
# end
|
||||
class_eval %{
|
||||
def #{association_name}_attributes=(attributes)
|
||||
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]})
|
||||
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
|
||||
add_autosave_association_callbacks(reflection)
|
||||
else
|
||||
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
||||
end
|
||||
|
@ -223,15 +253,25 @@ module ActiveRecord
|
|||
# destruction of this association.
|
||||
#
|
||||
# See ActionView::Helpers::FormHelper::fields_for for more info.
|
||||
def _delete
|
||||
def _destroy
|
||||
marked_for_destruction?
|
||||
end
|
||||
|
||||
# Deal with deprecated _delete.
|
||||
#
|
||||
def _delete #:nodoc:
|
||||
ActiveSupport::Deprecation.warn "_delete is deprecated in nested attributes. Use _destroy instead."
|
||||
_destroy
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Attribute hash keys that should not be assigned as normal attributes.
|
||||
# These hash keys are nested attributes implementation details.
|
||||
UNASSIGNABLE_KEYS = %w{ id _delete }
|
||||
#
|
||||
# TODO Remove _delete from UNASSIGNABLE_KEYS when deprecation warning are
|
||||
# removed.
|
||||
UNASSIGNABLE_KEYS = %w( id _destroy _delete )
|
||||
|
||||
# Assigns the given attributes to the association.
|
||||
#
|
||||
|
@ -240,17 +280,23 @@ module ActiveRecord
|
|||
# record will be built.
|
||||
#
|
||||
# If the given attributes include a matching <tt>:id</tt> attribute _and_ a
|
||||
# <tt>:_delete</tt> key set to a truthy value, then the existing record
|
||||
# <tt>:_destroy</tt> key set to a truthy value, then the existing record
|
||||
# will be marked for destruction.
|
||||
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
|
||||
attributes = attributes.stringify_keys
|
||||
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
||||
options = self.nested_attributes_options[association_name]
|
||||
attributes = attributes.with_indifferent_access
|
||||
|
||||
if attributes['id'].blank?
|
||||
unless reject_new_record?(association_name, attributes)
|
||||
send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS))
|
||||
method = "build_#{association_name}"
|
||||
if respond_to?(method)
|
||||
send(method, attributes.except(*UNASSIGNABLE_KEYS))
|
||||
else
|
||||
raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
|
||||
end
|
||||
end
|
||||
elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s
|
||||
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
|
||||
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -259,7 +305,7 @@ module ActiveRecord
|
|||
# Hashes with an <tt>:id</tt> value matching an existing associated record
|
||||
# will update that record. Hashes without an <tt>:id</tt> value will build
|
||||
# a new record for the association. Hashes with a matching <tt>:id</tt>
|
||||
# value and a <tt>:_delete</tt> key set to a truthy value will mark the
|
||||
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
|
||||
# matched record for destruction.
|
||||
#
|
||||
# For example:
|
||||
|
@ -267,7 +313,7 @@ module ActiveRecord
|
|||
# assign_nested_attributes_for_collection_association(:people, {
|
||||
# '1' => { :id => '1', :name => 'Peter' },
|
||||
# '2' => { :name => 'John' },
|
||||
# '3' => { :id => '2', :_delete => true }
|
||||
# '3' => { :id => '2', :_destroy => true }
|
||||
# })
|
||||
#
|
||||
# Will update the name of the Person with ID 1, build a new associated
|
||||
|
@ -279,51 +325,68 @@ module ActiveRecord
|
|||
# assign_nested_attributes_for_collection_association(:people, [
|
||||
# { :id => '1', :name => 'Peter' },
|
||||
# { :name => 'John' },
|
||||
# { :id => '2', :_delete => true }
|
||||
# { :id => '2', :_destroy => true }
|
||||
# ])
|
||||
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy)
|
||||
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
||||
options = self.nested_attributes_options[association_name]
|
||||
|
||||
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
||||
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
||||
end
|
||||
|
||||
if options[:limit] && attributes_collection.size > options[:limit]
|
||||
raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead."
|
||||
end
|
||||
|
||||
if attributes_collection.is_a? Hash
|
||||
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
|
||||
end
|
||||
|
||||
attributes_collection.each do |attributes|
|
||||
attributes = attributes.stringify_keys
|
||||
attributes = attributes.with_indifferent_access
|
||||
|
||||
if attributes['id'].blank?
|
||||
unless reject_new_record?(association_name, attributes)
|
||||
send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
|
||||
end
|
||||
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
|
||||
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
|
||||
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Updates a record with the +attributes+ or marks it for destruction if
|
||||
# +allow_destroy+ is +true+ and has_delete_flag? returns +true+.
|
||||
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
|
||||
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
|
||||
if has_delete_flag?(attributes) && allow_destroy
|
||||
if has_destroy_flag?(attributes) && allow_destroy
|
||||
record.mark_for_destruction
|
||||
else
|
||||
record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
|
||||
end
|
||||
end
|
||||
|
||||
# Determines if a hash contains a truthy _delete key.
|
||||
def has_delete_flag?(hash)
|
||||
ConnectionAdapters::Column.value_to_boolean hash['_delete']
|
||||
# Determines if a hash contains a truthy _destroy key.
|
||||
def has_destroy_flag?(hash)
|
||||
ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) ||
|
||||
ConnectionAdapters::Column.value_to_boolean(hash['_delete']) # TODO Remove after deprecation.
|
||||
end
|
||||
|
||||
# Determines if a new record should be build by checking for
|
||||
# has_delete_flag? or if a <tt>:reject_if</tt> proc exists for this
|
||||
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
|
||||
# association and evaluates to +true+.
|
||||
def reject_new_record?(association_name, attributes)
|
||||
has_delete_flag?(attributes) ||
|
||||
self.class.reject_new_nested_attributes_procs[association_name].try(:call, attributes)
|
||||
has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
|
||||
end
|
||||
|
||||
def call_reject_if(association_name, attributes)
|
||||
callback = self.nested_attributes_options[association_name][:reject_if]
|
||||
|
||||
case callback
|
||||
when Symbol
|
||||
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
||||
when Proc
|
||||
callback.try(:call, attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,11 +27,13 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def message
|
||||
generate_message(@message, options.dup)
|
||||
# When type is a string, it means that we do not have to do a lookup, because
|
||||
# the user already sent the "final" message.
|
||||
type.is_a?(String) ? type : generate_message(default_options)
|
||||
end
|
||||
|
||||
def full_message
|
||||
attribute.to_s == 'base' ? message : generate_full_message(message, options.dup)
|
||||
attribute.to_s == 'base' ? message : generate_full_message(default_options)
|
||||
end
|
||||
|
||||
alias :to_s :message
|
||||
|
@ -60,24 +62,19 @@ module ActiveRecord
|
|||
# <li><tt>activerecord.errors.messages.blank</tt></li>
|
||||
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
|
||||
# </ol>
|
||||
def generate_message(message, options = {})
|
||||
def generate_message(options = {})
|
||||
keys = @base.class.self_and_descendants_from_active_record.map do |klass|
|
||||
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
||||
:"models.#{klass.name.underscore}.#{message}" ]
|
||||
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{@message}",
|
||||
:"models.#{klass.name.underscore}.#{@message}" ]
|
||||
end.flatten
|
||||
|
||||
keys << options.delete(:default)
|
||||
keys << :"messages.#{message}"
|
||||
keys << message if message.is_a?(String)
|
||||
keys << @type unless @type == message
|
||||
keys << :"messages.#{@message}"
|
||||
keys << @message if @message.is_a?(String)
|
||||
keys << @type unless @type == @message
|
||||
keys.compact!
|
||||
|
||||
options.reverse_merge! :default => keys,
|
||||
:scope => [:activerecord, :errors],
|
||||
:model => @base.class.human_name,
|
||||
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||
:value => value
|
||||
|
||||
options.merge!(:default => keys)
|
||||
I18n.translate(keys.shift, options)
|
||||
end
|
||||
|
||||
|
@ -108,16 +105,24 @@ module ActiveRecord
|
|||
# full_messages:
|
||||
# title:
|
||||
# blank: This title is screwed!
|
||||
def generate_full_message(message, options = {})
|
||||
options.reverse_merge! :message => self.message,
|
||||
:model => @base.class.human_name,
|
||||
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||
:value => value
|
||||
def generate_full_message(options = {})
|
||||
keys = [
|
||||
:"full_messages.#{@message}",
|
||||
:'full_messages.format',
|
||||
'{{attribute}} {{message}}'
|
||||
]
|
||||
|
||||
key = :"full_messages.#{@message}"
|
||||
defaults = [:'full_messages.format', '{{attribute}} {{message}}']
|
||||
options.merge!(:default => keys, :message => self.message)
|
||||
I18n.translate(keys.shift, options)
|
||||
end
|
||||
|
||||
I18n.t(key, options.merge(:default => defaults, :scope => [:activerecord, :errors]))
|
||||
# Return user options with default options.
|
||||
#
|
||||
def default_options
|
||||
options.reverse_merge :scope => [:activerecord, :errors],
|
||||
:model => @base.class.human_name,
|
||||
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
||||
:value => value
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -134,7 +139,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def initialize(base) # :nodoc:
|
||||
@base, @errors = base, {}
|
||||
@base = base
|
||||
clear
|
||||
end
|
||||
|
||||
# Adds an error to the base object instead of any particular attribute. This is used
|
||||
|
@ -150,16 +156,10 @@ module ActiveRecord
|
|||
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
||||
# If no +messsage+ is supplied, :invalid is assumed.
|
||||
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
||||
# def add(attribute, message = nil, options = {})
|
||||
# message ||= :invalid
|
||||
# message = generate_message(attribute, message, options)) if message.is_a?(Symbol)
|
||||
# @errors[attribute.to_s] ||= []
|
||||
# @errors[attribute.to_s] << message
|
||||
# end
|
||||
|
||||
def add(error_or_attr, message = nil, options = {})
|
||||
error, attribute = error_or_attr.is_a?(Error) ? [error_or_attr, error_or_attr.attribute] : [nil, error_or_attr]
|
||||
#
|
||||
def add(attribute, message = nil, options = {})
|
||||
options[:message] = options.delete(:default) if options.has_key?(:default)
|
||||
error, message = message, nil if message.is_a?(Error)
|
||||
|
||||
@errors[attribute.to_s] ||= []
|
||||
@errors[attribute.to_s] << (error || Error.new(@base, attribute, message, options))
|
||||
|
@ -283,7 +283,7 @@ module ActiveRecord
|
|||
|
||||
# Removes all errors that have been added.
|
||||
def clear
|
||||
@errors = {}
|
||||
@errors = ActiveSupport::OrderedHash.new
|
||||
end
|
||||
|
||||
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
|
||||
|
@ -321,7 +321,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def generate_message(attribute, message = :invalid, options = {})
|
||||
ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error#generate_message.")
|
||||
ActiveSupport::Deprecation.warn("ActiveRecord::Errors#generate_message has been deprecated. Please use ActiveRecord::Error.new().to_s.")
|
||||
Error.new(@base, attribute, message, options).to_s
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActiveRecord
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 4
|
||||
TINY = 5
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
require 'active_record'
|
||||
ActiveSupport::Deprecation.warn 'require "activerecord" is deprecated and will be removed in Rails 3. Use require "active_record" instead.'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue