Add foreign_type option for polymorphic has_one and has_many.

To be possible to use a custom column name to save/read the polymorphic
associated type in a has_many or has_one polymorphic association, now users
can use the option :foreign_type to inform in what column the associated object
type will be saved.
上级 7daeb98c
* Add `foreign_type` option to `has_one` and `has_many` association macros.
This option enables to define the column name of associated object's type for polymorphic associations.
*Ulisses Almeida, Kassio Borges*
* Remove deprecated behavior allowing nested arrays to be passed as query
values.
......
......@@ -1176,6 +1176,12 @@ module ClassMethods
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
# association will use "person_id" as the default <tt>:foreign_key</tt>.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the polymorphic association
# specified on "as" option with a "_type" suffix. So a class that defines a
# <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the
# default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the name of the column to use as the primary key for the association. By default this is +id+.
# [:dependent]
......@@ -1323,6 +1329,12 @@ def has_many(name, scope = nil, options = {}, &extension)
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
# will use "person_id" as the default <tt>:foreign_key</tt>.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the polymorphic association
# specified on "as" option with a "_type" suffix. So a class that defines a
# <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the
# default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:as]
......
......@@ -5,7 +5,7 @@ def macro
end
def valid_options
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
end
def self.valid_dependent_options
......
......@@ -5,7 +5,7 @@ def macro
end
def valid_options
valid = super + [:as]
valid = super + [:as, :foreign_type]
valid += [:through, :source, :source_type] if options[:through]
valid
end
......
......@@ -277,7 +277,7 @@ def compute_class(name)
def initialize(name, scope, options, active_record)
super
@automatic_inverse_of = nil
@type = options[:as] && "#{options[:as]}_type"
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
@foreign_type = options[:foreign_type] || "#{name}_type"
@constructable = calculate_constructable(macro, options)
@association_scope_cache = {}
......
......@@ -7,6 +7,7 @@
require 'models/topic'
require 'models/reply'
require 'models/category'
require 'models/image'
require 'models/post'
require 'models/author'
require 'models/essay'
......@@ -1783,6 +1784,15 @@ def test_abstract_class_with_polymorphic_has_many
assert_equal [tagging], post.taggings
end
def test_with_polymorphic_has_many_with_custom_columns_name
post = Post.create! :title => 'foo', :body => 'bar'
image = Image.create!
post.images << image
assert_equal [image], post.images
end
def test_build_with_polymorphic_has_many_does_not_allow_to_override_type_and_id
welcome = posts(:welcome)
tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange')
......
......@@ -8,6 +8,7 @@
require 'models/car'
require 'models/bulb'
require 'models/author'
require 'models/image'
require 'models/post'
class HasOneAssociationsTest < ActiveRecord::TestCase
......@@ -573,6 +574,16 @@ def test_has_one_relationship_cannot_have_a_counter_cache
end
end
def test_with_polymorphic_has_one_with_custom_columns_name
post = Post.create! :title => 'foo', :body => 'bar'
image = Image.create!
post.main_image = image
post.reload
assert_equal image, post.main_image
end
test 'dangerous association name raises ArgumentError' do
[:errors, 'errors', :save, 'save'].each do |name|
assert_raises(ArgumentError, "Association #{name} should not be allowed") do
......
class Image < ActiveRecord::Base
belongs_to :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class
end
......@@ -128,6 +128,9 @@ def add_joins_and_select
has_many :taggings_using_author_id, :primary_key => :author_id, :as => :taggable, :class_name => 'Tagging'
has_many :tags_using_author_id, :through => :taggings_using_author_id, :source => :tag
has_many :images, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class
has_one :main_image, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class, :class_name => 'Image'
has_many :standard_categorizations, :class_name => 'Categorization', :foreign_key => :post_id
has_many :author_using_custom_pk, :through => :standard_categorizations
has_many :authors_using_custom_pk, :through => :standard_categorizations
......
......@@ -595,6 +595,11 @@ def except(adapter_names_to_exclude)
t.string :title, null: false
end
create_table :images, force: true do |t|
t.integer :imageable_identifier
t.string :imageable_class
end
create_table :price_estimates, force: true do |t|
t.string :estimate_of_type
t.integer :estimate_of_id
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册