简体   繁体   English

ruby on rails-以相同的形式更新一个模型及其关联的多个记录

[英]ruby on rails - update multiple records of one model and its association in same form

I have a form that enables a user to update multiple alert rules records at the same time. 我有一个允许用户同时更新多个警报规则记录的表单。 But each alert rule record can have many notification emails. 但是每个警报规则记录可以具有许多通知电子邮件。 So I would like to be able to update alert rules and the associated notification emails within the same form. 因此,我希望能够在同一表格中更新警报规则和相关的通知电子邮件。

Models: 楷模:

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

In one of my controllers, I send an array of alerts to a form: 在一个控制器中,我向表单发送了一系列警报:

  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

Then in my form, I want to allow the user to update alert rules and the associated email notifications: 然后在我的表单中,我希望允许用户更新警报规则和相关的电子邮件通知:

    = 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'

When I submit to server, this is what I get: 当我提交到服务器时,这就是我得到的:

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"}

This shouldnt be. 这不应该。 As I show in the controller above, I only build one notification per alert, so why it sends all the notifications back as if they are all associated with the first alert, when I check the first alert only, is beyond me. 正如我在上面的控制器中显示的那样,每个警报只构建一个通知,因此为什么当我仅检查第一个警报时,将所有通知都发送回好像所有通知都与第一个警报关联一样。

update: even when I used create! 更新:即使我使用创建! instead of build to actually write the associated record, it still presented same problem: unable to get the alert id inside the name attribute of the notification_email input field 它仍然存在相同的问题:无法构建以实际编写关联记录,但是仍然存在相同的问题:无法在notification_email输入字段的name属性内获取警报ID。

Thanks for response 感谢您的回应

You're not going to like it. 您不会喜欢它。 But this is what I did to make it work... 但这是我所做的,以使其起作用...

Information based on this stackoverflow question and was heavily influenced by this pretty awesome answer I can up with this: 基于此stackoverflow问题的信息 ,并受此相当出色的答案的影响很大,我可以这样做:

models/alert_rules_set.rb 型号/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

Controller 控制者

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 形成

= 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'

Here's a link to a git branch where I got it working with another app and the commit compare . 这是git分支的链接,在这里我可以将其与另一个应用程序一起使用并进行commit compare

This is crazy! 这太疯狂了! What's going on? 这是怎么回事?

So historically ActionPack tended to be pretty tightly coupled to ActiveRecord . 因此,从历史上看, ActionPack往往与ActiveRecord紧密耦合。 This isn't a good idea for several reasons, so starting with Rails 3.0 ActiveModel was introduced. 出于多种原因,这不是一个好主意,因此从Rails 3.0 ActiveModel开始引入。 Unfortunately this is one of those cases that could probably be better. 不幸的是,这是可能会更好的情况之一。

ActiveModel 活动模型

NOTE: Some of this is changing, specifically with this Rails 4 commit 注意:有些情况正在改变,特别是在此Rails 4提交中

So basically, I'm implementing the bare minimum to make this case work. 因此,基本上,我正在实施最低要求以使此案例正常工作。 Apart from the extend and include directives, the only thing that is necessary to make any other ActiveModel class play nice in this case seems to be persisted? 除了extendinclude指令之外,在这种情况下,使任何其他ActiveModel类正常运行所需的唯一事情似乎仍然persisted? .

As for everything else... 至于其他一切...

attr_accessor :alert_rules
This is just holding the alert_rules data. 这只是保存alert_rules数据。 This is what fields_for is going to use to populate the fields with existing data. 这就是fields_for将用于使用现有数据填充字段的内容。

alert_rules_attributes=(attributes)
This is kinda special. 这有点特别。 This is what fields_for checks for when it wants to know when to render a collection or not. 这是fields_for检查何时要知道何时渲染集合的工具。 Without this you will just get a single nested field(ie. [alert_rule][notification_emails_attributes] ) instead of a collection of nested fields(ie. [alert_rule][notification_emails_attributes][1] ). 没有这个,您将只会得到一个嵌套字段(即[alert_rule][notification_emails_attributes] ),而不是嵌套字段的集合(即[alert_rule][notification_emails_attributes][1] )。

self.alert_rules_from(collection_set_hash)
This is the more confusing part. 这是更令人困惑的部分。 So basically when you submit some nested attributes they're actually in a Hash with an index key. 因此,基本上,当您提交一些嵌套属性时,它们实际上位于带有索引键的Hash

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

This method is a quick way to get rid of that and get usable data out ( This happens in ActiveRecord too ). 这种方法是一种摆脱这种情况并获取可用数据的快速方法( 这也发生在ActiveRecord中 )。 I figured it would probably be easier to manipulate the data than try to do some saving magic in the AlertRuleSet class (but you could). 我认为操作数据可能比尝试在AlertRuleSet类中进行一些保存魔术要容易得多(但是可以)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM