7

I am newish to Ruby on Rails, using version 4.1 in a project.

I am a bit confused about the way the nested resources are working in rails. Perhaps someone can help.

I am building a tasking system as a learning project.

I have websites that have tasks that belong to them. When viewing tasks, I can do:

resources :websites do 
  resources :tasks
end

And a link will take me to my task just fine, with a url like http://myapp.com/websites/2/tasks/3

<%= link_to 'Show', website_task_path(website,task) %>

What I noticed though, is that I can change the website id in the url to anything - http://myapp.com/websites/what-the-hell-is-going-on-here/tasks/1 - works just the same as the other link. Or I can access that task with a different website id in the url as well.

So the question is, is Rails supposed to do anything with that piece of information by default? Is it up to me if I want to make sure that you are accessing the task with the right parent resource in the parameters?

2 Answers 2

6

Your task's ID is unique in the tasks table. As long as you want to show, edit or delete a task, this information is enough. You can easily get the parent through your association. But a nested resource allows you to create new tasks. In this case there is no ID set. You need to know the correct parent to set it on your task.

Excerpt from a possible TasksController:

class TasksController < ApplicationController

  before_action :set_website, only: [:create]

  def create
    @website.tasks.create!(task_params)
  end

  private

  def set_website
    @website = Website.find(params[:website_id])
  end

  def task_params
    params.require(:task).permit(:title, :text)
  end

end

Generated nested routes:

# Routes without :task_id - Parent matters!

website_tasks
GET  /websites/:website_id/tasks(.:format) tasks#index
POST /websites/:website_id/tasks(.:format) tasks#create

new_website_task
GET  /websites/:website_id/tasks/new(.:format) tasks#new

# Routes with :task_id - Parent redundant.

edit_website_task
GET  /websites/:website_id/tasks/:id/edit(.:format) tasks#edit

website_task
GET    /websites/:website_id/tasks/:id(.:format) tasks#show
PATCH  /websites/:website_id/tasks/:id(.:format) tasks#update
PUT    /websites/:website_id/tasks/:id(.:format) tasks#update
DELETE /websites/:website_id/tasks/:id(.:format) tasks#destroy

You can clean the routes from the redundant website_id by using shallow nesting. This is explained in detail in the Rails docs.

Basically it means:

resources :website do
  resources :tasks, only: [:index, :new, :create]
end
resources :tasks, only: [:show, :edit, :update, :destroy]

Which is the same as writing:

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

There may be some use cases where the full path is valuable, e. g. you want to feed it to a search engine or you want the URL to be more speaking for readers.

Sign up to request clarification or add additional context in comments.

2 Comments

I think I understand. My takeaway is that the parent resource may be important for context when the child resource id is not sent (index/create/new). If I am sending the task id as well, it may be redundant but useful as a reference or breadcrumb (show/edit)
Exactly :) Glad I could help.
1

So the question is, is Rails supposed to do anything with that piece of information by default?

You can change the website ID by hard-coding it directly in your ULR if you wish but is is useless as your controller take care of dynamically generating URL based on the request.

Is it up to me if I want to make sure that you are accessing the task with the right parent resource in the parameters?

Yes it is up to you to make sure the A task is accessed with the right parent by making sure that:

  • your route are nested routes like you did
  • your Model have associations defined, eg:Website: has_many :tasks and Task: belongs_to :website (exemple) you may also need to add accepts_nested_attributes_for :tasks, reject_if: :all_blank, allow_destroy: true if you want to create task with your website controller (I do not know what you want to do)
  • when you create a task in your controller you are doing it with the parent e.g: @website.tasks.new(task_params) so that task is saved in the database with a website_id which will allow you to retrieve A task or taskS depending on your association.

Your route will be generated and work as long as Model, views and controller know what task if for website and viceversa.

Comments

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.