Cameron Bass Cameron Bass - 1 month ago 7
Ruby Question

Creating an object that belong to another - Understand Rails Associations

I have an object called Tasklist that has many Tasks. Simple enough. But Tasklist belongs to User. I'm trying to create a new task from the tasklists show page, and I'm having a ton of trouble figuring out how to do that. I'll post code and explain my problem further. Any help would be great! Thank you.

Tasklists Controller



def show
@tasklist = current_user.tasklists.find(params[:id])
@task = @tasklist.tasks.new
end


Tasklists Show Page



<h1>page<%= @tasklist.name %></h1>


<%= form_for @task do |form| %>

<%= form.label :description %>
<%= form.text_field :description %>

<%= form.submit %>

<% end %>


Tasks Controller



def create
current_user.tasklists.tasks.create(task_params)
end

private

def task_params
params.require(:task).permit(:description)
end


Here is the error after I try to create the object from the show page.

## ERROR
NoMethodError in TasksController#create
undefined method `tasks' for #<Tasklist::ActiveRecord_Associations_CollectionProxy:0x007faa4263b520>

def create
current_user.tasklists.tasks.create(task_params)
end

max max
Answer

User tasklists returns a collection of records and you are trying to call the instance method #tasks on it.

Just think about it - how should ActiveRecord know which task_list in the collection you re trying to add a task to?

First you need to untangle your associations:

class User < ActiveRecord::Base
  has_many :task_lists
  has_many :tasks, though: :task_lists
end

class TaskList < ActiveRecord::Base
  belongs_to :user
  has_many :tasks
end

class Task < ActiveRecord::Base
  belongs_to :task_list
  # dont use belongs_to as Rails will not keep the relation up 
  # to date!
  has_one :user, though: :task_list
end

Basically we are setting up an indirect relationship between User and Task and ensuring the actual foreign key columns are not duplicated.

What you want here is a nested resource:

resources :task_lists, shallow: true do
  resources :tasks
end

This will give you these routes among others (run rake routes to see them all)

            Prefix Verb   URI Pattern                                   Controller#Action
   task_list_tasks GET    /task_lists/:task_list_id/tasks(.:format)     tasks#index
                   POST   /task_lists/:task_list_id/tasks(.:format)     tasks#create
new_task_list_task GET    /task_lists/:task_list_id/tasks/new(.:format) tasks#new

This means we can fetch the task list from params[:task_list_id].

class TasksController < ApplicationController
  before_action :set_task_list, only: [:new, :create, :index]

  # POST /task_lists/:task_list_id/tasks
  def create
    @task = @task_list.tasks.new(task_list_params)
    if @task.save
      redirect_to @task_list, success: 'Task created.'
    else
      render 'tasks/show'
    end
  end

  private 

  def set_task_list
    @task_list = TaskList.includes(:tasks)
                         .where(user: current_user)
                         .find(params[:task_list_id])
  end

  def task_list_params
    params.require(:task).permit(:description)
  end
end

Since we have has_one :user, though: :task_list we don't have to worry about the current_user when creating a task. We just add one to the task_list.

Now lets update the form so that it points to the right resource:

<h1>page<%= @tasklist.name %></h1>

<%= form_for[@tasklist, @task] do |form| %>

  <%= form.label :description %>
  <%= form.text_field :description %>

  <%= form.submit %>

<% end %>