mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Allow delegated_type to be specified primary_key and foreign_key
Since delegated_type assumes that the foreign_key ends with `_id`, `singular_id` defined by it does not work when the foreign_key does not end with `id`. This commit fixes it by taking into account `primary_key` and `foreign_key` in the options.
This commit is contained in:
parent
9c120d7559
commit
941c5641b0
7 changed files with 85 additions and 6 deletions
|
@ -1,3 +1,12 @@
|
|||
* Allow delegated_type to be specified primary_key and foreign_key.
|
||||
|
||||
Since delegated_type assumes that the foreign_key ends with `_id`,
|
||||
`singular_id` defined by it does not work when the foreign_key does
|
||||
not end with `id`. This change fixes it by taking into account
|
||||
`primary_key` and `foreign_key` in the options.
|
||||
|
||||
*Ryota Egusa*
|
||||
|
||||
* Restore possibility of passing `false` to :polymorphic option of `belongs_to`.
|
||||
|
||||
Previously, passing `false` would trigger the option validation logic
|
||||
|
|
|
@ -156,8 +156,6 @@ module ActiveRecord
|
|||
# Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
|
||||
# Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
|
||||
#
|
||||
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
||||
#
|
||||
# You can also declare namespaced types:
|
||||
#
|
||||
# class Entry < ApplicationRecord
|
||||
|
@ -167,15 +165,38 @@ module ActiveRecord
|
|||
# Entry.access_notice_messages
|
||||
# entry.access_notice_message
|
||||
# entry.access_notice_message?
|
||||
#
|
||||
# === Options
|
||||
#
|
||||
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
||||
# The following options can be included to specialize the behavior of the delegated type convenience methods.
|
||||
#
|
||||
# [:foreign_key]
|
||||
# Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
|
||||
# +role+ with an "_id" suffix. So a class that defines a
|
||||
# <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
|
||||
# the default <tt>:foreign_key</tt>.
|
||||
# [:primary_key]
|
||||
# Specify the method that returns the primary key of associated object used for the convenience methods.
|
||||
# By default this is +id+.
|
||||
#
|
||||
# Option examples:
|
||||
# class Entry < ApplicationRecord
|
||||
# delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
|
||||
# end
|
||||
#
|
||||
# Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
|
||||
# Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
|
||||
def delegated_type(role, types:, **options)
|
||||
belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
|
||||
define_delegated_type_methods role, types: types
|
||||
define_delegated_type_methods role, types: types, options: options
|
||||
end
|
||||
|
||||
private
|
||||
def define_delegated_type_methods(role, types:)
|
||||
def define_delegated_type_methods(role, types:, options:)
|
||||
primary_key = options[:primary_key] || "id"
|
||||
role_type = "#{role}_type"
|
||||
role_id = "#{role}_id"
|
||||
role_id = options[:foreign_key] || "#{role}_id"
|
||||
|
||||
define_method "#{role}_class" do
|
||||
public_send("#{role}_type").constantize
|
||||
|
@ -200,7 +221,7 @@ module ActiveRecord
|
|||
public_send(role) if public_send(query)
|
||||
end
|
||||
|
||||
define_method "#{singular}_id" do
|
||||
define_method "#{singular}_#{primary_key}" do
|
||||
public_send(role_id) if public_send(query)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,9 @@ require "cases/helper"
|
|||
require "models/entry"
|
||||
require "models/message"
|
||||
require "models/comment"
|
||||
require "models/uuid_entry"
|
||||
require "models/uuid_message"
|
||||
require "models/uuid_comment"
|
||||
|
||||
class DelegatedTypeTest < ActiveRecord::TestCase
|
||||
fixtures :comments
|
||||
|
@ -11,6 +14,11 @@ class DelegatedTypeTest < ActiveRecord::TestCase
|
|||
setup do
|
||||
@entry_with_message = Entry.create! entryable: Message.new(subject: "Hello world!")
|
||||
@entry_with_comment = Entry.create! entryable: comments(:greetings)
|
||||
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
@uuid_entry_with_message = UuidEntry.create! uuid: SecureRandom.uuid, entryable: UuidMessage.new(uuid: SecureRandom.uuid, subject: "Hello world!")
|
||||
@uuid_entry_with_comment = UuidEntry.create! uuid: SecureRandom.uuid, entryable: UuidComment.new(uuid: SecureRandom.uuid, content: "comment")
|
||||
end
|
||||
end
|
||||
|
||||
test "delegated class" do
|
||||
|
@ -54,4 +62,14 @@ class DelegatedTypeTest < ActiveRecord::TestCase
|
|||
assert_equal @entry_with_comment.entryable_id, @entry_with_comment.comment_id
|
||||
assert_nil @entry_with_comment.message_id
|
||||
end
|
||||
|
||||
test "association uuid" do
|
||||
skip unless current_adapter?(:PostgreSQLAdapter)
|
||||
|
||||
assert_equal @uuid_entry_with_message.entryable_uuid, @uuid_entry_with_message.uuid_message_uuid
|
||||
assert_nil @uuid_entry_with_message.uuid_comment_uuid
|
||||
|
||||
assert_equal @uuid_entry_with_comment.entryable_uuid, @uuid_entry_with_comment.uuid_comment_uuid
|
||||
assert_nil @uuid_entry_with_comment.uuid_message_uuid
|
||||
end
|
||||
end
|
||||
|
|
5
activerecord/test/models/uuid_comment.rb
Normal file
5
activerecord/test/models/uuid_comment.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UuidComment < ActiveRecord::Base
|
||||
has_one :uuid_entry, as: :entryable
|
||||
end
|
5
activerecord/test/models/uuid_entry.rb
Normal file
5
activerecord/test/models/uuid_entry.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UuidEntry < ActiveRecord::Base
|
||||
delegated_type :entryable, types: %w[ UuidMessage UuidComment ], primary_key: :uuid, foreign_key: :entryable_uuid
|
||||
end
|
5
activerecord/test/models/uuid_message.rb
Normal file
5
activerecord/test/models/uuid_message.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UuidMessage < ActiveRecord::Base
|
||||
has_one :uuid_entry, as: :entryable
|
||||
end
|
|
@ -104,11 +104,27 @@ _SQL
|
|||
t.decimal :decimal_array_default, array: true, default: [1.23, 3.45]
|
||||
end
|
||||
|
||||
create_table :uuid_comments, force: true, id: false do |t|
|
||||
t.uuid :uuid, primary_key: true, **uuid_default
|
||||
t.string :content
|
||||
end
|
||||
|
||||
create_table :uuid_entries, force: true, id: false do |t|
|
||||
t.uuid :uuid, primary_key: true, **uuid_default
|
||||
t.string :entryable_type, null: false
|
||||
t.uuid :entryable_uuid, null: false
|
||||
end
|
||||
|
||||
create_table :uuid_items, force: true, id: false do |t|
|
||||
t.uuid :uuid, primary_key: true, **uuid_default
|
||||
t.string :title
|
||||
end
|
||||
|
||||
create_table :uuid_messages, force: true, id: false do |t|
|
||||
t.uuid :uuid, primary_key: true, **uuid_default
|
||||
t.string :subject
|
||||
end
|
||||
|
||||
if supports_partitioned_indexes?
|
||||
create_table(:measurements, id: false, force: true, options: "PARTITION BY LIST (city_id)") do |t|
|
||||
t.string :city_id, null: false
|
||||
|
|
Loading…
Reference in a new issue