Spectator6
Spectator6

Reputation: 403

How to create a link_to delete action that removes a has_many through association?

Spent the last day and a half scouring through the great threads here at SO and reading other things online and I'm still having trouble grasping the solution and how these moving pieces operate together.

I have a many-to-many relationship between County and TownshipRange through CountyTownshipRangeSurvey. This relationship is indicated in the routes file through nested resources.

I want the user to be able to delete the association between the two records, but the problem I'm running into is the delete link on the TownshipRange index page only accepts the township_range id value and not the associated county id value. Note that the correct county id param is present when viewing the index page (which contains the delete link).

How can I allow for this?

My hunch is that I need to make a change in two place. First, to the routes file so it will accept the county id and second, to the link_to in the view.

As a follow-up question... I realize I'm trying to put the functionality into the township_ranges_controller. Is this part of the problem? Would I be better served to move this functionality into a separate controller dedicated solely to the creation of the "through table" associations?

Thank you all for your invaluable insights! If you need any more code snippets, please let me know!

Model county.rb

class County < ApplicationRecord
  has_many :township_ranges, through: :county_township_range_surveys
  has_many :county_township_range_surveys
  ...
end

Model township_range.rb

class TownshipRange < ApplicationRecord
  has_many :counties, through: :county_township_range_surveys
  has_many :county_township_range_surveys
  ...
end

Model county_township_range_survey.rb

class CountyTownshipRangeSurvey < ApplicationRecord
  belongs_to :county
  belongs_to :township_range
  ...
end

Controller township_ranges_controller.rb

before_action :set_township_range, only: [:show, :edit, :update, :destroy]

...

# QUESTION: How do I find the appropriate county?
def destroy
  @county = County.find(params[:id]) # This does not work
  @county.township_ranges.destroy(@township_range)
  redirect_to township_ranges_path(@county)
  flash[:success] = "Township and Range has been deleted"
end

....

private

    def set_township_range
      @township_range = TownshipRange.find(params[:id])
    end

    ...

related portion of routes.rb (expanded 20161124)

resources :states, shallow: true do
  resources :counties do
    resources :township_ranges do
      resources :sections
    end
  end
end

Rails routes indicates the generated route does not look for the associated counties/:id for the DELETE action.

    township_ranges GET    /counties/:id/township_ranges(.:format)     township_ranges#index
                    POST   /counties/:id/township_ranges(.:format)     township_ranges#create
 new_township_range GET    /counties/:id/township_ranges/new(.:format) township_ranges#new
edit_township_range GET    /township_ranges/:id/edit(.:format)         township_ranges#edit
     township_range GET    /township_ranges/:id(.:format)              township_ranges#show
                    PATCH  /township_ranges/:id(.:format)              township_ranges#update
                    PUT    /township_ranges/:id(.:format)              township_ranges#update
                    DELETE /township_ranges/:id(.:format)              township_ranges#destroy

It will look for the full route DELETE counties/:county_id/township_range/:id/ if I isolate the nesting by itself like this

resources :counties do
  resources :township_ranges 
end

but that keeps counties from being nested under states, which is not what I'm after...

township_range/index.html.erb

<td><%= link_to 'Destroy', township_range, method: :delete, 
          data: { confirm: "Delete #{township_range.township} 
          #{township_range.range} ?" } %></td>

development.log (with link update as requested by @Wish Zone)

Started GET "/counties/25/township_ranges" for 127.0.0.1 at 2016-11-23 15:23:20 -0600
Processing by TownshipRangesController#index as HTML
  Parameters: {"id"=>"25"}
  [1m[36mCounty Load (0.1ms)[0m  [1m[34mSELECT  "counties".* FROM "counties" WHERE "counties"."id" = ? LIMIT ?[0m  [["id", 25], ["LIMIT", 1]]
  Rendering township_ranges/index.html.erb within layouts/application
  [1m[36mTownshipRange Load (34.5ms)[0m  [1m[34mSELECT "township_ranges".* FROM "township_ranges" INNER JOIN "county_township_range_surveys" ON "township_ranges"."id" = "county_township_range_surveys"."township_range_id" WHERE "county_township_range_surveys"."county_id" = ?[0m  [["county_id", 25]]
  Rendered township_ranges/index.html.erb within layouts/application (72.6ms)
Completed 500 Internal Server Error in 113ms (ActiveRecord: 35.2ms)



ActionView::Template::Error (No route matches {:action=>"show", :controller=>"township_ranges", :county_id=>#<County id: 25, name: "comanche", abbreviation: nil, state_id: 35, created_at: "2016-11-22 16:24:52", updated_at: "2016-11-22 16:24:52">, :id=>nil} missing required keys: [:id]):
    22:         <td><%= link_to 'Edit', edit_township_range_path(township_range) %></td>
    23:         <%# QUESTION: Do I need to modify the link here so it somehow takes in the county id param? %>
    24:         <%# NOTE: This will likely also require some sort of customization to the routes file so that the county_id is passed as a param %>
    25:         <td><%= link_to 'Destroy', township_range_path(id: @township_range, county_id: @county), method: :delete,
    26:                   data: { confirm: "Delete #{township_range.township} #{township_range.range} ?" } %></td>
    27:       </tr>
    28:     <% end %>

Upvotes: 1

Views: 878

Answers (1)

Vishal G
Vishal G

Reputation: 1531

According to your situation Route should be

township_range_path(id: @township_range , county_id: @county)

and in controller you can find

County.find(params[:county_id])

Added by Spectator6: The final answer ended up being to remove the member do's I originally had in my routes. The working delete path ended up being

township_range_path(id: township_range, county_id: @county)

Thanks again @Wish Zone for your patience and for helping me understand the link_to path and routes a bit better :)

Upvotes: 2

Related Questions