【问题标题】:Double has_many attributes双 has_many 属性
【发布时间】:2017-01-24 10:20:38
【问题描述】:

我是 Rails 的初学者,无法找到解决问题的正确方法。

我有三个模型:对话、参与者、消息,它们具有以下属性:

对话:

module Messenger
  class Conversation <ActiveRecord::Base

    has_many :participants, :class_name => 'Messenger::Participant'

    def messages
      self.participants.messages.order(:created_at)
    end
  end
end

参与者:

module Messenger

  class Participant <ActiveRecord::Base

    has_many :messages, :class_name => 'Messenger::Message'

    belongs_to :conversation, :class_name => 'Messenger::Conversation'

  end
end

留言:

module Messenger

  class Message <ActiveRecord::Base

    default_scope {order(:created_at)}
    default_scope {where(deleted: false)}

    belongs_to :participant, :class_name => 'Messenger::Participant'

  end
end

我的麻烦是我正在尝试制作一个表单来创建包含第一条消息的对话。表格如下所示:

= form_for @conversation, url: messenger.conversations_create_path do |f|
  .row
    .col-md-12.no-padding
      .whitebg.padding15
        .form-group.user-info-block.required
          = f.label :title, t('trad'), class: 'control-label'
          = f.text_field :title, class: 'form-control'

        .form-group.user-info-block.required
          = f.label :model, t('trad'), class: 'control-label'
          = f.text_field :model, class: 'form-control'

        .form-group.user-info-block.required
          = f.label :model_id, t('trad'), class: 'control-label'
          = f.text_field :model_id, class: 'form-control'

        = fields_for @message, @conversation.participants.message do |m|
          = m.label :content, t('trad'), class: 'control-label'
          = m.text_area :content, class:'form-control'

  .user-info-block.action-buttons
    = f.submit t('trad'), :class => 'btn btn-primary pull-right'

我尝试了很多方法来简化这个表单,但是我遇到了一些我不知道如何正确使用 rails 来解决的问题。

我尝试使用Field_for在我的对话表单中包含一条消息,但由于我的数据库中没有保存任何内容,但似乎我无法将消息链接到不存在的参与者。

所以基本上我希望我的第一个表单在验证后创建一个对话,将当前用户链接到该对话并将消息链接到第一个用户,但我认为有一些方法可以使用框架来做到这一点,我会不喜欢手动操作。

实现这一目标的正确方法是什么?我什至处于良好的轨道上还是应该改变一些东西或添加一些东西?

编辑:为了更容易理解,参与者得到了一个 user_id 和一个 conversation_id,这意味着这是一个关系表。我无法调整模型的属性以使其更容易,因为出于安全原因我必须保持这种方式。

【问题讨论】:

  • 您可能希望通过 Conversation 中的消息关联来查看 has_many,而不是您的手动方法。

标签: ruby-on-rails ruby-on-rails-4 activerecord


【解决方案1】:

消息需要直接belong_to对话,因为当参与者有多个对话时,您需要消除歧义。

完成此操作后,您可以使用

在控制器中构建对话的默认消息
@conversation.messages.build(participant: @conversation.participants.first)

这很罗嗦,所以你可以添加几个模型方法来减少控制器调用

@conversation.build_default_message

在这种情况下,您想要创建一个对话,但它还需要创建一个带有用户输入的消息。所以 Conversation 需要代表 Message 接受属性。你可以使用accepts_nested_attributes_for

class Conversation
  accepts_nested_attributes_for :messages
end

这将允许您使用

创建包含 1 个或多个关联消息的对话
Conversation.create(
  ..., 
  messages_attributes: [
    { participant_id: 1, content: 'question' } 
  ]
)

【讨论】:

  • 我们已经这样定义了我们的模型,并且出于安全原因不能在转换中直接链接消息,这就是为什么我需要保留这样的模型
【解决方案2】:

首先,为了让您的表单使用fields_for 表单助手接受嵌套属性,您需要在Conversation 模型上指定accepts_nested_attributes_for

module Messenger
  class Conversation <ActiveRecord::Base

    has_many :participants, :class_name => 'Messenger::Participant'

    # Required for form helper
    accepts_nested_attributes_for :participants

    [...]

由于您想从同一个表单中同时保存 Participant 及其 Message,因此您需要在您的 Participant 模型上添加第二个 accepts_nested_attributes_for

module Messenger

  class Participant <ActiveRecord::Base

    has_many :messages, :class_name => 'Messenger::Message'

    # Required for form helper
    accepts_nested_attributes_for :messages

    belongs_to :conversation, :class_name => 'Messenger::Conversation'

  end
end

下一步,在你的控制器中,由于这是一个新的Conversation,一开始没有任何Participants,你需要build一个关联的Participant(大概基于current_user),以及这个新Participant的关联Message

def new
  @conversation.participants.build(user: current_user).messages.build
end

最后在你的视图中,在form_for @conversation do |f|f.fields_for :participants do |p|p.fields_for :messages do |m|三个嵌套块中指定属性字段:

= form_for @conversation, url: messenger.conversations_create_path do |f|
  [...]
  = f.fields_for :participants do |p|
    = p.fields_for :messages do |m|
      = m.label :content, t('trad'), class: 'control-label'
      = m.text_area :content, class:'form-control'

  .user-info-block.action-buttons
    = f.submit t('trad'), :class => 'btn btn-primary pull-right'

附注:Conversation 中的(未正确实现的)messages 方法应替换为简单的has_many :through 关系:

has_many :messages, through: :participants

【讨论】:

  • 嗯,谢谢你一百万次,这就是我一直在寻找的结果,而且 through 关键字确实对我的代码有很大的改进。工作完美,接受!
【解决方案3】:

首先,我认为,你这个方法有错误:

def messages
  self.participants.messages.order(:created_at)
end

自从Conversationhas_many :participants

self.participants 将返回一个数组,而不是单个 Participant 活动记录对象。所以你不能直接在数组上调用messages。您需要迭代该数组并在每个对象上调用 messages

使用嵌套形式和fields_for 方法和accepts_nested_attributes_for(您可以从 SO 或文档中找到如何编写这些)并发布代码代码并错误您得到什么。那么有人可能会帮助你。

并使用fields_for

由于您无法将消息直接链接到 Conversation 并且您需要一个消息字段,因此您需要将 @message 链接到任何参与者。您可以从current_user 或第一个用户(即User.first)为@conversation 构建一个Participant,然后为此Participant 构建一个@message

【讨论】:

    猜你喜欢
    • 2012-03-10
    • 2013-08-14
    • 1970-01-01
    • 2012-07-27
    • 2011-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多