In the last section we generated some routes for the memberships destroy action. If you noticed, the URL it generated was /memberships/:id.
Memberships, however, are a part of a user or a channel. It makes more sense to nest them under one of those to have the URL be something like /channels/:channel_id/memberships/:id.
In this section, we will do just that.
Open up config/routes.rb. We are going to nest memberships under the channels resource (we chose channels since we will only act on memberships under current user, so the user nesting didn't make as much sense). The resource method accepts a block, and everything inside of that block will be nested under the that resource.
So this:
resources :channels
resources :membershipswill become this:
resources :channels do
resources :memberships
endAn aside: You can add arbitrary routes as well under resources by using a
collectionandmemberblock. Thecollectionblock will nest it under the namespace, but not an instance. Thememberblock will nest it under the instance. For example:resources :channels do collection do get 'search' end member do get 'info' end endThis will generate the following routes:
- The default CRUD routes of the resource
/channels/searchaschannels_search_path(note the pluralization of channel)/channels/:id/infoaschannel_info_path
Now when you run bin/rails routes --grep memberships, you will see the change:
$ bin/rails routes --grep memberships --expanded
--[ Route 1 ]------------------------------------------------------------------------------------------------------------------------------------
Prefix | channel_membership
Verb | DELETE
URI | /channels/:channel_id/memberships/:id(.:format)
Controller#Action | memberships#destroyThe important thing to note is that the prefix also changed. Where we had membership_path before, we must replace it with channel_membership_path and also pass in the channel (or channel id).
The Leave Channel button should now be:
<%= link_to "Leave Channel", channel_membership_path(@channel, @active_membership), class: "btn btn-danger", method: :delete, data: { confirm: "Are you sure you want to leave #{@channel.name}?" } %>In Memberships Controller, we are now nested under Channels. That means that channel_id will always be available and we should only act on memberships in that channel.
By convention, we can add a before_action :set_channel to the top of the file. This is an action that will run before any action. Inside we will write code to set the @channel variable to the channel that is passed in the URL.
before_action :set_channel
...
private
def set_channel
@channel = Channel.find(params[:channel_id])
endNow that we have that set up, we can scope the memberships call to the channel. Change this:
# Find the user's current_membership
membership = current_user.memberships.find(params[:id])to this:
# Find the user's current_membership
membership = @channel.memberships.find_by(id: params[:id], user: current_user)This changes the initial scope to use the channel. We also changed from using find which only accepts the ID, to using find_by which accepts keywords/hash of the attributes on which you want to search. In find_by, we include the current_user so it is still scoped to the current user.
Try it out!
- Nest the memberships under the channels resource in
config/routes.rb:resources :channels do resources :memberships end
- Update the URL for the Leave Channel Button in
app/views/channels/show.html.erb:<%= link_to "Leave Channel", channel_membership_path(@channel, @active_membership), class: "btn btn-danger", method: :delete, data: { confirm: "Are you sure you want to leave #{@channel.name}?" } %>
- Add
before_action :set_channelto the top ofapp/controllers/memberships_controller.rb: - Add the set_channel method to the bottom of
app/controllers/memberships_controller.rbunderprivate:private def set_channel @channel = Channel.find(params[:channel_id]) end
- Change the scope of the
membershipscall to the channel inapp/controllers/memberships_controller.rb:def destroy @membership = @channel.memberships.find_by(id: params[:id], user: current_user) ... end
- Try it out!
https://github.com/dcsil/rails-tutorial-example/commit/f1b086c43cc437e0bfd5cc9c5f08b6f623660b47