【问题标题】:ruby on rails - update multiple records of one model and its association in same formruby on rails - 以相同的形式更新一个模型的多个记录及其关联
【发布时间】:2012-03-19 21:30:59
【问题描述】:

我有一个表单可以让用户同时更新多个警报规则记录。但是每个警报规则记录可以有许多通知电子邮件。所以我希望能够在同一个表单中更新警报规则和相关的通知电子邮件。

型号:

class AlertRule < ActiveRecord::Base

    has_many :notification_emails, :dependent => :destroy
    accepts_nested_attributes_for :notification_emails, :reject_if => lambda { |notification| notification[:email].blank? }
end

class NotificationEmail < ActiveRecord::Base
  belongs_to :alert_rule
end

在我的一个控制器中,我将一组警报发送到一个表单:

  def alerts_config

      //more code
      @alert_rules = alert_rules.flatten
      @alert_rules.each { |a| a.notification_emails.build }

      render :partial => 'home/alerts_config', :layout => false
  else

然后在我的表单中,我想允许用户更新警报规则和相关的电子邮件通知:

    = form_for @alert_rules, :url => '/home/save_alerts_config/' + @unit.id.to_s, :remote => true, :class => 'ajaxForm' do |f|
  %table.scrollTable{:cellspacing => "0", :width => "740px", :style => "border-collapse: collapse; border-spacing: 0;"}
    %thead.fixedHeader
      %tr
        %th Alerts
        %th Enable
        %th Primary Email
        %th Notification Emails
        %th
    %tbody.scrollContent
      - for rule in @alert_rules
        %tr
          %td= rule.alert_code.name
          %td= check_box_tag "enabled_ids[]", rule.id
          %td= f.text_field :email, :value => rule.email, :index => rule.id
          %td.fields
            = f.fields_for :notification_emails, rule.notification_emails do |notification_builder|
              = notification_builder.text_field :email
              = notification_builder.hidden_field :_destroy              
              = link_to_function 'Remove Notification', 'remove_notifications(this)'
  .save_panel
    = submit_tag "Save", :class => 'submit myButton'

当我提交到服务器时,这是我得到的:

Started POST "/home/save_alerts_config/6243" for 127.0.0.1 at 2012-03-20 17:49:06 -0400
  Processing by HomeController#save_alerts_config as JS
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"NPwuKuWippYjm2tJcfQI+/x9oEBwcR2rxcfpZMTO/Qo=", "enabled_ids"=>["51"], "alert_rule"=>{"51"=>{"email"=>"hythy"}, "notification_emails_attributes"=>{"0"=>{"email"=>"rtyrytry", "_destroy"=>"false"}, "1"=>{"email"=>"", "_destroy"=>"false"}, "2"=>{"email"=>"", "_destroy"=>"false"}, "3"=>{"email"=>"", "_destroy"=>"false"}, "4"=>{"email"=>"", "_destroy"=>"false"}, "5"=>{"email"=>"", "_destroy"=>"false"}, "6"=>{"email"=>"", "_destroy"=>"false"}, "7"=>{"email"=>"", "_destroy"=>"false"}, "8"=>{"email"=>"", "_destroy"=>"false"}, "9"=>{"email"=>"", "_destroy"=>"false"}, "10"=>{"email"=>"", "_destroy"=>"false"}, "11"=>{"email"=>"", "_destroy"=>"false"}, "12"=>{"email"=>"", "_destroy"=>"false"}, "13"=>{"email"=>"", "_destroy"=>"false"}, "14"=>{"email"=>"", "_destroy"=>"false"}, "15"=>{"email"=>"", "_destroy"=>"false"}, "16"=>{"email"=>"", "_destroy"=>"false"}, "17"=>{"email"=>"", "_destroy"=>"false"}, "18"=>{"email"=>"", "_destroy"=>"false"}, "19"=>{"email"=>"", "_destroy"=>"false"}, "20"=>{"email"=>"", "_destroy"=>"false"}, "21"=>{"email"=>"", "_destroy"=>"false"}, "22"=>{"email"=>"", "_destroy"=>"false"}, "23"=>{"email"=>"", "_destroy"=>"false"}, "24"=>{"email"=>"", "_destroy"=>"false"}, "25"=>{"email"=>"", "_destroy"=>"false"}, "26"=>{"email"=>"", "_destroy"=>"false"}, "27"=>{"email"=>"", "_destroy"=>"false"}, "28"=>{"email"=>"", "_destroy"=>"false"}, "29"=>{"email"=>"", "_destroy"=>"false"}, "30"=>{"email"=>"", "_destroy"=>"false"}, "31"=>{"email"=>"", "_destroy"=>"false"}, "32"=>{"email"=>"", "_destroy"=>"false"}, "33"=>{"email"=>"", "_destroy"=>"false"}, "34"=>{"email"=>"", "_destroy"=>"false"}, "35"=>{"email"=>"", "_destroy"=>"false"}, "36"=>{"email"=>"", "_destroy"=>"false"}, "37"=>{"email"=>"", "_destroy"=>"false"}, "38"=>{"email"=>"", "_destroy"=>"false"}, "39"=>{"email"=>"", "_destroy"=>"false"}, "40"=>{"email"=>"", "_destroy"=>"false"}}, "52"=>{"email"=>"yutu"}, "53"=>{"email"=>"ytuytu"}, "54"=>{"email"=>""}, "55"=>{"email"=>""}, "56"=>{"email"=>""}, "57"=>{"email"=>""}, "58"=>{"email"=>""}, "59"=>{"email"=>""}, "60"=>{"email"=>""}, "61"=>{"email"=>""}, "62"=>{"email"=>""}, "63"=>{"email"=>""}, "64"=>{"email"=>""}, "65"=>{"email"=>""}, "66"=>{"email"=>""}, "67"=>{"email"=>""}, "68"=>{"email"=>""}, "69"=>{"email"=>""}, "70"=>{"email"=>""}, "71"=>{"email"=>""}, "72"=>{"email"=>""}, "73"=>{"email"=>""}, "74"=>{"email"=>""}, "75"=>{"email"=>""}, "76"=>{"email"=>""}, "77"=>{"email"=>""}, "78"=>{"email"=>""}, "79"=>{"email"=>""}, "80"=>{"email"=>""}, "81"=>{"email"=>""}, "82"=>{"email"=>""}, "83"=>{"email"=>""}, "84"=>{"email"=>""}, "85"=>{"email"=>""}, "86"=>{"email"=>""}, "87"=>{"email"=>""}, "88"=>{"email"=>""}, "89"=>{"email"=>""}, "90"=>{"email"=>""}, "91"=>{"email"=>""}}, "commit"=>"Save", "id"=>"6243"}

这不应该。正如我在上面的控制器中显示的那样,我只为每个警报构建一个通知,所以当我只检查第一个警报时,为什么它会将所有通知都发送回来,就好像它们都与第一个警报相关联一样。

更新: 即使我使用了创建!而不是构建来实际写入关联记录,它仍然存在同样的问题:无法在 notification_email 输入字段的 name 属性中获取警报 id

感谢回复

【问题讨论】:

  • fields_for 需要一个集合对象。当你打电话给all 时,你是在给它一个Array。如果您将all 挂断,会发生什么情况?所以它看起来像, rule.notification_emails do
  • @Azolo 我最初尝试这样做,但它给了我相同的错误消息“ActionView::Template::Error (undefined method `email' for []:Array):”
  • 我认为这并不容易,我想知道你会得到什么。 = notification_builder.object.to_s 带给你什么?
  • @Azolo 当我添加两个参数时:f.fields_for :notification_emails, rule.notification_emails notification_builder.object.to_s 将返回 [#] 并且当我不传递第二个参数时:f.fields_for :notification_emails,notification_builder.object.to_s 将返回一个空字符串。当我使用 .all 时,它给了我一个空数组: []
  • 也许这与电子邮件通知还不是真正的关联(因为我使用了 build)n 的事实有关,因此接受的嵌套属性_for 不会工作:1.9.2p290 :001 > AlertRule.first.notification_emails。 build => # 1.9.2p290 :002 > AlertRule.first.notification_emails => []

标签: ruby-on-rails forms model-associations


【解决方案1】:

你不会喜欢的。但这就是我为使它工作所做的工作......

基于此stackoverflow question 的信息并受到this pretty awesome answer 的严重影响我可以解决这个问题:

models/alert_rules_set.rb

# NOTICE: I'm not inheriting from ActiveRecord::Base
class AlertRulesSet
  extend ActiveModel::Naming
  include ActiveModel::Conversion

  attr_accessor :alert_rules

  def alert_rules_attributes=(attributes)
    # I'm tricking "fields_for" here.
    # This should never actually be called
    raise
  end

  # Strip fields_for indexes and return an array
  def self.alert_rules_from(collection_set_hash)
    collection_set_hash[:alert_rules_attributes].values
  end

  def persisted?
    false
  end
end

控制器

def alerts_config
  //more code
  @alert_rules_set = AlertRulesSet.new
  @alert_rules_set = AlertRules.all # Or Whatever
  @alert_rules_set.alert_rules.each { |a| a.notification_emails.build }

  render :partial => 'home/alerts_config', :layout => false
end

def update_alerts_config
  @alert_rules = AlertRulesSet.alert_rules_from(params[:alert_rules_set])

  # Save Logic here
end

表格

= form_for @alert_rules_set, :url => '/home/save_alerts_config/' + @unit.id.to_s, :remote => true, :class => 'ajaxForm' do |f|
  %table.scrollTable{:cellspacing => "0", :width => "740px", :style => "border-collapse: collapse; border-spacing: 0;"}
  %thead.fixedHeader
    %tr
      %th Alerts
      %th Enable
      %th Primary Email
      %th Notification Emails
      %th
  %tbody.scrollContent
    = f.fields_for :alert_rules do |alert_rules_builder|
      %tr
        %td= rule.alert_code.name
        %td= check_box_tag "enabled_ids[]", rule.id
        %td= alert_rules_builder.text_field :email, 
                         :value => rule.email, :index => rule.id
        %td.fields
          = alert_rules_builder.fields_for :notification_emails do |notification_builder|
          = notification_builder.text_field :email
          = notification_builder.hidden_field :_destroy              
          = link_to_function 'Remove Notification',
                'remove_notifications(this)'.save_panel
= submit_tag "Save", :class => 'submit myButton'

这是一个指向 git branch where I got it working 与另一个应用程序和 commit compare 的链接。

这太疯狂了!发生了什么事?

所以从历史上看,ActionPack 往往与ActiveRecord 紧密耦合。由于几个原因,这不是一个好主意,因此从 Rails 3.0 开始引入ActiveModel。不幸的是,这是可能会更好的情况之一。

活动模型

注意:其中一些正在发生变化,特别是 this Rails 4 commit

所以基本上,我正在实施最低限度的工作以使这个案例有效。除了 extendinclude 指令之外,在这种情况下,使任何其他 ActiveModel 类正常运行所需的唯一东西似乎是 persisted?

至于其他的……

attr_accessor :alert_rules
这只是保存alert_rules 数据。这就是fields_for 将使用现有数据填充字段的内容。

alert_rules_attributes=(attributes)
这有点特别。这就是fields_for 在想知道何时渲染集合时检查的内容。没有这个,你将只得到一个嵌套字段(即[alert_rule][notification_emails_attributes])而不是嵌套字段的集合(即[alert_rule][notification_emails_attributes][1])。

self.alert_rules_from(collection_set_hash)
这是更令人困惑的部分。所以基本上,当您提交一些嵌套属性时,它们实际上是在带有索引键的Hash 中。

nested_model_attributes = {
    "0"=>{"attribute"=>"This", "id"=>"0"},
    "1"=>{"attribute"=>"That"}
}

此方法是摆脱这种情况并获取可用数据的快速方法 (This happens in ActiveRecord too)。我认为操作数据可能比尝试在 AlertRuleSet 类中做一些保存魔法更容易(但你可以)。

【讨论】:

  • 看起来不错。能否再解释一下 self.users_from 的用途,谢谢。
  • 对不起!昨晚很晚,它实际上应该是 alert_rules_from (针对您的具体情况),但我想我忘了更改它。但是,是的,我会更改它并解释它存在的原因。
  • 研究起来很有趣,但我觉得我并没有解释一些不像我想象的那么基本的重要信息。
猜你喜欢
  • 1970-01-01
  • 2013-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-08
  • 2012-03-09
  • 1970-01-01
相关资源
最近更新 更多