简体   繁体   中英

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

I can't figure out why my database records are not getting updated, or new records created, for that matter, when I POST from a form.

I am able to manually populate the database and create relationships:

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

And I am also able to load this data info my form. What I can't figure out is how to update or add new records. Everything i've tried so far has failed.

I have 3 models - Command , Contact and joined CommandsContact . commands_contacts join table is holing an extra attribute :notification_type , which could either be set to host or service , and my Contact model has 2 extra relationships setup to i can access :host_notification_commands and :service_notification_commands . This gives me an ability to do things like Contact.fist.host_notification_commands or Contact.find(3).service_notification_commands.

It appears that no UPDATE or INSERT queries are ever firing up from the controller, when I to proper POST and I can't figure out how to debug that.

Models

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

And after this everything falls apart.

Controller

Due to the fact that I am using accepts_nested_attributes_for , I have to append _attributes to the names of my nested objects - :host_notification_commands and :service_notification_commands . I'll change my form to submit it that way, but simple re-assignment works for an example sake.

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

Updating existing record results in:

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

Of course it does not exist! I am trying to create this relationship!

Adding a new one, I get:

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

Absolutely correct. The user haven't even been created yet and relationship is not established with the commands, why is Rails trying to find it???

I am also not seeing any update or insert queries being logged in the rails console, so i guess its not even getting to that point...

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)

What am i missing over here?

EDIT: I guess i could abandon the idea of using strong_parameters , parse all POST params and then manually populate the database, but thats not very Rails-y.

EDIT #2: Including params posted to the controller.

Data coming in as params[:contact] from the form

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

After going through strong_parameters it becomes this:

Essentially the same thing, only with created_at and updated_at stripped and _attributes appended to the attribute names, so it works with 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"
        }
    ]
}

EDIT #3: I have enabled MySQL query logging but I do not see UPDATE / INSERT queries executed at all. How can i debug why contact.update(safe_params) is not doing anything?

EDIT #4: As a simple test without AngularJS or POST being in the equation, I made a simple rake tasks that defines a JSON object and tries to update the db. I am getting the same issue, so Im pretty convinced the issue is somewhere in my models... but where???

Please take a look at this Gist https://gist.github.com/pruchai/6afe74b170da2a3d307f

When building a form and using nested_fields_for your form-structure is built correctly. So ideally you would try first do it the "old fashioned way", check what is pushed to the server and mimick that.

So generally I do not do this by hand, but I maintain a gem to handle nested forms (cocoon), so if I am not mistaken, for the accepts_nested_attributes to work, the parameters have to look as follows:

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

So instead of an array the .._attributes should contain a hash. The key is just the position in the (existing) array, I asuume. In cocoon I fill this dummy index with some large number derived from the current time. I assume this order should correspond to the default order for the relation. But if you hand down the "id" field, it is also possible rails uses that. Not sure. To destroy an existing element, you have to set the _destroy attribute (and allow it in the strong parameters as well).

Imho for new items the id should be empty, otherwise rails will assume the item exists (as generally ids are not generated on the client side).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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