无法使用具有 strong_parameters 和嵌套属性的 POST 更新或创建记录

Unable to update or create records using POST with strong_parameters and nested attributes

我不明白为什么我的数据库记录没有更新,或者没有创建新记录,就此而言,当我从表单中 POST 时。

我能够手动填充数据库并创建关系:

contact = Contact.first
command = Command.find(3)
contact.host_notification_commands << command

而且我还能够将此数据信息加载到我的表单中。我想不通的是如何更新或添加新记录。到目前为止我尝试过的一切都失败了。

我有 3 个模型 - CommandContact 并加入了 CommandsContactcommands_contacts join table 正在保留一个额外的属性 :notification_type,可以将其设置为 hostservice,我的 Contact 模型有 2 个额外的关系设置,我可以访问 :host_notification_commands:service_notification_commands。这使我能够执行 Contact.fist.host_notification_commandsContact.find(3).service_notification_commands.

之类的操作

似乎没有 UPDATEINSERT 查询从控制器启动,当我正确 POST 并且我不知道如何调试它时。

型号

class Command < ActiveRecord::Base

  has_many :commands_contacts
  has_many :contacts, :through => :commands_contacts

end

class Contact < ActiveRecord::Base

    has_many :commands_contacts
    has_many :commands, :through => :commands_contacts

    has_many :host_notification_commands, -> { where commands_contacts: { :notification_type => 'host' } },
            :through => :commands_contacts,
            :class_name => 'Command', 
            :source => :command

    has_many :service_notification_commands, -> { where commands_contacts: { notification_type: 'service' } },
            :through => :commands_contacts,
            :class_name => 'Command', 
            :source => :command

    accepts_nested_attributes_for :commands, :host_notification_commands, :service_notification_commands

end

class CommandsContact < ActiveRecord::Base

    belongs_to :command
    belongs_to :contact

    accepts_nested_attributes_for :command

end

然后一切都崩溃了。

控制器

由于我使用的是 accepts_nested_attributes_for,因此我必须将 _attributes 附加到我的嵌套对象的名称 - :host_notification_commands:service_notification_commands。我将更改我的表单以这种方式提交,但简单的重新分配只是为了举例。

def update
    contact = Contact.find_by_id(params[:id])
    contact.update(safe_params)
end    

private
def safe_params

    params[:contact][:host_notification_commands_attributes] = params[:contact][:host_notification_commands]
    params[:contact][:service_notification_commands_attributes] = params[:contact][:service_notification_commands]

    params.require(:contact)
        .permit(:contact_name, :host_notification_commands_attributes => [:id, :command_name, :command_line, :command_description],
                :service_notification_commands_attributes => [:id, :command_name, :command_line, :command_description])
end

更新现有记录结果:

#<ActiveRecord::RecordNotFound: Couldn't find Command with ID=2 for Contact with ID=1>

当然不存在!我正在努力建立这种关系!

添加一个新的,我得到:

#<ActiveRecord::RecordNotFound: Couldn't find Command with ID=1 for Contact with ID=>

完全正确。甚至还没有创建用户,也没有与命令建立关系,为什么 Rails 试图找到它???

我也没有看到任何更新或插入查询被记录在 rails 控制台中,所以我想它甚至没有达到那个点...

D, [2015-01-20T18:01:30.336669 #95542] DEBUG -- :    (0.1ms)  BEGIN
D, [2015-01-20T18:01:30.338971 #95542] DEBUG -- :   Command Load (0.3ms)  SELECT `commands`.* FROM `commands` INNER JOIN `commands_contacts` ON `commands`.`id` = `commands_contacts`.`command_id` WHERE `commands_contacts`.`contact_id` = 1 AND `commands_contacts`.`notification_type` = 'host' AND `commands`.`id` IN (1, 3)
D, [2015-01-20T18:01:30.340555 #95542] DEBUG -- :   Command Load (0.2ms)  SELECT `commands`.* FROM `commands` INNER JOIN `commands_contacts` ON `commands`.`id` = `commands_contacts`.`command_id` WHERE `commands_contacts`.`contact_id` = 1 AND `commands_contacts`.`notification_type` = 'service' AND `commands`.`id` IN (4, 2)
D, [2015-01-20T18:01:30.341501 #95542] DEBUG -- :    (0.1ms)  ROLLBACK
#<ActiveRecord::RecordNotFound: Couldn't find Command with ID=2 for Contact with ID=1>
Completed 200 OK in 11ms (Views: 0.4ms | ActiveRecord: 0.9ms)

我在这里错过了什么?

编辑: 我想我可以放弃使用 strong_parameters 的想法,解析所有 POST 参数然后手动填充数据库,但事实并非如此非常 Rails-y.

编辑 #2: 包括发布到控制器的参数。

数据以 params[:contact] 形式从表单

中传入
{
                     "contact_name" => "joe-user",
       "host_notification_commands" => [
        [0] {
                             "id" => 1,
                   "command_name" => "host-notify-by-email",
                   "command_line" => "/usr/local/bin/host-notify",
            "command_description" => "Host Alert",
                     "created_at" => "2015-01-19T17:24:12.000Z",
                     "updated_at" => "2015-01-21T03:29:03.000Z"
        },
        [1] {
                             "id" => 2,
                   "command_name" => "host-notify-by-pager",
                   "command_line" => "/usr/local/bin/host-notify-pager",
            "command_description" => "Host Alert by Pager",
                     "created_at" => "2015-01-19T17:24:33.000Z",
                     "updated_at" => "2015-01-19T17:24:33.000Z"
        }
    ],
    "service_notification_commands" => [
        [0] {
                             "id" => 4,
                   "command_name" => "service-notify-by-email",
                   "command_line" => "/usr/local/bin/service-notify",
            "command_description" => "Service Alert",
                     "created_at" => "2015-01-19T17:24:44.000Z",
                     "updated_at" => "2015-01-19T17:24:44.000Z"
        }
    ]
}

经过strong_parameters后变成这样:

本质上是一样的,只是去掉了 created_atupdated_at 并且 _attributes 附加到属性名称,因此它适用于 accept_nested_attributes_for

{
                                "contact_name" => "joe-user",
       "host_notification_commands_attributes" => [
        [0] {
                             "id" => 1,
                   "command_name" => "host-notify-by-email",
                   "command_line" => "/usr/local/bin/host-notify",
            "command_description" => "Host Alert"
        },
        [1] {
                             "id" => 2,
                   "command_name" => "host-notify-by-pager",
                   "command_line" => "/usr/local/bin/host-notify-pager",
            "command_description" => "Host Alert by Pager"
        }
    ],
    "service_notification_commands_attributes" => [
        [0] {
                             "id" => 4,
                   "command_name" => "service-notify-by-email",
                   "command_line" => "/usr/local/bin/service-notify",
            "command_description" => "Service Alert"
        }
    ]
}

编辑 #3: 我启用了 MySQL 查询日志记录,但我根本没有看到 UPDATE/INSERT 查询执行。我如何调试为什么 contact.update(safe_params) 没有做任何事情?

编辑 #4: 作为一个没有 AngularJS 或 POST 的简单测试,我做了一个简单的 rake 任务,它定义了一个 JSON 对象并尝试更新数据库。我遇到了同样的问题,所以我很确定问题出在我的模型中...但是在哪里???

请看一下这个 Gist https://gist.github.com/pruchai/6afe74b170da2a3d307f

构建表单并使用 nested_fields_for 时,您的表单结构构建正确。所以理想情况下,你会先尝试做 "old fashioned way",检查推送到服务器的内容并模仿它。

所以通常我不会手动执行此操作,但我会维护一个 gem 来处理嵌套表单(茧),所以如果我没记错的话,为了使 accepts_nested_attributes 起作用,参数必须如下所示:

{
    "contact_name" => "joe-user",
    "host_notification_commands_attributes" => {
      "0" => {
        "id" => 1,
        "command_name" => "host-notify-by-email",
        "command_line" => "/usr/local/bin/host-notify",
        "command_description" => "Host Alert"
      },
      "1" => {
        "id" => 2,
        "command_name" => "host-notify-by-pager",
        "command_line" => "/usr/local/bin/host-notify-pager",
        "command_description" => "Host Alert by Pager"
      }
    },
    "service_notification_commands_attributes" => {
      "0" => {
        "id" => 4,
        "command_name" => "service-notify-by-email",
        "command_line" => "/usr/local/bin/service-notify",
        "command_description" => "Service Alert"
      }, 
      "12342233444444" => {
        "command_name" => "service-notify-by-email",
        "command_line" => "/usr/local/bin/service-notify",
        "command_description" => "Service Alert"
      }
    }
  }

所以 .._attributes 应该包含一个散列而不是一个数组。关键只是(现有)数组中的位置,我假设。在 cocoon 中,我用从当前时间派生的一些大数字填充这个 虚拟索引 。我假设这个顺序应该对应于关系的默认顺序。但是,如果您传递 "id" 字段,则 rails 也可能使用它。不确定。要销毁现有元素,您必须设置 _destroy 属性(并在强参数中也允许它)。

恕我直言,对于新项目,id 应该为空,否则 rails 将假定该项目存在(因为通常不会在客户端生成 ID)。