2

I have the tables Task and Item. I have a form for Item where I record all the possible items that my Tasks may have, which is working fine. Then I have the form for Task where all the Items are displayed alongside a field to put a cost value for each item. This will result in a join between Task and Item: TaskItem (this table contains task_id, item_id and cost).

When I submit the form, it's saving the Task but not the TaskItems associated. I don't see what I'm missing as I searched a lot for this problem and nothing seems to work. Please, see the code below.   

Model:

class Task < ApplicationRecord
    has_many :task_items
    has_many :items, :through => :task_items

    accepts_nested_attributes_for :task_items, :allow_destroy => true
end

class Item < ApplicationRecord
    has_many :task_items
    has_many :tasks, :through => :task_items
end

class TaskItem < ApplicationRecord
    belongs_to :task
    belongs_to :item

    accepts_nested_attributes_for :item, :allow_destroy => true
end

Controller:

def new
    @items = Item.all

    @task = Task.new
    @task.task_items.build
end

def create
    @task = Task.new(task_params)
    @task.save
    redirect_to action: "index"
end

private def task_params
    params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end

My view:

<%= form_for :task, url:tasks_path do |f| %>
<p>
    <%= f.label :title %><br>
    <%= f.text_field(:title, {:class => 'form-control'}) %><br>
</p>

<% @items.each do |item| %>
    <% @task_items = TaskItem.new %>
    <%= f.fields_for :task_items do |ti| %>
        <%= ti.label item.description %>
        <%= ti.text_field :cost %>
        <%= ti.hidden_field :item_id, value: item.id %>
    <% end %>
<% end %>

<p>
    <%= f.submit({:class => 'btn btn-primary'}) %>
</p>
12
  • What is the log output when you try to save a TaskItem? Commented Mar 9, 2017 at 19:13
  • 1
    Using render plain: params[:task].inspect I got this: <ActionController::Parameters {"title"=>"Teste 1", "task_items"=>{"item_id"=>"4", "cost"=>"55"}} permitted: false> Commented Mar 9, 2017 at 19:20
  • 1
    "task"=>{"title"=>"Teste565656", "task_items"=>{"item_id"=>"4", "cost"=>"55"}}, "commit"=>"Save Contato"} Unpermitted parameter: task_items (0.0ms) begin transaction SQL (21.0ms) INSERT INTO "tasks" ("title", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "Teste565656"], ["created_at", 2017-03-09 19:31:41 UTC], ["updated_at", 2017-03-09 19:31:41 UTC]] (122.5ms) commit transaction Redirected to localhost:3000 Commented Mar 9, 2017 at 19:33
  • You have an error in your task_params error: it should state :cost instead of :valor according with the code present in your view Commented Mar 9, 2017 at 19:48
  • sorry, task_params fixed but it wasn't the cause of the problem. somehow task_items_attributes are not being understandood on saving my task. Commented Mar 9, 2017 at 19:57

1 Answer 1

4

You need to add inverse_of option to the has_many method in class Task:

class Task < ApplicationRecord
  has_many :task_items, inverse_of: :task
  has_many :items, through: :task_items

  accepts_nested_attributes_for :task_items, :allow_destroy => true
end

This is due to the when creating a new TaskItem instance, it requires that the Task instance already exists in database to be able to grab the id fo the Task instance. Using this option, it skips the validation.

You can read this post about inverse_of option and its use cases.

fields_for has an option to specify the object which is going to store the information. This combined with building each of the TaskItem from the has_many collection should ensure that all the relationship are set correctly.

View Code:

<%= form_for @task do |f| %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field(:title, {:class => 'form-control'}) %><br>
  </p>

  <% @items.each do |item| %>
    <% task_item = @task.task_items.build %>
    <%= f.fields_for :task_items, task_item do |ti| %>
      <%= ti.label item.description %>
      <%= ti.text_field :cost %>
      <%= ti.hidden_field :item_id, value: item.id %>
    <% end %>
  <% end %>

  <p>
    <%= f.submit({:class => 'btn btn-primary'}) %>
  </p>  
<% end %>

Controller Code:

def index
end

def new
  @items = Item.all
  @task = Task.new
end

def create
  @task = Task.new(task_params)
  @task.save
  redirect_to action: "index"
end

private

def task_params
  params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end
Sign up to request clarification or add additional context in comments.

1 Comment

It still says "Unpermitted parameter: task_items" in my log and the task_items are not created. Shoul I use inverse_of anywhere else? Am I using the _attributes correctly? Many thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.