Ruby on Rails - 如何在同一模型中引用两次?

60

通过generate scaffold命令,在activerecord模型中设置双重关系是否可行?例如,如果我有一个User模型和一个PrivateMessage模型,那么private_messages表需要跟踪senderrecipient。显然,对于单一关系,我只需执行以下操作:

ruby script/generate scaffold pm title:string content:string user:references

有没有类似的方法来设置两个关系?

另外,是否有任何方法可以为这些关系设置别名?

那么,而不是说:

@message.user
你可以使用类似以下的方法: @message.sender 或者 @message.recipient 非常感谢任何建议。
谢谢。
4个回答

139

如果访问这个问题的人是Ruby on Rails的新手,并且在将所有东西组合在一起时遇到了麻烦(就像我第一次调查时那样),那么下面是完整的答案。

解决方案的某些部分发生在您的迁移中,另一些部分发生在您的模型中:

迁移

class CreatePrivateMessages < ActiveRecord::Migration
  def change
    create_table :private_messages do |t|
      t.references :sender
      t.references :recipient
    end
    # Rails 5+ only: add foreign keys
    add_foreign_key :private_messages, :users, column: :sender_id, primary_key: :id
    add_foreign_key :private_messages, :users, column: :recipient_id, primary_key: :id
  end
end

这里您在指定一个包含两列的表格,这两列将被称为“发件人”和“收件人”,并且它们持有对另一个表格的引用。Rails将会自动为您创建名为"sender_id"和"recipient_id"的列。在我们的情况下,它们将分别引用Users表中的行,但我们是在models中指定这些内容而不是在migrations中。

Models

class PrivateMessage < ActiveRecord::Base
  belongs_to :sender, :class_name => 'User'
  belongs_to :recipient, :class_name => 'User'
end

在PrivateMessage模型上创建一个名为:sender的属性,然后指定该属性与User类相关联。Rails会查找我们之前定义的名为"sender_id"的数据库列,并使用它来存储外键。对于接收者也是同样的操作。
这将允许您通过PrivateMessage模型的实例访问Sender和Recipient,它们都是User模型的实例,如下所示:
@private_message.sender.name
@private_message.recipient.email

以下是您的用户模型:

class User < ActiveRecord::Base
  has_many :sent_private_messages, :class_name => 'PrivateMessage', :foreign_key => 'sender_id'
  has_many :received_private_messages, :class_name => 'PrivateMessage', :foreign_key => 'recipient_id'
end

在此,您正在创建一个名为“sent_private_messages”的User Model属性,指定该属性与PrivateMessage Model相关,并且将PrivateMessage模型上与此属性相关的外键称为'sender_id'。然后,您对接收到的私人消息做同样的事情。
这使您可以通过执行以下操作获取用户发送或接收的所有私人消息:
@user.sent_private_messages
@user.received_private_messages

执行其中任一操作都将返回PrivateMessage模型的实例数组。
...

2
PrivateMessage 模型的 class_name 键应该是符号。我无法自行编辑此内容,因为编辑必须至少包含 6 个字符。 - Tim Fletcher
@TimFletcher 这个页面上的所有示例都使用字符串,而不是符号:http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to。也许`to_s`消息是发送给传递的符号(这只是猜测)? - Richard Jones
2
非常全面的答案,非常出色。Rails 5有一个更新,我认为可以帮助改进你的答案——确保外键约束被添加到数据库中。如果您想使用它更新您的答案,我在下面的答案中包含了对迁移的编辑。 - bbengfort
1
作为额外的说明,当我尝试这样做时,我遇到了“关系不存在”的错误 - 在添加外键之前,两个表都必须已经存在。在我的情况下,我不得不在创建两个表之后创建一个单独的迁移来添加外键。 - Alex E
8
在Rails 5及以上版本中,你可以将foreign_key语句放入create_table块中,像这样: t.references :sender, foreign_key: { to_table: 'users' } - Toby 1 Kenobi

65
将此添加到您的模型中。
belongs_to :sender, :class_name => "User"
belongs_to :recipient, :class_name => "User"

你可以调用@message.sender@message.recipient, 二者都指向用户模型。

在生成命令中,需要将user:references替换为 sender:referencesrecipient:references


18

嗨,要在你的两个模型中建立双向关系,请按照以下方法操作:

class Message < ActiveRecord::Base

 belongs_to     :sender,
                :class_name => "User",
                :foreign_key  => "sender_id"

 belongs_to     :recipient,
                :class_name => "User",
                :foreign_key  => "recipient_id" 
end

class User < ActiveRecord::Base

  has_many      :sent, 
                :class_name => "Message",
                :foreign_key  => "sent_id"

  has_many      :received, 
                :class_name => "Message", 
                :foreign_key  => "received_id"
end

我希望这可以帮到你...


11

上述答案虽然很好,但并没有在数据库中创建外键约束,而仅仅是创建了索引和bigint列。为确保强制执行外键约束,请在迁移中添加以下内容:

class CreatePrivateMessages < ActiveRecord::Migration[5.1]
    def change
        create_table :private_messages do |t|
          t.references :sender
          t.references :recipient
        end

        add_foreign_key :private_messages, :users, column: :sender_id, primary_key: :id
        add_foreign_key :private_messages, :users, column: :recipient_id, primary_key: :id
    end
end

这将确保在使用的数据库中,sender_idrecipient_id上创建索引以及外键约束。

2
这是Rails 5的适当解决方案。 - Jacka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接