【问题标题】:Rails - Polymorphic associations failRails - 多态关联失败
【发布时间】:2012-12-08 18:31:18
【问题描述】:

这应该很容易解决,但我是 Rails 新手。目前正在研究the rails guide on associations,但不断遇到障碍。

我有一个可以向其他用户发送消息的用户模型。为此,所有线程都有一个对话模型,每个单独的消息都有一个对话项模型。

这些是现在的关联:

用户.rb

has_many :received_messages, as: :recipient, class_name: 'Conversation', foreign_key: :recipient_id
has_many :sent_messages, as: :sender, class_name: 'Conversation', foreign_key: :sender_id
has_many :received_messages, as: :recipient, class_name: 'Conversation_item', foreign_key: :recipient_id
has_many :sent_messages, as: :sender, class_name: 'Conversation_item', foreign_key: :sender_id

对话.rb

has_many :conversation_items, dependent: :destroy
belongs_to :sender, polymorphic: true
belongs_to :recipient, polymorphic: true

Conversation_item.rb

belongs_to :conversation
belongs_to :sender, polymorphic: true
belongs_to :recipient, polymorphic: true

我正在尝试通过他的对话项目访问用户,如下所示:

<%= conversation_item.sender.inspect %>

这将返回 nil - 属性 sender 似乎确实有效,但不知何故不包含任何内容。但是,对话项确实有一个 sender_id。

我在上面的关联中缺少什么?还是其他地方?

【问题讨论】:

    标签: ruby-on-rails activerecord associations has-many belongs-to


    【解决方案1】:

    在我看来,您不需要在这里使用多态关系。你应该可以这样做:

    class User < ActiveRecord::Base
      has_many :participations, class_name: 'Participant'
      has_many :conversations, through: :participations
      has_many :conversation_items, through: :conversations
    end
    
    class Participant < ActiveRecord::Base
      belongs_to :user
      belongs_to :conversation
    end 
    
    class Conversation < ActiveRecord::Base
      has_many :participants
      has_many :conversation_items
    end
    
    class ConversationItem < ActiveRecord::Base
      belongs_to :conversation
      belongs_to :sender, class_name: 'User', foreign_key: 'sender_id'
    end
    

    对我来说,这作为一个领域模型更有意义,其中对话有很多参与者。您可以将发件人附加到对话项目,并且对话中的每个参与者(不是发件人)都是隐含的收件人。对话有发送者和接收者是没有意义的,因为对话是双向或多向对话。

    多态关系在这里不起作用的原因是多态关系适用于表可以引用不同类型项目的情况。例如,假设您的 ConversationItem 有很多附件,并且附件也可以应用于帖子。你会有类似的东西:

    class ConversationItem < ActiveRecord::Base
      #...
      has_many :attachments, as: :attachable
      #...
    end
    
    class Post < ActiveRecord::Base
      has_many :attachments, as: :attachable
    end
    
    class Attachment < ActiveRecord::Base
      belongs_to :attachable, polymorphic: true
    end
    

    附件将包含字段attachable_idattachable_type。当您从对话项目中查询附件时,您会得到类似SELECT * FROM attachments where attachable_id = &lt;id of the conversation item&gt; and attachable_type = 'ConversationItem' 的查询。因此,多态关联使您能够将模型附加到许多不同类型的其他模型。

    在这种情况下,您可以看到拥有多态关系是没有意义的,除非您将拥有许多不同类型的模型来发送和接收对话项目,因为在您的 conversation_items 表上,您将拥有 @987654326 @、sender_idrecipient_typerecipient_id

    您的模型的另一个问题是您尝试使用不同的参数两次定义相同的关系。当您调用has_many :foo 时,Rails 会生成一堆不同的方法,例如foosfoos= 等等。如果你再次调用它,你只是用新参数重新定义了这些方法。

    跟进

    您不一定需要 Participant 模型。拥有它使您能够让多个用户参与,并且可以避免为同一模型创建多个关系。您可以使用has_and_belongs_to_many 关系实现相同的目的,但是如果需要,您将失去将额外属性附加到连接表的能力。

    例如,假设您希望允许人们退出对话而不删除他们曾经是参与者,使用加入模型,您可以添加一个布尔字段调用active,当有人退出时设置为false .使用 HABTM,您无法做到这一点。让参与者离开需要从连接表中完全删除该对。我通常更喜欢加入模型,比如这里的参与模型,因为你永远不知道你的模式会如何演变。

    也就是说,这里是 HABTM 的一个示例:

    class User < ActiveRecord::Base
      has_and_belongs_to_many :conversations
    end
    
    class Conversation < ActiveRecord::Base
      has_and_belongs_to_many :users
    end
    

    这里您没有连接模型,但您需要创建一个连接表才能使其工作。一个迁移的例子是:

    create_table :conversations_users, :id => false do |t|
      t.integer :conversation_id
      t.integer :user_id
    end
    
    add_index :conversations_users, [:conversation_id, :user_id], unique: true
    

    连接表的组成部分的名称按字母顺序排列,即conversations_users,而不是users_conversations

    进一步跟进

    如果您使用 Participant 表,外键将位于 participantsconversation_items

    participants
      user_id
      conversation_id
    
    conversation_items
      conversation_id
      sender_id
    

    【讨论】:

    • 谢谢!我还没有真正得到我需要参与者类的东西——它有什么属性?只是一个 user_id 和一个 conversation_id?
    • 所以基本上我会在会话和会话项中去掉参与者的 ID,而是使用带有 user_id 和会话 ID 的参与者模型?
    • 要回答您的第一个问题,是的,您将在 Participation 模型上使用 user_idconversation_id。对于第二个问题,是的,这就是你正在做的事情。它消除了为相同模型创建多个关系的需要。看看我的跟进是否有帮助。
    • 感谢您的跟进 - 我想我们已经接近解决方案了! :-) 我想我会采用参与者方法,因为它提供了很大的灵活性——但是这种语法似乎有一个错误:belongs_to :sender, class: 'User'——它给了我Unknown key: class。我需要改变什么?
    • 对不起,这是一个蹩脚的错字。现在可以了!非常感谢您的帮助和耐心!我很想投 100 票,但只能投 1 票。很久以来我得到的最佳答案!
    【解决方案2】:

    你模型的逻辑似乎很好。我的建议是控制器和视图结构。当您在 Rails 中创建关联时,嵌套资源的常用方法(在您的 routes.rb 中):

    资源 :conversations do 资源:conversation_items 结束

    那么你的 Conversation_items 控制器应该在 new 和 create 动作中有这样的东西:

    def new
      @conversation = Conversation.find(params[:conversation_id]
      @conversation_item = Conversation_item.new
    end
    
    def create
      @conversation = Conversation.find(params[:conversation_id]
      @conversation_item = @conversation.conversation_items.build(params[:conversation_items]
    end
    

    最后,在您的 _form.html.erb(同时提供创建和更新操作)中:

      <%= form_for [@conversation, @conversation_item] do |f| %>
    
         # your code here
    
      <%= end %>
    

    我在那里,你可以see here ...这是苦乐参半:)

    【讨论】:

    • 不,我不认为是这样。我有你描述的资源,我现在既不是指 new 也不是指 create 方法。只针对已经有对话的 index 方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-23
    相关资源
    最近更新 更多