mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Added preliminary support for join models [DHH] Added preliminary support for polymorphic associations [DHH] Refactored associations to use reflections to get DRYer, beware, major refactoring -- double check before deploying anything with this (all tests pass, but..)
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3213 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
57b7532b91
commit
6abda696b5
17 changed files with 521 additions and 447 deletions
|
@ -1,5 +1,9 @@
|
|||
*SVN*
|
||||
|
||||
* Added preliminary support for polymorphic associations [DHH]
|
||||
|
||||
* Added preliminary support for join models [DHH]
|
||||
|
||||
* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. [jeremy@jthopple.com, Marcel Molina Jr.]
|
||||
|
||||
* Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz <kennethkunz@gmail.com>]
|
||||
|
|
|
@ -38,10 +38,10 @@ require 'active_record/base'
|
|||
require 'active_record/observer'
|
||||
require 'active_record/validations'
|
||||
require 'active_record/callbacks'
|
||||
require 'active_record/reflection'
|
||||
require 'active_record/associations'
|
||||
require 'active_record/aggregations'
|
||||
require 'active_record/transactions'
|
||||
require 'active_record/reflection'
|
||||
require 'active_record/timestamp'
|
||||
require 'active_record/acts/list'
|
||||
require 'active_record/acts/tree'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
module ActiveRecord
|
||||
module Aggregations # :nodoc:
|
||||
def self.append_features(base)
|
||||
super
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
|
@ -128,6 +127,8 @@ module ActiveRecord
|
|||
|
||||
reader_method(name, class_name, mapping)
|
||||
writer_method(name, class_name, mapping)
|
||||
|
||||
create_reflection(:composed_of, part_id, options, self)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -4,6 +4,7 @@ require 'active_record/associations/belongs_to_association'
|
|||
require 'active_record/associations/belongs_to_polymorphic_association'
|
||||
require 'active_record/associations/has_one_association'
|
||||
require 'active_record/associations/has_many_association'
|
||||
require 'active_record/associations/has_many_through_association'
|
||||
require 'active_record/associations/has_and_belongs_to_many_association'
|
||||
require 'active_record/deprecated_associations'
|
||||
|
||||
|
@ -341,57 +342,19 @@ module ActiveRecord
|
|||
# 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
|
||||
# 'ORDER BY p.first_name'
|
||||
def has_many(association_id, options = {}, &extension)
|
||||
options.assert_valid_keys(
|
||||
:foreign_key, :class_name, :exclusively_dependent, :dependent,
|
||||
:conditions, :order, :include, :finder_sql, :counter_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove, :extend,
|
||||
:group, :as
|
||||
)
|
||||
reflection = create_has_many_reflection(association_id, options, &extension)
|
||||
|
||||
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
||||
configure_dependency_for_has_many(reflection)
|
||||
|
||||
association_name, association_class_name, association_class_primary_key_name =
|
||||
associate_identification(association_id, options[:class_name], options[:foreign_key])
|
||||
|
||||
require_association_class(association_class_name)
|
||||
|
||||
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.' if options[:dependent] and options[:exclusively_dependent]
|
||||
|
||||
if options[:exclusively_dependent]
|
||||
options[:dependent] = :delete_all
|
||||
#warn "The :exclusively_dependent option is deprecated. Please use :dependent => :delete_all instead.")
|
||||
if options[:through]
|
||||
collection_reader_method(reflection, HasManyThroughAssociation)
|
||||
else
|
||||
add_multiple_associated_save_callbacks(reflection.name)
|
||||
add_association_callbacks(reflection.name, reflection.options)
|
||||
collection_accessor_methods(reflection, HasManyAssociation)
|
||||
end
|
||||
|
||||
# See HasManyAssociation#delete_records. Dependent associations
|
||||
# delete children, otherwise foreign key is set to NULL.
|
||||
case options[:dependent]
|
||||
when :destroy, true
|
||||
module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'"
|
||||
when :delete_all
|
||||
module_eval "before_destroy { |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = \#{record.quoted_id})) }"
|
||||
when :nullify
|
||||
module_eval "before_destroy { |record| #{association_class_name}.update_all(%(#{association_class_primary_key_name} = NULL), %(#{association_class_primary_key_name} = \#{record.quoted_id})) }"
|
||||
when nil, false
|
||||
# pass
|
||||
else
|
||||
raise ArgumentError, 'The :dependent option expects either true, :destroy, :delete_all, or :nullify'
|
||||
end
|
||||
|
||||
|
||||
add_multiple_associated_save_callbacks(association_name)
|
||||
add_association_callbacks(association_name, options)
|
||||
|
||||
collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasManyAssociation)
|
||||
|
||||
# deprecated api
|
||||
deprecated_collection_count_method(association_name)
|
||||
deprecated_add_association_relation(association_name)
|
||||
deprecated_remove_association_relation(association_name)
|
||||
deprecated_has_collection_method(association_name)
|
||||
deprecated_find_in_collection_method(association_name)
|
||||
deprecated_find_all_in_collection_method(association_name)
|
||||
deprecated_collection_create_method(association_name)
|
||||
deprecated_collection_build_method(association_name)
|
||||
add_deprecated_api_for_has_many(reflection.name)
|
||||
end
|
||||
|
||||
# Adds the following methods for retrieval and query of a single associated object.
|
||||
|
@ -436,42 +399,27 @@ module ActiveRecord
|
|||
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
|
||||
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
|
||||
def has_one(association_id, options = {})
|
||||
options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend)
|
||||
|
||||
association_name, association_class_name, association_class_primary_key_name =
|
||||
associate_identification(association_id, options[:class_name], options[:foreign_key], false)
|
||||
|
||||
require_association_class(association_class_name)
|
||||
reflection = create_has_one_reflection(association_id, options)
|
||||
|
||||
module_eval do
|
||||
after_save <<-EOF
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
unless association.nil?
|
||||
association["#{association_class_primary_key_name}"] = id
|
||||
association["#{reflection.primary_key_name}"] = id
|
||||
association.save(true)
|
||||
association.send(:construct_sql)
|
||||
end
|
||||
EOF
|
||||
end
|
||||
|
||||
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
|
||||
association_constructor_method(:build, association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
|
||||
association_constructor_method(:create, association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
|
||||
association_accessor_methods(reflection, HasOneAssociation)
|
||||
association_constructor_method(:build, reflection, HasOneAssociation)
|
||||
association_constructor_method(:create, reflection, HasOneAssociation)
|
||||
|
||||
case options[:dependent]
|
||||
when :destroy, true
|
||||
module_eval "before_destroy '#{association_name}.destroy unless #{association_name}.nil?'"
|
||||
when :nullify
|
||||
module_eval "before_destroy '#{association_name}.update_attribute(\"#{association_class_primary_key_name}\", nil)'"
|
||||
when nil, false
|
||||
# pass
|
||||
else
|
||||
raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
|
||||
end
|
||||
configure_dependency_for_has_one(reflection)
|
||||
|
||||
# deprecated api
|
||||
deprecated_has_association_method(association_name)
|
||||
deprecated_association_comparison_method(association_name, association_class_name)
|
||||
deprecated_has_association_method(reflection.name)
|
||||
deprecated_association_comparison_method(reflection.name, reflection.class_name)
|
||||
end
|
||||
|
||||
# Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
|
||||
|
@ -517,52 +465,41 @@ module ActiveRecord
|
|||
# belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
|
||||
# :conditions => 'discounts > #{payments_count}'
|
||||
def belongs_to(association_id, options = {})
|
||||
options.assert_valid_keys(:class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :polymorphic)
|
||||
|
||||
association_name, association_class_name, class_primary_key_name =
|
||||
associate_identification(association_id, options[:class_name], options[:foreign_key], false)
|
||||
|
||||
association_class_primary_key_name = options[:foreign_key] || association_class_name.foreign_key
|
||||
|
||||
if options[:polymorphic]
|
||||
options[:foreign_type] ||= association_class_name.underscore + "_type"
|
||||
|
||||
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, BelongsToPolymorphicAssociation)
|
||||
reflection = create_belongs_to_reflection(association_id, options)
|
||||
|
||||
if reflection.options[:polymorphic]
|
||||
association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
|
||||
|
||||
module_eval do
|
||||
before_save <<-EOF
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
if !association.nil?
|
||||
if association.new_record?
|
||||
association.save(true)
|
||||
association.send(:construct_sql)
|
||||
end
|
||||
|
||||
if association.updated?
|
||||
self["#{association_class_primary_key_name}"] = association.id
|
||||
self["#{options[:foreign_type]}"] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, association.class).to_s
|
||||
self["#{reflection.primary_key_name}"] = association.id
|
||||
self["#{reflection.options[:foreign_type]}"] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, association.class).to_s
|
||||
end
|
||||
end
|
||||
EOF
|
||||
end
|
||||
else
|
||||
require_association_class(association_class_name)
|
||||
|
||||
association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
|
||||
association_constructor_method(:build, association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
|
||||
association_constructor_method(:create, association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
|
||||
association_accessor_methods(reflection, BelongsToAssociation)
|
||||
association_constructor_method(:build, reflection, BelongsToAssociation)
|
||||
association_constructor_method(:create, reflection, BelongsToAssociation)
|
||||
|
||||
module_eval do
|
||||
before_save <<-EOF
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
if !association.nil?
|
||||
if association.new_record?
|
||||
association.save(true)
|
||||
association.send(:construct_sql)
|
||||
end
|
||||
|
||||
if association.updated?
|
||||
self["#{association_class_primary_key_name}"] = association.id
|
||||
self["#{reflection.primary_key_name}"] = association.id
|
||||
end
|
||||
end
|
||||
EOF
|
||||
|
@ -570,19 +507,19 @@ module ActiveRecord
|
|||
|
||||
if options[:counter_cache]
|
||||
module_eval(
|
||||
"after_create '#{association_class_name}.increment_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{association_class_primary_key_name})" +
|
||||
" unless #{association_name}.nil?'"
|
||||
"after_create '#{reflection.class_name}.increment_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{reflection.primary_key_name})" +
|
||||
" unless #{reflection.name}.nil?'"
|
||||
)
|
||||
|
||||
module_eval(
|
||||
"before_destroy '#{association_class_name}.decrement_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{association_class_primary_key_name})" +
|
||||
" unless #{association_name}.nil?'"
|
||||
"before_destroy '#{reflection.class_name}.decrement_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{reflection.primary_key_name})" +
|
||||
" unless #{reflection.name}.nil?'"
|
||||
)
|
||||
end
|
||||
|
||||
# deprecated api
|
||||
deprecated_has_association_method(association_name)
|
||||
deprecated_association_comparison_method(association_name, association_class_name)
|
||||
deprecated_has_association_method(reflection.name)
|
||||
deprecated_association_comparison_method(reflection.name, reflection.class_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -663,43 +600,29 @@ module ActiveRecord
|
|||
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
|
||||
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
|
||||
def has_and_belongs_to_many(association_id, options = {}, &extension)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :table_name, :foreign_key, :association_foreign_key, :conditions, :include,
|
||||
:join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,
|
||||
:before_remove, :after_remove, :extend
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
||||
|
||||
association_name, association_class_name, association_class_primary_key_name =
|
||||
associate_identification(association_id, options[:class_name], options[:foreign_key])
|
||||
|
||||
require_association_class(association_class_name)
|
||||
|
||||
options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(association_class_name))
|
||||
|
||||
add_multiple_associated_save_callbacks(association_name)
|
||||
|
||||
collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasAndBelongsToManyAssociation)
|
||||
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
||||
|
||||
add_multiple_associated_save_callbacks(reflection.name)
|
||||
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
|
||||
|
||||
# Don't use a before_destroy callback since users' before_destroy
|
||||
# callbacks will be executed after the association is wiped out.
|
||||
old_method = "destroy_without_habtm_shim_for_#{association_name}"
|
||||
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
|
||||
class_eval <<-end_eval
|
||||
alias_method :#{old_method}, :destroy_without_callbacks
|
||||
def destroy_without_callbacks
|
||||
#{association_name}.clear
|
||||
#{reflection.name}.clear
|
||||
#{old_method}
|
||||
end
|
||||
end_eval
|
||||
|
||||
add_association_callbacks(association_name, options)
|
||||
add_association_callbacks(reflection.name, options)
|
||||
|
||||
# deprecated api
|
||||
deprecated_collection_count_method(association_name)
|
||||
deprecated_add_association_relation(association_name)
|
||||
deprecated_remove_association_relation(association_name)
|
||||
deprecated_has_collection_method(association_name)
|
||||
deprecated_collection_count_method(reflection.name)
|
||||
deprecated_add_association_relation(reflection.name)
|
||||
deprecated_remove_association_relation(reflection.name)
|
||||
deprecated_has_collection_method(reflection.name)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -713,93 +636,81 @@ module ActiveRecord
|
|||
table_name_prefix + join_table + table_name_suffix
|
||||
end
|
||||
|
||||
def associate_identification(association_id, association_class_name, foreign_key, plural = true)
|
||||
if association_class_name !~ /::/
|
||||
association_class_name = type_name_with_module(
|
||||
association_class_name ||
|
||||
Inflector.camelize(plural ? Inflector.singularize(association_id.id2name) : association_id.id2name)
|
||||
)
|
||||
end
|
||||
|
||||
primary_key_name = foreign_key || name.foreign_key
|
||||
|
||||
return association_id.id2name, association_class_name, primary_key_name
|
||||
end
|
||||
|
||||
def association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class)
|
||||
define_method(association_name) do |*params|
|
||||
def association_accessor_methods(reflection, association_proxy_class)
|
||||
define_method(reflection.name) do |*params|
|
||||
force_reload = params.first unless params.empty?
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
if association.nil? or force_reload
|
||||
association = association_proxy_class.new(self,
|
||||
association_name, association_class_name,
|
||||
association_class_primary_key_name, options)
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
|
||||
if association.nil? || force_reload
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
retval = association.reload
|
||||
unless retval.nil?
|
||||
instance_variable_set("@#{association_name}", association)
|
||||
instance_variable_set("@#{reflection.name}", association)
|
||||
else
|
||||
instance_variable_set("@#{association_name}", nil)
|
||||
instance_variable_set("@#{reflection.name}", nil)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
association
|
||||
end
|
||||
|
||||
define_method("#{association_name}=") do |new_value|
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
define_method("#{reflection.name}=") do |new_value|
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
if association.nil?
|
||||
association = association_proxy_class.new(self,
|
||||
association_name, association_class_name,
|
||||
association_class_primary_key_name, options)
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
end
|
||||
|
||||
association.replace(new_value)
|
||||
|
||||
unless new_value.nil?
|
||||
instance_variable_set("@#{association_name}", association)
|
||||
instance_variable_set("@#{reflection.name}", association)
|
||||
else
|
||||
instance_variable_set("@#{association_name}", nil)
|
||||
instance_variable_set("@#{reflection.name}", nil)
|
||||
return nil
|
||||
end
|
||||
|
||||
association
|
||||
end
|
||||
|
||||
define_method("set_#{association_name}_target") do |target|
|
||||
define_method("set_#{reflection.name}_target") do |target|
|
||||
return if target.nil?
|
||||
association = association_proxy_class.new(self,
|
||||
association_name, association_class_name,
|
||||
association_class_primary_key_name, options)
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
association.target = target
|
||||
instance_variable_set("@#{association_name}", association)
|
||||
instance_variable_set("@#{reflection.name}", association)
|
||||
end
|
||||
end
|
||||
|
||||
def collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class)
|
||||
define_method(association_name) do |*params|
|
||||
def collection_reader_method(reflection, association_proxy_class)
|
||||
define_method(reflection.name) do |*params|
|
||||
force_reload = params.first unless params.empty?
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
|
||||
unless association.respond_to?(:loaded?)
|
||||
association = association_proxy_class.new(self,
|
||||
association_name, association_class_name,
|
||||
association_class_primary_key_name, options)
|
||||
instance_variable_set("@#{association_name}", association)
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
instance_variable_set("@#{reflection.name}", association)
|
||||
end
|
||||
|
||||
association.reload if force_reload
|
||||
|
||||
association
|
||||
end
|
||||
end
|
||||
|
||||
define_method("#{association_name}=") do |new_value|
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
def collection_accessor_methods(reflection, association_proxy_class)
|
||||
collection_reader_method(reflection, association_proxy_class)
|
||||
|
||||
define_method("#{reflection.name}=") do |new_value|
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
unless association.respond_to?(:loaded?)
|
||||
association = association_proxy_class.new(self,
|
||||
association_name, association_class_name,
|
||||
association_class_primary_key_name, options)
|
||||
instance_variable_set("@#{association_name}", association)
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
instance_variable_set("@#{reflection.name}", association)
|
||||
end
|
||||
association.replace(new_value)
|
||||
association
|
||||
end
|
||||
|
||||
define_method("#{Inflector.singularize(association_name)}_ids=") do |new_value|
|
||||
send("#{association_name}=", association_class_name.constantize.find(new_value))
|
||||
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
||||
send("#{reflection.name}=", reflection.class_name.constantize.find(new_value))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -847,17 +758,15 @@ module ActiveRecord
|
|||
after_update(after_callback)
|
||||
end
|
||||
|
||||
def association_constructor_method(constructor, association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class)
|
||||
define_method("#{constructor}_#{association_name}") do |*params|
|
||||
def association_constructor_method(constructor, reflection, association_proxy_class)
|
||||
define_method("#{constructor}_#{reflection.name}") do |*params|
|
||||
attributees = params.first unless params.empty?
|
||||
replace_existing = params[1].nil? ? true : params[1]
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
association = instance_variable_get("@#{reflection.name}")
|
||||
|
||||
if association.nil?
|
||||
association = association_proxy_class.new(self,
|
||||
association_name, association_class_name,
|
||||
association_class_primary_key_name, options)
|
||||
instance_variable_set("@#{association_name}", association)
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
instance_variable_set("@#{reflection.name}", association)
|
||||
end
|
||||
|
||||
if association_proxy_class == HasOneAssociation
|
||||
|
@ -910,6 +819,118 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
|
||||
def configure_dependency_for_has_many(reflection)
|
||||
if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
|
||||
raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
|
||||
end
|
||||
|
||||
if reflection.options[:exclusively_dependent]
|
||||
reflection.options[:dependent] = :delete_all
|
||||
#warn "The :exclusively_dependent option is deprecated. Please use :dependent => :delete_all instead.")
|
||||
end
|
||||
|
||||
# See HasManyAssociation#delete_records. Dependent associations
|
||||
# delete children, otherwise foreign key is set to NULL.
|
||||
case reflection.options[:dependent]
|
||||
when :destroy, true
|
||||
module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
|
||||
when :delete_all
|
||||
module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{reflection.primary_key_name} = \#{record.quoted_id})) }"
|
||||
when :nullify
|
||||
module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{reflection.primary_key_name} = \#{record.quoted_id})) }"
|
||||
when nil, false
|
||||
# pass
|
||||
else
|
||||
raise ArgumentError, 'The :dependent option expects either true, :destroy, :delete_all, or :nullify'
|
||||
end
|
||||
end
|
||||
|
||||
def configure_dependency_for_has_one(reflection)
|
||||
case reflection.options[:dependent]
|
||||
when :destroy, true
|
||||
module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
|
||||
when :nullify
|
||||
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
|
||||
when nil, false
|
||||
# pass
|
||||
else
|
||||
raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_deprecated_api_for_has_many(association_name)
|
||||
deprecated_collection_count_method(association_name)
|
||||
deprecated_add_association_relation(association_name)
|
||||
deprecated_remove_association_relation(association_name)
|
||||
deprecated_has_collection_method(association_name)
|
||||
deprecated_find_in_collection_method(association_name)
|
||||
deprecated_find_all_in_collection_method(association_name)
|
||||
deprecated_collection_create_method(association_name)
|
||||
deprecated_collection_build_method(association_name)
|
||||
end
|
||||
|
||||
def create_has_many_reflection(association_id, options, &extension)
|
||||
options.assert_valid_keys(
|
||||
:foreign_key, :class_name, :exclusively_dependent, :dependent,
|
||||
:conditions, :order, :include, :finder_sql, :counter_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove, :extend,
|
||||
:group, :as, :through
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
||||
|
||||
reflection = create_reflection(:has_many, association_id, options, self)
|
||||
reflection.require_class
|
||||
|
||||
reflection
|
||||
end
|
||||
|
||||
def create_has_one_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend
|
||||
)
|
||||
|
||||
reflection = create_reflection(:has_one, association_id, options, self)
|
||||
reflection.require_class
|
||||
|
||||
reflection
|
||||
end
|
||||
|
||||
def create_belongs_to_reflection(association_id, options)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
|
||||
:counter_cache, :extend, :polymorphic
|
||||
)
|
||||
|
||||
reflection = create_reflection(:belongs_to, association_id, options, self)
|
||||
|
||||
if options[:polymorphic]
|
||||
reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
|
||||
else
|
||||
reflection.require_class
|
||||
end
|
||||
|
||||
reflection
|
||||
end
|
||||
|
||||
def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
|
||||
options.assert_valid_keys(
|
||||
:class_name, :table_name, :foreign_key, :association_foreign_key, :conditions, :include,
|
||||
:join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,
|
||||
:before_remove, :after_remove, :extend
|
||||
)
|
||||
|
||||
options[:extend] = create_extension_module(association_id, extension) if block_given?
|
||||
|
||||
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
|
||||
reflection.require_class
|
||||
|
||||
reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
|
||||
|
||||
reflection
|
||||
end
|
||||
|
||||
def reflect_on_included_associations(associations)
|
||||
[ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
|
||||
end
|
||||
|
|
|
@ -18,6 +18,7 @@ module ActiveRecord
|
|||
def <<(*records)
|
||||
result = true
|
||||
load_target
|
||||
|
||||
@owner.transaction do
|
||||
flatten_deeper(records).each do |record|
|
||||
raise_on_type_mismatch(record)
|
||||
|
@ -28,7 +29,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
result and self
|
||||
result && self
|
||||
end
|
||||
|
||||
alias_method :push, :<<
|
||||
|
@ -60,11 +61,13 @@ module ActiveRecord
|
|||
# Removes all records from this association. Returns +self+ so method calls may be chained.
|
||||
def clear
|
||||
return self if length.zero? # forces load_target if hasn't happened already
|
||||
if @options[:exclusively_dependent]
|
||||
|
||||
if @reflection.options[:exclusively_dependent]
|
||||
destroy_all
|
||||
else
|
||||
delete_all
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -124,14 +127,6 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
private
|
||||
def raise_on_type_mismatch(record)
|
||||
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
|
||||
end
|
||||
|
||||
def target_obsolete?
|
||||
false
|
||||
end
|
||||
|
||||
# Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
|
||||
def flatten_deeper(array)
|
||||
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
|
||||
|
@ -155,8 +150,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def callbacks_for(callback_name)
|
||||
full_callback_name = "#{callback_name.to_s}_for_#{@association_name.to_s}"
|
||||
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) or []
|
||||
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
|
||||
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,15 +5,9 @@ module ActiveRecord
|
|||
alias_method :proxy_extend, :extend
|
||||
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }
|
||||
|
||||
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
||||
@owner = owner
|
||||
@options = options
|
||||
@association_name = association_name
|
||||
@association_class = eval(association_class_name, nil, __FILE__, __LINE__)
|
||||
@association_class_primary_key_name = association_class_primary_key_name
|
||||
|
||||
proxy_extend(options[:extend]) if options[:extend]
|
||||
|
||||
def initialize(owner, reflection)
|
||||
@owner, @reflection = owner, reflection
|
||||
proxy_extend(reflection.options[:extend]) if reflection.options[:extend]
|
||||
reset
|
||||
end
|
||||
|
||||
|
@ -28,6 +22,11 @@ module ActiveRecord
|
|||
other === @target
|
||||
end
|
||||
|
||||
def reset
|
||||
@target = nil
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
def reload
|
||||
reset
|
||||
load_target
|
||||
|
@ -45,14 +44,14 @@ module ActiveRecord
|
|||
@target
|
||||
end
|
||||
|
||||
def target=(t)
|
||||
@target = t
|
||||
@loaded = true
|
||||
def target=(target)
|
||||
@target = target
|
||||
loaded
|
||||
end
|
||||
|
||||
protected
|
||||
def dependent?
|
||||
@options[:dependent] || false
|
||||
@reflection.options[:dependent] || false
|
||||
end
|
||||
|
||||
def quoted_record_ids(records)
|
||||
|
@ -68,7 +67,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def sanitize_sql(sql)
|
||||
@association_class.send(:sanitize_sql, sql)
|
||||
@reflection.klass.send(:sanitize_sql, sql)
|
||||
end
|
||||
|
||||
def extract_options_from_args!(args)
|
||||
|
@ -84,13 +83,14 @@ module ActiveRecord
|
|||
def load_target
|
||||
if !@owner.new_record? || foreign_key_present
|
||||
begin
|
||||
@target = find_target if not loaded?
|
||||
@target = find_target if !loaded?
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
reset
|
||||
end
|
||||
end
|
||||
@loaded = true if @target
|
||||
@target
|
||||
|
||||
loaded if target
|
||||
target
|
||||
end
|
||||
|
||||
# Can be overwritten by associations that might have the foreign key available for an association without
|
||||
|
@ -100,7 +100,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def raise_on_type_mismatch(record)
|
||||
raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
|
||||
unless record.is_a?(@reflection.klass)
|
||||
raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,41 +1,27 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class BelongsToAssociation < AssociationProxy #:nodoc:
|
||||
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
||||
super
|
||||
construct_sql
|
||||
end
|
||||
|
||||
def reset
|
||||
@target = nil
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
def create(attributes = {})
|
||||
record = @association_class.create(attributes)
|
||||
replace(record, true)
|
||||
record
|
||||
replace(@reflection.klass.create(attributes))
|
||||
end
|
||||
|
||||
def build(attributes = {})
|
||||
record = @association_class.new(attributes)
|
||||
replace(record, true)
|
||||
record
|
||||
replace(@reflection.klass.new(attributes))
|
||||
end
|
||||
|
||||
def replace(obj, dont_save = false)
|
||||
if obj.nil?
|
||||
@target = @owner[@association_class_primary_key_name] = nil
|
||||
def replace(record)
|
||||
if record.nil?
|
||||
@target = @owner[@reflection.primary_key_name] = nil
|
||||
else
|
||||
raise_on_type_mismatch(obj) unless obj.nil?
|
||||
raise_on_type_mismatch(record)
|
||||
|
||||
@target = (AssociationProxy === obj ? obj.target : obj)
|
||||
@owner[@association_class_primary_key_name] = obj.id unless obj.new_record?
|
||||
@target = (AssociationProxy === record ? record.target : record)
|
||||
@owner[@reflection.primary_key_name] = record.id unless record.new_record?
|
||||
@updated = true
|
||||
end
|
||||
@loaded = true
|
||||
|
||||
return (@target.nil? ? nil : self)
|
||||
loaded
|
||||
record
|
||||
end
|
||||
|
||||
def updated?
|
||||
|
@ -44,27 +30,15 @@ module ActiveRecord
|
|||
|
||||
private
|
||||
def find_target
|
||||
if @options[:conditions]
|
||||
@association_class.find(
|
||||
@owner[@association_class_primary_key_name],
|
||||
:conditions => interpolate_sql(@options[:conditions]),
|
||||
:include => @options[:include]
|
||||
)
|
||||
else
|
||||
@association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include])
|
||||
end
|
||||
@reflection.klass.find(
|
||||
@owner[@reflection.primary_key_name],
|
||||
:conditions => @reflection.options[:conditions] ? interpolate_sql(@reflection.options[:conditions]) : nil,
|
||||
:include => @reflection.options[:include]
|
||||
)
|
||||
end
|
||||
|
||||
def foreign_key_present
|
||||
!@owner[@association_class_primary_key_name].nil?
|
||||
end
|
||||
|
||||
def target_obsolete?
|
||||
@owner[@association_class_primary_key_name] != @target.id
|
||||
end
|
||||
|
||||
def construct_sql
|
||||
@finder_sql = "#{@association_class.table_name}.#{@association_class.primary_key} = #{@owner.id}"
|
||||
!@owner[@reflection.primary_key_name].nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,69 +1,49 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
|
||||
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
||||
@owner = owner
|
||||
@options = options
|
||||
@association_name = association_name
|
||||
@association_class_primary_key_name = association_class_primary_key_name
|
||||
|
||||
proxy_extend(options[:extend]) if options[:extend]
|
||||
|
||||
reset
|
||||
end
|
||||
|
||||
def create(attributes = {})
|
||||
raise ActiveRecord::ActiveRecordError, "Can't create an abstract polymorphic object"
|
||||
end
|
||||
|
||||
def build(attributes = {})
|
||||
raise ActiveRecord::ActiveRecordError, "Can't build an abstract polymorphic object"
|
||||
end
|
||||
|
||||
def replace(obj, dont_save = false)
|
||||
if obj.nil?
|
||||
@target = @owner[@association_class_primary_key_name] = @owner[@options[:foreign_type]] = nil
|
||||
class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
|
||||
def replace(record)
|
||||
if record.nil?
|
||||
@target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
|
||||
else
|
||||
@target = (AssociationProxy === obj ? obj.target : obj)
|
||||
@target = (AssociationProxy === record ? record.target : record)
|
||||
|
||||
unless obj.new_record?
|
||||
@owner[@association_class_primary_key_name] = obj.id
|
||||
@owner[@options[:foreign_type]] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, obj.class).to_s
|
||||
unless record.new_record?
|
||||
@owner[@reflection.primary_key_name] = record.id
|
||||
@owner[@reflection.options[:foreign_type]] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, record.class).to_s
|
||||
end
|
||||
|
||||
@updated = true
|
||||
end
|
||||
|
||||
@loaded = true
|
||||
|
||||
return (@target.nil? ? nil : self)
|
||||
loaded
|
||||
record
|
||||
end
|
||||
|
||||
|
||||
def updated?
|
||||
@updated
|
||||
end
|
||||
|
||||
private
|
||||
def find_target
|
||||
return nil if association_class.nil?
|
||||
|
||||
if @options[:conditions]
|
||||
if @reflection.options[:conditions]
|
||||
association_class.find(
|
||||
@owner[@association_class_primary_key_name],
|
||||
:conditions => interpolate_sql(@options[:conditions]),
|
||||
:include => @options[:include]
|
||||
@owner[@reflection.primary_key_name],
|
||||
:conditions => interpolate_sql(@reflection.options[:conditions]),
|
||||
:include => @reflection.options[:include]
|
||||
)
|
||||
else
|
||||
association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include])
|
||||
association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
|
||||
end
|
||||
end
|
||||
|
||||
def foreign_key_present
|
||||
!@owner[@association_class_primary_key_name].nil?
|
||||
!@owner[@reflection.primary_key_name].nil?
|
||||
end
|
||||
|
||||
def target_obsolete?
|
||||
@owner[@association_class_primary_key_name] != @target.id
|
||||
end
|
||||
|
||||
def association_class
|
||||
@owner[@options[:foreign_type]] ? @owner[@options[:foreign_type]].constantize : nil
|
||||
@owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
||||
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
|
||||
@association_foreign_key = options[:association_foreign_key] || association_class_name.foreign_key
|
||||
@association_table_name = options[:table_name] || @association_class.table_name
|
||||
@join_table = options[:join_table]
|
||||
@order = options[:order]
|
||||
|
||||
construct_sql
|
||||
end
|
||||
|
||||
def build(attributes = {})
|
||||
load_target
|
||||
record = @association_class.new(attributes)
|
||||
record = @reflection.klass.new(attributes)
|
||||
@target << record
|
||||
record
|
||||
end
|
||||
|
@ -27,7 +21,7 @@ module ActiveRecord
|
|||
options = Base.send(:extract_options_from_args!, args)
|
||||
|
||||
# If using a custom finder_sql, scan the entire collection.
|
||||
if @options[:finder_sql]
|
||||
if @reflection.options[:finder_sql]
|
||||
expects_array = args.first.kind_of?(Array)
|
||||
ids = args.flatten.compact.uniq
|
||||
|
||||
|
@ -40,60 +34,64 @@ module ActiveRecord
|
|||
end
|
||||
else
|
||||
conditions = "#{@finder_sql}"
|
||||
|
||||
if sanitized_conditions = sanitize_sql(options[:conditions])
|
||||
conditions << " AND (#{sanitized_conditions})"
|
||||
end
|
||||
|
||||
options[:conditions] = conditions
|
||||
options[:joins] = @join_sql
|
||||
options[:readonly] ||= false
|
||||
|
||||
if options[:order] && @options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@options[:order]}"
|
||||
elsif @options[:order]
|
||||
options[:order] = @options[:order]
|
||||
if options[:order] && @reflection.options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
||||
elsif @reflection.options[:order]
|
||||
options[:order] = @reflection.options[:order]
|
||||
end
|
||||
|
||||
# Pass through args exactly as we received them.
|
||||
args << options
|
||||
@association_class.find(*args)
|
||||
@reflection.klass.find(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def push_with_attributes(record, join_attributes = {})
|
||||
raise_on_type_mismatch(record)
|
||||
join_attributes.each { |key, value| record[key.to_s] = value }
|
||||
|
||||
callback(:before_add, record)
|
||||
insert_record(record) unless @owner.new_record?
|
||||
@target << record
|
||||
callback(:after_add, record)
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
alias :concat_with_attributes :push_with_attributes
|
||||
|
||||
def size
|
||||
@options[:uniq] ? count_records : super
|
||||
@reflection.options[:uniq] ? count_records : super
|
||||
end
|
||||
|
||||
protected
|
||||
def method_missing(method, *args, &block)
|
||||
if @target.respond_to?(method) || (!@association_class.respond_to?(method) && Class.respond_to?(method))
|
||||
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
||||
super
|
||||
else
|
||||
@association_class.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
|
||||
@association_class.send(method, *args, &block)
|
||||
@reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
|
||||
@reflection.klass.send(method, *args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_target
|
||||
if @options[:finder_sql]
|
||||
records = @association_class.find_by_sql(@finder_sql)
|
||||
if @reflection.options[:finder_sql]
|
||||
records = @reflection.klass.find_by_sql(@finder_sql)
|
||||
else
|
||||
records = find(:all, :include => @options[:include])
|
||||
records = find(:all, :include => @reflection.options[:include])
|
||||
end
|
||||
|
||||
@options[:uniq] ? uniq(records) : records
|
||||
@reflection.options[:uniq] ? uniq(records) : records
|
||||
end
|
||||
|
||||
def count_records
|
||||
|
@ -105,16 +103,16 @@ module ActiveRecord
|
|||
return false unless record.save
|
||||
end
|
||||
|
||||
if @options[:insert_sql]
|
||||
@owner.connection.execute(interpolate_sql(@options[:insert_sql], record))
|
||||
if @reflection.options[:insert_sql]
|
||||
@owner.connection.execute(interpolate_sql(@reflection.options[:insert_sql], record))
|
||||
else
|
||||
columns = @owner.connection.columns(@join_table, "#{@join_table} Columns")
|
||||
columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
||||
|
||||
attributes = columns.inject({}) do |attributes, column|
|
||||
case column.name
|
||||
when @association_class_primary_key_name
|
||||
when @reflection.primary_key_name
|
||||
attributes[column.name] = @owner.quoted_id
|
||||
when @association_foreign_key
|
||||
when @reflection.association_foreign_key
|
||||
attributes[column.name] = record.quoted_id
|
||||
else
|
||||
if record.attributes.has_key?(column.name)
|
||||
|
@ -126,7 +124,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
sql =
|
||||
"INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
||||
"INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
||||
"VALUES (#{attributes.values.join(', ')})"
|
||||
|
||||
@owner.connection.execute(sql)
|
||||
|
@ -136,26 +134,26 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def delete_records(records)
|
||||
if sql = @options[:delete_sql]
|
||||
if sql = @reflection.options[:delete_sql]
|
||||
records.each { |record| @owner.connection.execute(interpolate_sql(sql, record)) }
|
||||
else
|
||||
ids = quoted_record_ids(records)
|
||||
sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_foreign_key} IN (#{ids})"
|
||||
sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
|
||||
@owner.connection.execute(sql)
|
||||
end
|
||||
end
|
||||
|
||||
def construct_sql
|
||||
interpolate_sql_options!(@options, :finder_sql)
|
||||
interpolate_sql_options!(@reflection.options, :finder_sql)
|
||||
|
||||
if @options[:finder_sql]
|
||||
@finder_sql = @options[:finder_sql]
|
||||
if @reflection.options[:finder_sql]
|
||||
@finder_sql = @reflection.options[:finder_sql]
|
||||
else
|
||||
@finder_sql = "#{@join_table}.#{@association_class_primary_key_name} = #{@owner.quoted_id} "
|
||||
@finder_sql << " AND (#{interpolate_sql(@options[:conditions])})" if @options[:conditions]
|
||||
@finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
|
||||
@finder_sql << " AND (#{interpolate_sql(@reflection.options[:conditions])})" if @reflection.options[:conditions]
|
||||
end
|
||||
|
||||
@join_sql = "JOIN #{@join_table} ON #{@association_class.table_name}.#{@association_class.primary_key} = #{@join_table}.#{@association_foreign_key}"
|
||||
@join_sql = "JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasManyAssociation < AssociationCollection #:nodoc:
|
||||
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
@conditions = sanitize_sql(options[:conditions])
|
||||
|
||||
@conditions = sanitize_sql(reflection.options[:conditions])
|
||||
construct_sql
|
||||
end
|
||||
|
||||
|
@ -13,8 +12,8 @@ module ActiveRecord
|
|||
attributes.collect { |attr| create(attr) }
|
||||
else
|
||||
load_target
|
||||
record = @association_class.new(attributes)
|
||||
record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
|
||||
record = @reflection.klass.new(attributes)
|
||||
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
||||
@target << record
|
||||
record
|
||||
end
|
||||
|
@ -22,13 +21,13 @@ module ActiveRecord
|
|||
|
||||
# DEPRECATED.
|
||||
def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
|
||||
if @options[:finder_sql]
|
||||
@association_class.find_by_sql(@finder_sql)
|
||||
if @reflection.options[:finder_sql]
|
||||
@reflection.klass.find_by_sql(@finder_sql)
|
||||
else
|
||||
conditions = @finder_sql
|
||||
conditions += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
|
||||
orderings ||= @options[:order]
|
||||
@association_class.find_all(conditions, orderings, limit, joins)
|
||||
orderings ||= @reflection.options[:order]
|
||||
@reflection.klass.find_all(conditions, orderings, limit, joins)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -39,14 +38,14 @@ module ActiveRecord
|
|||
|
||||
# Count the number of associated records. All arguments are optional.
|
||||
def count(runtime_conditions = nil)
|
||||
if @options[:counter_sql]
|
||||
@association_class.count_by_sql(@counter_sql)
|
||||
elsif @options[:finder_sql]
|
||||
@association_class.count_by_sql(@finder_sql)
|
||||
if @reflection.options[:counter_sql]
|
||||
@reflection.klass.count_by_sql(@counter_sql)
|
||||
elsif @reflection.options[:finder_sql]
|
||||
@reflection.klass.count_by_sql(@finder_sql)
|
||||
else
|
||||
sql = @finder_sql
|
||||
sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
|
||||
@association_class.count(sql)
|
||||
@reflection.klass.count(sql)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -54,7 +53,7 @@ module ActiveRecord
|
|||
options = Base.send(:extract_options_from_args!, args)
|
||||
|
||||
# If using a custom finder_sql, scan the entire collection.
|
||||
if @options[:finder_sql]
|
||||
if @reflection.options[:finder_sql]
|
||||
expects_array = args.first.kind_of?(Array)
|
||||
ids = args.flatten.compact.uniq
|
||||
|
||||
|
@ -72,49 +71,49 @@ module ActiveRecord
|
|||
end
|
||||
options[:conditions] = conditions
|
||||
|
||||
if options[:order] && @options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@options[:order]}"
|
||||
elsif @options[:order]
|
||||
options[:order] = @options[:order]
|
||||
if options[:order] && @reflection.options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
||||
elsif @reflection.options[:order]
|
||||
options[:order] = @reflection.options[:order]
|
||||
end
|
||||
|
||||
# Pass through args exactly as we received them.
|
||||
args << options
|
||||
@association_class.find(*args)
|
||||
@reflection.klass.find(*args)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def method_missing(method, *args, &block)
|
||||
if @target.respond_to?(method) || (!@association_class.respond_to?(method) && Class.respond_to?(method))
|
||||
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
||||
super
|
||||
else
|
||||
@association_class.with_scope(
|
||||
@reflection.klass.with_scope(
|
||||
:find => {
|
||||
:conditions => @finder_sql,
|
||||
:joins => @join_sql,
|
||||
:readonly => false
|
||||
},
|
||||
:create => {
|
||||
@association_class_primary_key_name => @owner.id
|
||||
@reflection.primary_key_name => @owner.id
|
||||
}
|
||||
) do
|
||||
@association_class.send(method, *args, &block)
|
||||
@reflection.klass.send(method, *args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_target
|
||||
if @options[:finder_sql]
|
||||
@association_class.find_by_sql(@finder_sql)
|
||||
if @reflection.options[:finder_sql]
|
||||
@reflection.klass.find_by_sql(@finder_sql)
|
||||
else
|
||||
@association_class.find(:all,
|
||||
@reflection.klass.find(:all,
|
||||
:conditions => @finder_sql,
|
||||
:order => @options[:order],
|
||||
:limit => @options[:limit],
|
||||
:joins => @options[:joins],
|
||||
:include => @options[:include],
|
||||
:group => @options[:group]
|
||||
:order => @reflection.options[:order],
|
||||
:limit => @reflection.options[:limit],
|
||||
:joins => @reflection.options[:joins],
|
||||
:include => @reflection.options[:include],
|
||||
:group => @reflection.options[:group]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -122,10 +121,10 @@ module ActiveRecord
|
|||
def count_records
|
||||
count = if has_cached_counter?
|
||||
@owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
elsif @options[:counter_sql]
|
||||
@association_class.count_by_sql(@counter_sql)
|
||||
elsif @reflection.options[:counter_sql]
|
||||
@reflection.klass.count_by_sql(@counter_sql)
|
||||
else
|
||||
@association_class.count(@counter_sql)
|
||||
@reflection.klass.count(@counter_sql)
|
||||
end
|
||||
|
||||
@target = [] and loaded if count == 0
|
||||
|
@ -138,22 +137,22 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def cached_counter_attribute_name
|
||||
"#{@association_name}_count"
|
||||
"#{@reflection.name}_count"
|
||||
end
|
||||
|
||||
def insert_record(record)
|
||||
record[@association_class_primary_key_name] = @owner.id
|
||||
record[@reflection.primary_key_name] = @owner.id
|
||||
record.save
|
||||
end
|
||||
|
||||
def delete_records(records)
|
||||
if @options[:dependent]
|
||||
if @reflection.options[:dependent]
|
||||
records.each { |r| r.destroy }
|
||||
else
|
||||
ids = quoted_record_ids(records)
|
||||
@association_class.update_all(
|
||||
"#{@association_class_primary_key_name} = NULL",
|
||||
"#{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_class.primary_key} IN (#{ids})"
|
||||
@reflection.klass.update_all(
|
||||
"#{@reflection.primary_key_name} = NULL",
|
||||
"#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -164,25 +163,25 @@ module ActiveRecord
|
|||
|
||||
def construct_sql
|
||||
case
|
||||
when @options[:as]
|
||||
when @reflection.options[:finder_sql]
|
||||
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
||||
|
||||
when @reflection.options[:as]
|
||||
@finder_sql =
|
||||
"#{@association_class.table_name}.#{@options[:as]}_id = #{@owner.quoted_id} AND " +
|
||||
"#{@association_class.table_name}.#{@options[:as]}_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, @owner.class).to_s}'"
|
||||
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
||||
"#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, @owner.class).to_s}'"
|
||||
@finder_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
|
||||
|
||||
when @options[:finder_sql]
|
||||
@finder_sql = interpolate_sql(@options[:finder_sql])
|
||||
|
||||
else
|
||||
@finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
|
||||
end
|
||||
|
||||
if @options[:counter_sql]
|
||||
@counter_sql = interpolate_sql(@options[:counter_sql])
|
||||
elsif @options[:finder_sql]
|
||||
@options[:counter_sql] = @options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
|
||||
@counter_sql = interpolate_sql(@options[:counter_sql])
|
||||
if @reflection.options[:counter_sql]
|
||||
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
||||
elsif @reflection.options[:finder_sql]
|
||||
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
|
||||
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
||||
else
|
||||
@counter_sql = @finder_sql
|
||||
end
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasManyThroughAssociation < AssociationProxy #:nodoc:
|
||||
def find(*args)
|
||||
options = Base.send(:extract_options_from_args!, args)
|
||||
|
||||
conditions = "#{@finder_sql}"
|
||||
if sanitized_conditions = sanitize_sql(options[:conditions])
|
||||
conditions << " AND (#{sanitized_conditions})"
|
||||
end
|
||||
options[:conditions] = conditions
|
||||
|
||||
if options[:order] && @reflection.options[:order]
|
||||
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
||||
elsif @reflection.options[:order]
|
||||
options[:order] = @reflection.options[:order]
|
||||
end
|
||||
|
||||
# Pass through args exactly as we received them.
|
||||
args << options
|
||||
@reflection.klass.find(*args)
|
||||
end
|
||||
|
||||
def reset
|
||||
@target = []
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
protected
|
||||
def method_missing(method, *args, &block)
|
||||
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
||||
super
|
||||
else
|
||||
@reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
|
||||
end
|
||||
end
|
||||
|
||||
def find_target
|
||||
@reflection.klass.find(:all,
|
||||
:conditions => construct_conditions,
|
||||
:from => construct_from,
|
||||
:order => @reflection.options[:order],
|
||||
:limit => @reflection.options[:limit],
|
||||
:joins => @reflection.options[:joins],
|
||||
:group => @reflection.options[:group]
|
||||
)
|
||||
end
|
||||
|
||||
def construct_conditions
|
||||
through_reflection = @owner.class.reflections[@reflection.options[:through]]
|
||||
|
||||
if through_reflection.options[:as]
|
||||
conditions =
|
||||
"#{@reflection.table_name}.#{@reflection.klass.primary_key} = #{through_reflection.table_name}.#{@reflection.klass.to_s.foreign_key} " +
|
||||
"AND #{through_reflection.table_name}.#{through_reflection.options[:as]}_id = #{@owner.quoted_id} " +
|
||||
"AND #{through_reflection.table_name}.#{through_reflection.options[:as]}_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, @owner.class).to_s}'"
|
||||
else
|
||||
conditions =
|
||||
"#{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{through_reflection.table_name}.#{@reflection.klass.to_s.foreign_key} " +
|
||||
"AND #{through_reflection.table_name}.#{@owner.to_s.foreign_key} = #{@owner.quoted_id}"
|
||||
end
|
||||
|
||||
conditions << " AND (#{interpolate_sql(sanitize_sql(@reflection.options[:conditions]))})" if @reflection.options[:conditions]
|
||||
|
||||
return conditions
|
||||
end
|
||||
|
||||
def construct_from
|
||||
"#{@reflection.table_name}, #{@owner.class.reflections[@reflection.options[:through]].table_name}"
|
||||
end
|
||||
|
||||
def construct_scope
|
||||
{
|
||||
:find => { :conditions => construct_conditions },
|
||||
:create => { @reflection.primary_key_name => @owner.id }
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,9 +1,8 @@
|
|||
module ActiveRecord
|
||||
module Associations
|
||||
class HasOneAssociation < BelongsToAssociation #:nodoc:
|
||||
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
|
||||
def initialize(owner, reflection)
|
||||
super
|
||||
|
||||
construct_sql
|
||||
end
|
||||
|
||||
|
@ -14,12 +13,12 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def build(attributes = {}, replace_existing = true)
|
||||
record = @association_class.new(attributes)
|
||||
record = @reflection.klass.new(attributes)
|
||||
|
||||
if replace_existing
|
||||
replace(record, true)
|
||||
else
|
||||
record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
|
||||
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
||||
self.target = record
|
||||
end
|
||||
|
||||
|
@ -28,12 +27,13 @@ module ActiveRecord
|
|||
|
||||
def replace(obj, dont_save = false)
|
||||
load_target
|
||||
|
||||
unless @target.nil?
|
||||
if dependent? && !dont_save && @target != obj
|
||||
@target.destroy unless @target.new_record?
|
||||
@owner.clear_association_cache
|
||||
else
|
||||
@target[@association_class_primary_key_name] = nil
|
||||
@target[@reflection.primary_key_name] = nil
|
||||
@target.save unless @owner.new_record?
|
||||
end
|
||||
end
|
||||
|
@ -43,11 +43,12 @@ module ActiveRecord
|
|||
else
|
||||
raise_on_type_mismatch(obj)
|
||||
|
||||
obj[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
|
||||
obj[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
||||
@target = (AssociationProxy === obj ? obj.target : obj)
|
||||
end
|
||||
|
||||
@loaded = true
|
||||
|
||||
unless @owner.new_record? or obj.nil? or dont_save
|
||||
return (obj.save ? self : false)
|
||||
else
|
||||
|
@ -57,16 +58,16 @@ module ActiveRecord
|
|||
|
||||
private
|
||||
def find_target
|
||||
@association_class.find(:first, :conditions => @finder_sql, :order => @options[:order], :include => @options[:include])
|
||||
end
|
||||
|
||||
def target_obsolete?
|
||||
false
|
||||
@reflection.klass.find(:first,
|
||||
:conditions => @finder_sql,
|
||||
:order => @reflection.options[:order],
|
||||
:include => @reflection.options[:include]
|
||||
)
|
||||
end
|
||||
|
||||
def construct_sql
|
||||
@finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql << " AND (#{sanitize_sql(@options[:conditions])})" if @options[:conditions]
|
||||
@finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
||||
@finder_sql << " AND (#{sanitize_sql(@reflection.options[:conditions])})" if @reflection.options[:conditions]
|
||||
@finder_sql
|
||||
end
|
||||
end
|
||||
|
|
|
@ -930,12 +930,17 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def construct_finder_sql(options)
|
||||
sql = "SELECT #{options[:select] || '*'} FROM #{table_name} "
|
||||
sql = "SELECT #{options[:select] || '*'} "
|
||||
sql << "FROM #{options[:from] || table_name} "
|
||||
|
||||
add_joins!(sql, options)
|
||||
add_conditions!(sql, options[:conditions])
|
||||
|
||||
sql << " GROUP BY #{options[:group]} " if options[:group]
|
||||
sql << " ORDER BY #{options[:order]} " if options[:order]
|
||||
|
||||
add_limit!(sql, options)
|
||||
|
||||
sql
|
||||
end
|
||||
|
||||
|
@ -1180,7 +1185,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def validate_find_options(options)
|
||||
options.assert_valid_keys [:conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group]
|
||||
options.assert_valid_keys [:conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :from]
|
||||
end
|
||||
|
||||
def encode_quoted_value(value)
|
||||
|
|
|
@ -1,36 +1,7 @@
|
|||
module ActiveRecord
|
||||
module Reflection # :nodoc:
|
||||
def self.append_features(base)
|
||||
super
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.class_eval do
|
||||
class << self
|
||||
alias_method :composed_of_without_reflection, :composed_of
|
||||
|
||||
def composed_of_with_reflection(part_id, options = {})
|
||||
composed_of_without_reflection(part_id, options)
|
||||
reflect_on_all_aggregations << AggregateReflection.new(:composed_of, part_id, options, self)
|
||||
end
|
||||
|
||||
alias_method :composed_of, :composed_of_with_reflection
|
||||
end
|
||||
end
|
||||
|
||||
for association_type in %w( belongs_to has_one has_many has_and_belongs_to_many )
|
||||
base.module_eval <<-"end_eval"
|
||||
class << self
|
||||
alias_method :#{association_type}_without_reflection, :#{association_type}
|
||||
|
||||
def #{association_type}_with_reflection(association_id, options = {}, &block)
|
||||
#{association_type}_without_reflection(association_id, options, &block)
|
||||
reflect_on_all_associations << AssociationReflection.new(:#{association_type}, association_id, options, self)
|
||||
end
|
||||
|
||||
alias_method :#{association_type}, :#{association_type}_with_reflection
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
|
||||
# Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
|
||||
|
@ -39,26 +10,39 @@ module ActiveRecord
|
|||
#
|
||||
# You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
|
||||
module ClassMethods
|
||||
def create_reflection(macro, name, options, active_record)
|
||||
case macro
|
||||
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
||||
reflections[name] = AssociationReflection.new(macro, name, options, active_record)
|
||||
when :composed_of
|
||||
reflections[name] = AggregateReflection.new(macro, name, options, active_record)
|
||||
end
|
||||
end
|
||||
|
||||
def reflections
|
||||
read_inheritable_attribute(:reflections) or write_inheritable_attribute(:reflections, {})
|
||||
end
|
||||
|
||||
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
||||
def reflect_on_all_aggregations
|
||||
read_inheritable_attribute(:aggregations) or write_inheritable_attribute(:aggregations, [])
|
||||
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
|
||||
end
|
||||
|
||||
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
|
||||
# Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
|
||||
def reflect_on_aggregation(aggregation)
|
||||
reflect_on_all_aggregations.find { |reflection| reflection.name == aggregation } unless reflect_on_all_aggregations.nil?
|
||||
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
|
||||
end
|
||||
|
||||
# Returns an array of AssociationReflection objects for all the aggregations in the class.
|
||||
def reflect_on_all_associations
|
||||
read_inheritable_attribute(:associations) or write_inheritable_attribute(:associations, [])
|
||||
reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
|
||||
end
|
||||
|
||||
# Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
|
||||
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
||||
def reflect_on_association(association)
|
||||
reflect_on_all_associations.find { |reflection| reflection.name == association }
|
||||
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -92,6 +76,14 @@ module ActiveRecord
|
|||
# Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" would return the Money class and
|
||||
# "has_many :clients" would return the Client class.
|
||||
def klass() end
|
||||
|
||||
def class_name
|
||||
@class_name ||= name_to_class_name(name.id2name)
|
||||
end
|
||||
|
||||
def require_class
|
||||
require_association(class_name.underscore) if class_name
|
||||
end
|
||||
|
||||
def ==(other_aggregation)
|
||||
name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
|
||||
|
@ -102,7 +94,7 @@ module ActiveRecord
|
|||
# Holds all the meta-data about an aggregation as it was specified in the Active Record class.
|
||||
class AggregateReflection < MacroReflection #:nodoc:
|
||||
def klass
|
||||
Object.const_get(options[:class_name] || name_to_class_name(name.id2name))
|
||||
@klass ||= Object.const_get(class_name)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -114,22 +106,40 @@ module ActiveRecord
|
|||
# Holds all the meta-data about an association as it was specified in the Active Record class.
|
||||
class AssociationReflection < MacroReflection #:nodoc:
|
||||
def klass
|
||||
@klass ||= active_record.send(:compute_type, (name_to_class_name(name.id2name)))
|
||||
@klass ||= active_record.send(:compute_type, class_name)
|
||||
end
|
||||
|
||||
def table_name
|
||||
@table_name ||= klass.table_name
|
||||
end
|
||||
|
||||
def primary_key_name
|
||||
return @primary_key_name if @primary_key_name
|
||||
|
||||
case macro
|
||||
when :belongs_to
|
||||
@primary_key_name = options[:foreign_key] || class_name.foreign_key
|
||||
else
|
||||
@primary_key_name = options[:foreign_key] || active_record.name.foreign_key
|
||||
end
|
||||
end
|
||||
|
||||
def association_foreign_key
|
||||
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
|
||||
end
|
||||
|
||||
private
|
||||
def name_to_class_name(name)
|
||||
if name =~ /::/
|
||||
name
|
||||
else
|
||||
unless class_name = options[:class_name]
|
||||
if options[:class_name]
|
||||
class_name = options[:class_name]
|
||||
else
|
||||
class_name = name.to_s.camelize
|
||||
class_name = class_name.singularize if [:has_many, :has_and_belongs_to_many].include?(macro)
|
||||
class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
|
||||
end
|
||||
|
||||
active_record.send(:type_name_with_module, class_name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,14 +4,18 @@ require 'fixtures/tagging'
|
|||
require 'fixtures/post'
|
||||
require 'fixtures/comment'
|
||||
|
||||
class AssociationsInterfaceTest < Test::Unit::TestCase
|
||||
class AssociationsJoinModelTest < Test::Unit::TestCase
|
||||
fixtures :posts, :comments, :tags, :taggings
|
||||
|
||||
def test_post_having_a_single_tag_through_has_many
|
||||
def test_polymorphic_has_many
|
||||
assert_equal taggings(:welcome_general), posts(:welcome).taggings.first
|
||||
end
|
||||
|
||||
def test_post_having_a_single_tag_through_belongs_to
|
||||
def test_polymorphic_belongs_to
|
||||
assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable
|
||||
end
|
||||
|
||||
def test_polymorphic_has_many_going_through_join_model
|
||||
assert_equal tags(:general), posts(:welcome).tags.first
|
||||
end
|
||||
end
|
1
activerecord/test/fixtures/post.rb
vendored
1
activerecord/test/fixtures/post.rb
vendored
|
@ -21,6 +21,7 @@ class Post < ActiveRecord::Base
|
|||
has_and_belongs_to_many :special_categories, :join_table => "categories_posts"
|
||||
|
||||
has_many :taggings, :as => :taggable
|
||||
has_many :tags, :through => :taggings
|
||||
|
||||
def self.what_are_you
|
||||
'a post...'
|
||||
|
|
|
@ -60,10 +60,9 @@ class ReflectionTest < Test::Unit::TestCase
|
|||
:composed_of, :gps_location, { }, Customer
|
||||
)
|
||||
|
||||
assert_equal(
|
||||
[ reflection_for_address, reflection_for_balance, reflection_for_gps_location ],
|
||||
Customer.reflect_on_all_aggregations
|
||||
)
|
||||
assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
|
||||
assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
|
||||
assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)
|
||||
|
||||
assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
|
||||
|
||||
|
|
Loading…
Reference in a new issue