提交 57b7532b 编写于 作者: D David Heinemeier Hansson

Work-in progress for providing better join model support and polymorphic associations

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3209 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 96c29ab8
require 'active_record/associations/association_proxy'
require 'active_record/associations/association_collection'
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_and_belongs_to_many_association'
......@@ -344,7 +345,7 @@ def has_many(association_id, options = {}, &extension)
:foreign_key, :class_name, :exclusively_dependent, :dependent,
:conditions, :order, :include, :finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove, :extend,
:group
:group, :as
)
options[:extend] = create_extension_module(association_id, extension) if block_given?
......@@ -516,15 +517,37 @@ def has_one(association_id, options = {})
# 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, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend)
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)
require_association_class(association_class_name)
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)
module_eval do
before_save <<-EOF
association = instance_variable_get("@#{association_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
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)
......@@ -532,12 +555,15 @@ def belongs_to(association_id, options = {})
module_eval do
before_save <<-EOF
association = instance_variable_get("@#{association_name}")
if not association.nil?
if !association.nil?
if association.new_record?
association.save(true)
association.send(:construct_sql)
end
self["#{association_class_primary_key_name}"] = association.id if association.updated?
if association.updated?
self["#{association_class_primary_key_name}"] = association.id
end
end
EOF
end
......@@ -558,6 +584,7 @@ def belongs_to(association_id, options = {})
deprecated_has_association_method(association_name)
deprecated_association_comparison_method(association_name, association_class_name)
end
end
# Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
# an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
......
......@@ -76,7 +76,6 @@ def extract_options_from_args!(args)
end
private
def method_missing(method, *args, &block)
load_target
@target.send(method, *args, &block)
......
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
......@@ -43,9 +42,6 @@ def updated?
@updated
end
protected
private
def find_target
if @options[:conditions]
......
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
else
@target = (AssociationProxy === obj ? obj.target : obj)
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
end
@updated = true
end
@loaded = true
return (@target.nil? ? nil : self)
end
private
def find_target
return nil if association_class.nil?
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
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 association_class
@owner[@options[:foreign_type]] ? @owner[@options[:foreign_type]].constantize : nil
end
end
end
end
......@@ -163,8 +163,16 @@ def target_obsolete?
end
def construct_sql
if @options[:finder_sql]
case
when @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}'"
@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 << " AND (#{interpolate_sql(@conditions)})" if @conditions
......@@ -176,8 +184,7 @@ def construct_sql
@options[:counter_sql] = @options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
@counter_sql = interpolate_sql(@options[:counter_sql])
else
@counter_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
@counter_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
@counter_sql = @finder_sql
end
end
end
......
......@@ -11,6 +11,11 @@ def test_drop_and_create_main_tables
assert true
end
def test_load_schema
eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb"))
assert true
end
def test_drop_and_create_courses_table
recreate Course, '2'
assert true
......
require 'abstract_unit'
require 'fixtures/tag'
require 'fixtures/tagging'
require 'fixtures/post'
require 'fixtures/comment'
class AssociationsInterfaceTest < Test::Unit::TestCase
fixtures :posts, :comments, :tags, :taggings
def test_post_having_a_single_tag_through_has_many
assert_equal taggings(:welcome_general), posts(:welcome).taggings.first
end
def test_post_having_a_single_tag_through_belongs_to
assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable
end
end
ActiveRecord::Schema.define do
create_table "taggings", :force => true do |t|
t.column "tag_id", :integer
t.column "taggable_type", :string
t.column "taggable_id", :integer
end
create_table "tags", :force => true do |t|
t.column "name", :string
end
end
\ No newline at end of file
......@@ -20,6 +20,8 @@ def find_most_recent
has_and_belongs_to_many :categories
has_and_belongs_to_many :special_categories, :join_table => "categories_posts"
has_many :taggings, :as => :taggable
def self.what_are_you
'a post...'
end
......
class Tag < ActiveRecord::Base
end
\ No newline at end of file
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :taggable, :polymorphic => true
end
\ No newline at end of file
welcome_general:
id: 1
tag_id: 1
taggable_id: 1
taggable_type: Post
general:
id: 1
name: General
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册