Ruby on rails - 两次引用相同的模型?

Dan*_*Dan 49 ruby-on-rails relationship rails-activerecord

是否可以activerecord通过generate scaffold命令在模型中建立双重关系?

例如,如果我有User模型和PrivateMessage模型,则pm表需要跟踪senderrecipient.

显然,对于单一的关系我会这样做:

ruby script/generate scaffold pm title:string content:string user:references
Run Code Online (Sandbox Code Playgroud)

是否有类似的方式来建立两个关系?

另外,有没有为关系设置别名?

所以不要说:

@message.user
Run Code Online (Sandbox Code Playgroud)

您可以使用以下内容:

@message.sender 要么 @message.recipient

任何建议将不胜感激.

谢谢.

Ric*_*nes 120

这是对这个问题的完整答案,如果访问这个问题的人是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
Run Code Online (Sandbox Code Playgroud)

在这里,您指定此表中有两列将被称为:sender和:recipient,它们包含对另一个表的引用.Rails实际上会为您创建名为"sender_id"和"recipient_id"的列.在我们的例子中,他们将每个引用Users表中的行,但我们在模型中指定,而不是在迁移中.

楷模

class PrivateMessage < ActiveRecord::Base
  belongs_to :sender, :class_name => 'User'
  belongs_to :recipient, :class_name => 'User'
end
Run Code Online (Sandbox Code Playgroud)

在这里,您要在PrivateMessage模型上创建一个名为:sender的属性,然后指定此属性与User类相关.看到"belongs_to:sender"的Rails将在数据库中查找名为"sender_id"的列,我们在上面定义了该列,并使用它来存储外键.然后,你正在为收件人做同样的事情.

这将允许您通过PrivateMessage模型的实例访问User模型的两个实例的Sender和Recipient,如下所示:

@private_message.sender.name
@private_message.recipient.email
Run Code Online (Sandbox Code Playgroud)

这是您的用户模型:

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
Run Code Online (Sandbox Code Playgroud)

在这里,您将在用户模型上创建一个名为:sent_private_messages的属性,指定此属性与PrivateMessage模型相关,并且PrivateMessage模型上与此属性相关的外键称为"sender_id".然后,您正在为收到的私人消息做同样的事情.

这允许您通过执行以下操作来获取所有用户发送或接收的私人消息:

@user.sent_private_messages
@user.received_private_messages
Run Code Online (Sandbox Code Playgroud)

执行其中任何一项都将返回PrivateMessage模型的实例数组.

....

  • 在 Rails 5 及更高版本中,您可以将 `foreign_key` 语句放入 `create_table` 块中,如下所示: `t.references :sender,foreign_key: { to_table: 'users' }` (8认同)
  • `PrivateMessage`模型的`class_name`键应该是我自己无法编辑的符号,因为编辑必须至少为6个字符. (2认同)
  • 优秀,非常全面的答案。Rails 5 有一个更新,我认为它可以帮助改进您的答案——确保将外键约束添加到数据库中。如果您想用它更新您的答案,我已经在下面的答案中包含了对迁移的编辑。 (2认同)

Veg*_*ger 56

将其添加到您的模型中

has_one :sender, :class_name => "User"
has_one :recipient, :class_name => "User"
Run Code Online (Sandbox Code Playgroud)

您可以调用@message.sender并且@message.recipient都引用User模型.

而不是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
Run Code Online (Sandbox Code Playgroud)

我希望这有助于你......


bbe*_*ort 9

上述答案虽然很好,但不会在数据库中创建外键约束,而只是创建索引和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
Run Code Online (Sandbox Code Playgroud)

这将确保该指数上取得成功的创建sender_idrecipient_id以及在数据库中的外键约束你使用.

  • 这是 Rails 5 的正确解决方案 (2认同)