Jonathan Tuzman
Jonathan Tuzman

Reputation: 13262

Unable to pass many-to-many relationships to Create method

Maybe it's been too long since I've used Rails but I can't figure out why this works (creating a Task and then assigning prerequisites using #prerequisite_ids=):

[3] pry(main)> task = Task.create(name:"do this last")                                                                                                                               
=> #<Task:0x00007fb0e2570860
 id: 7,
 name: "do this last",
  ...>
[4] pry(main)> task.prerequisite_ids=[5,6]                                                                                                                                           
=> [5, 6]
[5] pry(main)> task.prerequisites                                                                                                                                                    
=> [#<Task:0x00007fb0e2a82948
  id: 5,
  name: "Do this first",
  ...>
 #<Task:0x00007fb0e2a82600
  id: 6,
  name: "Do this next",
  ...>]

But this doesn't (passing an array of prerequisite_ids to Task.create)

[1] pry(main)> t = Task.create(name:"do this last", prerequisite_ids:[5,6])                                                                                                          
  Task Load (1.3ms)  SELECT "tasks".* FROM "tasks" WHERE "tasks"."id" IN ($1, $2)  [["id", 5], ["id", 6]]
   (0.3ms)  BEGIN
   (0.2ms)  ROLLBACK
=> #<Task:0x00007fb0e0e93d40 id: nil, name: "do this last", description: nil, created_at: nil, updated_at: nil, completed: false>
[2] pry(main)> t.errors                                                                                                                                                              
=> #<ActiveModel::Errors:0x00007fb0e2b18b00
 @base=#<Task:0x00007fb0e0e93d40 id: nil, name: "do this last", description: nil, created_at: nil, updated_at: nil, completed: false>,
 @details={:prerequisite_tasks=>[{:error=>:invalid}, {:error=>:invalid}]},
 @messages={:prerequisite_tasks=>["is invalid"]}>

The << operator also works to add prerequisites. And ta = Task.create(name:"do this last", prerequisites:[Task.find(5), Task.find(6)] similarly does not work.

My Model and Controller (even though I guess the controller doesn't actually factor in here):

class Task < ApplicationRecord
  has_many :prerequisite_tasks, foreign_key: :do_second_id  
  has_many :prerequisites, through: :prerequisite_tasks, source: :do_first
end

class PrerequisiteTask < ApplicationRecord
  belongs_to :do_first, class_name: :Task
  belongs_to :do_second, class_name: :Task
end

class TasksController < ApplicationController
  def index
    render json: Task.all
  end

  def show
    task = Task.find(params[:id])
    render json: task
  end

  def create
    task = Task.create task_params
    render json: task, status: 201
  end
end

def task_params
  params.require(:task).permit :name, :description, :completed, prerequisite_ids: []
end

UPDATE

I've gotten my tests to pass with this workaround:

  def create
    task = Task.create task_params.except(:prerequisite_ids)
    task.update prerequisite_ids: task_params[:prerequisite_ids]
    render json: task, status: 201
  end

Prying into the tests shows that the created object AND the returned json both have the correct prerequisite_ids. So it appears to work.

However, when I try to POST from Postman, it's not accepting the prerequisite_ids update:

Started POST "/tasks?task[name]=Post%20from%20Postman&task[description]=Testing%20value%20types&task[completed]=true&task[prerequisite_ids]=[5,6]" for ::1 at 2019-04-26 23:57:12 +0000
Processing by TasksController#create as */*
  Parameters: {"task"=>{"name"=>"Post from Postman", "description"=>"Testing value types", "completed"=>"true", "prerequisite_ids"=>"[5,6]"}}
Unpermitted parameter: :prerequisite_ids
   (0.6ms)  BEGIN
  ↳ app/controllers/tasks_controller.rb:12
  Task Create (1.2ms)  INSERT INTO "tasks" ("name", "description", "created_at", "updated_at", "completed") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "Post from Postman"], ["description", "Testing value types"], ["created_at", "2019-04-26 23:57:12.373180"], ["updated_at", "2019-04-26 23:57:12.373180"], ["completed", true]]
  ↳ app/controllers/tasks_controller.rb:12
   (1.1ms)  COMMIT
  ↳ app/controllers/tasks_controller.rb:12
Unpermitted parameter: :prerequisite_ids
   (0.6ms)  BEGIN
  ↳ app/controllers/tasks_controller.rb:13
  Task Load (0.5ms)  SELECT "tasks".* FROM "tasks" WHERE 1=0
  ↳ app/controllers/tasks_controller.rb:13
  Task Load (1.0ms)  SELECT "tasks".* FROM "tasks" INNER JOIN "prerequisite_tasks" ON "tasks"."id" = "prerequisite_tasks"."do_first_id" WHERE "prerequisite_tasks"."do_second_id" = $1  [["do_second_id", 15]]
  ↳ app/controllers/tasks_controller.rb:13
   (0.2ms)  COMMIT
  ↳ app/controllers/tasks_controller.rb:13
[active_model_serializers] Rendered TaskSerializer with ActiveModelSerializers::Adapter::Attributes (1.58ms)
Completed 201 Created in 55ms (Views: 7.9ms | ActiveRecord: 5.1ms)

The returned object:

{
    "id": 16,
    "name": "Post from Postman",
    "description": "Testing value types",
    "completed": true,
    "prerequisite_ids": []
}

Upvotes: 0

Views: 37

Answers (1)

br3nt
br3nt

Reputation: 9586

prerequisite_ids is not an attribute of the model which is why you cannot pass it into the create method.

Instead, it's a helper method added to the class when you created the relationship with has_many :prerequisites.

You can read more about this functionality in the Rails Guide, Methods Added by has_many, as well as in the ActiveRecord::Associations::ClassMethods docs.

Upvotes: 1

Related Questions