Skip to content

Instantly share code, notes, and snippets.

@pruchai
Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pruchai/6afe74b170da2a3d307f to your computer and use it in GitHub Desktop.
Save pruchai/6afe74b170da2a3d307f to your computer and use it in GitHub Desktop.
Rake task to demonstrate failure to update a record
namespace :post do
desc "Test Post"
task :nested_test => :environment do
def contact_params
params = {
:id => '1',
:contact_name => 'joe-user',
:host_notification_commands_attributes => {
'0' => {"id"=>1, "command_name"=>"command1", "command_line"=>"/usr/local/bin/command1", "command_description"=>"Command One"},
'1' => {"id"=>2, "command_name"=>"command2", "command_line"=>"/usr/local/bin/command2", "command_description"=>"Command Two"}
},
:service_notification_commands_attributes => {
'0' => {"id"=>3, "command_name"=>"command3", "command_line"=>"/usr/local/bin/command3", "command_description"=>"Command Three"},
'1' => {"id"=>4, "command_name"=>"command4", "command_line"=>"/usr/local/bin/command4", "command_description"=>"Command Four"}
}
return params
end
begin
contact = Contact.find_by_id(contact_params[:id])
contact.update(contact_params)
rescue => error
puts error
end
end
end
#
# Both, contact and command exist in the database
#
% rails c
Loading development environment (Rails 4.2.0)
irb(main):001:0> contact = Contact.find_by_id(1)
Contact Load (0.2ms) SELECT `contacts`.* FROM `contacts` WHERE `contacts`.`id` = 1 LIMIT 1
=> #<Contact id: 1, contact_name: "pruchai", created_at: "2015-01-19 22:44:10", updated_at: "2015-01-20 07:45:15">
irb(main):002:0> command = Command.find_by_id(1)
Command Load (0.2ms) SELECT `commands`.* FROM `commands` WHERE `commands`.`id` = 1 LIMIT 1
=> #<Command id: 1, command_name: "command1", command_line: "/usr/local/bin/command1", command_description: "Command One", created_at: "2015-01-19 17:24:12", updated_at: "2015-01-21 03:29:03">
irb(main):003:0>
#
# Running the task to simply eastablish relationship between 2 existing objects
#
# WHY IS IT TRYING TO FIND IT, INSTEAD OF CREATING IT???
#
% rake post:nested_test
Couldn't find Command with ID=1 for Contact with ID=1
@nathanvda
Copy link

Let me get this straight:

  • a Contact with ID=1 exists
  • a Command with ID=1 exists,

but you do not show the CommandContacts table, is it of the right type? You give the id of the commands, so it will go looking for existing commands. While a command with ID=1 exists, it maybe is not a host command? (notification_type = :host).

You should show the output of contact.host_notification_commands.

If you are creating new items, the ids should be empty.

@pruchai
Copy link
Author

pruchai commented Jan 26, 2015

@nathanvda: Really appreciate you taking your time to help. Thank you!

That is correct. At this point I am working with existing records and just trying to establish relationships between them. I have a contact with ID=1 and I have a command with ID=1, but they are no related to each other in any way shape of form. They are separate entities that do not know about each other.

My form has 2 drop down menus. They are identical and are populated with a list of commands that I have in commands table.

    commands = Command.all

The only difference between them is when i select a command in one drop down, a command object gets pushed into contact.host_notification_commands and another one pushes objects into contact.service_notification_commands. A contact may be associated with 0 or more notification commands of either type. Same command can be a host and a service notification command at the same time.

So, lets say that I want to update a contact with ID=1, which is not currently associated with any host or service commands, so I select a contact like this:

    def show
      contact = Contact.find_by_id(params[:id])
      render json: contact[0].to_json(:include => [ :host_notification_commands, :service_notification_commands ] )
    end

This results in the following contact object loaded into the form:

    {
      :id => 1,
      :contact_name => 'joe-user',
      :host_notification_commands => [],
      :service_notification_commands => []
    }

After that, I select an existing command named command1 from a drop down, which turns my contact objects into this:

    {
      :id => 1,
      :contact_name => 'joe-user',
      :host_notification_commands => [
        {
          :id => 1,
          :command_name => 'command1',
          :command_line => '/usr/local/bin/command1',
          :command_description => 'Command One'
          # :created_at and :updated_at also included, but ill leave them out for now
        }
      ],
      :service_notification_commands => []
    }

Now I do POST to my controller. POST parameters go trough strong_parameters and then to my update function.

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

And this is where it bombs. I really have no idea if I am mis-interpreting what Rails is supposed to do. I am under the assumption that it is supposed to take my contact and establish relationships with the command via commands_contacts table and set :notification_type column to host.

Instead, Rails runs a SELECT query to see if my contact with ID=1 has a relationship with a command with ID=1. Obviously, it fails, because this relationship is not there and I am just now trying CREATE it.

I created a couple of self-contained scripts as a test:

This one just tests the model and passes all the tests
https://gist.github.com/pruchai/c2e190611aa02c5a03b9

This one is pretty much the same, but adds a controller and tries to run an update on contact
https://gist.github.com/pruchai/0f8e134c1733e5cd40b6

The fact that I am able to reproduce the same behavior with the same error in 3 different ways tells me that I am either misunderstanding Rails functionality (very likely), hitting some kind of bug (I doubt that) or have problems with my logic (most probable scenario)

@nathanvda
Copy link

Ah I see now! You are trying to link existing commands with contacts, right? In that case you should not be filling the host_notification_commands, but you should be filling the join-table command_contacts.

@pruchai
Copy link
Author

pruchai commented Jan 26, 2015

Correct. But filling host_notification_commands and service_notification_commands does, indeed, fill the command_contacts table.

host_notification_commands and service_notification_commands are "virtual" associations setup in my Contact model.

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 :host_notification_commands
  accepts_nested_attributes_for :service_notification_commands
end

So, essentially, they are the same as Contact <--> Command association, but they automatically set the notification_type column in command_contacts table.

Regular has_many through Contact <--> Command relationship knows nothing about the notification_type, so i don't even know how I would set that field, without using additional host_notification_commands and service_notification_commands relationships

has_many :commands, :through => :commands_contacts

Here is a small demonstration

  contact = Contact.create!

  # This does not meet my needs, because it does not set notification_type
  contact.commands << Command.create!  
  # These 2 do meet my needs, because they set notification_type
  contact.host_notification_commands << Command.create!
  contact.service_notification_commands << Command.create!

  commands_contacts = CommandsContact.all
  commands_contacts.each do |cc|
    puts 'Contact ID: ' + cc.contact_id.to_s + ' / Command ID: ' + cc.command_id.to_s + ' / Notification Type: ' + cc.notification_type.to_s
  end

Running above code, results in following output:

Contact ID: 1 / Command ID: 1 / Notification Type:
Contact ID: 1 / Command ID: 2 / Notification Type: host
Contact ID: 1 / Command ID: 3 / Notification Type: service

As you can see, everything goes into the commands_contacts as expected. The only difference is the notification_type column

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment