Member and Collection Routes in Ruby on Rails

The routes of a Ruby on Rails application live in the config/routes.rb file. The Rails router matches a request in the browser to a controller action. The Rails default for routing is the resource routing. A resource route will map HTTP verbs like GET, POST, PATCH, PUT and DELETE to a controller action and also to a certain CRUD operation in the database. More precisely, a resource route will create seven routes in an application corresponding to the seven standard controller actions: index, show, new, edit, create, update and destroy.

Generate the Application

Let's generate a new application. In the terminal, we type:

rails new Blog

After we create the application, we change the directory to its folder:

cd Blog

Generate an Article Scaffold

Let's generate a scaffold for an Article resource:

bin/rails g scaffold Article title:string content:text 

And then we run the migration:

bin/rails db:migrate

Set the Root Route

This application needs a root route, so let's set it to the articles index action. In the config/routes.rb file, put this code at the top:

root 'articles#index'

Add a Status to Articles

Now we want to add a status attribute to the article model. The articles have two possible statuses: draft and published. For this status attribute, we'll use enums. In Rails, an enum is an attribute that maps to integers in the database but can be queried by name.

Let's generate the migration. In the terminal we type:

bin/rails g migration AddStatusToArticles status:integer

Then we run the migration:

bin/rails db:migrate

Let's declare the two statuses in the app/models/article.rb file:

enum status: {draft: 0, published: 1}, _default: "draft"

The default status is draft. When the article is ready to be published, we want to set its status to published.

We want to add a Publish button in the views. When the article is ready, we want to change its status to published by hitting this button.

To do that, we first define a custom action in the articles controller. We will call this action publish.

def publish
  @article.published!
  redirect_to root_path
  flash[:notice] = 'Article was successfully published.'
end

With this method, we change the status to published and then redirect to the root_path.
We add this action to the list of actions to which we will apply the set_article filter:

before_action :set_article, only: %i[ show edit update destroy publish ]

For this action, we will add a member route in the config/routes.rb file. We can add a block like this:

resources :articles do
  member do
    get 'publish'
  end
end

Or, if you have only one member route, you can eliminate the block like this:

resources :articles do
  get 'publish', on: :member
end

Since our application has only one member route, we can eliminate the block.

Defining this member route enables the Rails router to recognize paths like /articles/:id/publish with GET. Also, it creates the publish_article_path helper and the corresponding publish_article_url helper.

In the index view, we only want to display a list of published articles and put the drafts on a separate view. Let's open the articles controller and edit the index method:

def index
  @articles = Article.published.all
end

Next, we will add the drafts custom action to our controller:

def drafts
  @articles = Article.draft.all
end

And in the config/routes.rb file, we add a collection route to it:

resources :articles do
  get 'publish', on: :member
  get 'drafts', on: :collection
end

This will enable the Rails router to recognize the /articles/drafts URL with GET. Also, it will create the drafts_articles_path helper and its corresponding drafts_articles_url helper.

Then we create the corresponding drafts.html.erb view in the app/views/articles/ folder.  Here, we add the following code:

<h1> Drafts</h1>
<% @articles.each do |article| %>
  <%= render article %>
  <p>
    <%= link_to "Publish this article",  publish_article_path(article) %>
  </p>
<% end %>

We display a list of draft articles, and for every article, we add a Publish button linked to the publish_article_path

We also add a Publish button in the show article view:

<p>
  <%= link_to "Publish this article",  publish_article_path(@article) %>
</p>

Finally, we add a link to the drafts in the index articles view using the drafts_articles_path. At the bottom, we add this code:

<p>
  <%= link_to 'Drafts', drafts_articles_path %>
</p>

Conclusion

Using the member and the collection routes, we easily added additional routes to the seven default routes created using the resource routing.

Post last updated on May 13, 2023