Group By Month in Rails 5

Steps

Step 1

Task has due_date. Let's add this field to the task.

rails g migration add_due_date_to_tasks due_date:date

The generated migration file looks like this:

class AddDueDateToTasks < ActiveRecord::Migration[5.0]
  def change
    add_column :tasks, :due_date, :date
  end
end

Step 2

Create sample data in seeds.rb.

wealthy = Project.create(name: 'Wealth Building')
wealthy.tasks.create(name: 'Get rich quick', complete: false, priority: 4, due_date: 2.months.ago)
wealthy.tasks.create(name: 'Write a book', complete: true, priority: 5, due_date: 1.month.ago)

happy = Project.create(name: 'Be Happy')
happy.tasks.create(name: 'Buy a puppy', complete: false, priority: 9, due_date: 1.month.ago)
happy.tasks.create(name: 'Dance in the rain', complete: true, priority: 7, due_date: 2.months.ago)
happy.tasks.create(name: 'Dance in the snow', complete: true, priority: 6, due_date: 2.months.ago)
happy.tasks.create(name: 'Walk on water', complete: true, priority: 7, due_date: 2.months.from_now)
happy.tasks.create(name: 'Just Blog It', complete: true, priority: 5, due_date: 1.month.from_now)
happy.tasks.create(name: 'Play tennis', complete: true, priority: 4, due_date: 2.days.ago)
happy.tasks.create(name: 'Make fun of Donald Trump', complete: true, priority: 3, due_date: 2.days.ago)
happy.tasks.create(name: 'Walk on moon', complete: true, priority: 2, due_date: 2.days.ago)

Let's clear all the old data and recreate the tables and populate the database with new data.

rails db:drop
rails db:migrate
rails db:seed

Step 3

Initialize task_months in tasks controller:

@task_months = @tasks.group_by {|t| t.due_at.beginning_of_month }

The index action now looks like this:

def index
  @tasks = Task.all.to_a
  @task_months = @tasks.group_by {|t| t.due_at.beginning_of_month }
end

Step 4

The tasks/index.html.erb looks like this:

<h1>Tasks</h1>
<table>
  <thead>
    <tr>
      <th></th>
    </tr>
  </thead>
  <tbody>
      <% @task_months.each do |month, tasks| %>
        <%= month.strftime('%B') %>
      <tr>
        <% for task in tasks %>
        <div class='task'>
            <strong> task.name </strong>
            due on <%= task.due_date.to_date.to_s(:long) %>
        </div>
        <% end %>
       </tr>    
      <% end %>   
  </tbody>
</table>

The to_s(:long) is Rails method used to format the date.

Step 5

Reload the tasks index page to view the tasks. The hash does not preserve the order, so the months are jumbled up.

Tasks

January
task.name due on January 17, 2016
task.name due on January 17, 2016
task.name due on January 17, 2016
February
task.name due on February 17, 2016
task.name due on February 17, 2016
May
task.name due on May 17, 2016
April
task.name due on April 17, 2016
March
task.name due on March 15, 2016
task.name due on March 15, 2016
task.name due on March 15, 2016

Step 6

We can fix the sorting problem by using the Rails 5 order method to sort the tasks:

def index
  @tasks = Task.order(:due_date)
  @task_months = @tasks.group_by {|t| t.due_date.beginning_of_month }
end

Change the tasks/index.html.erb to look like this:

<tbody>
  <% @task_months.each do |month, tasks| %>
    <%= month.strftime('%B') %>
  <tr>
    <% for task in tasks %>
    <div class='task'>
        <strong> task.name </strong>
        due on <%= task.due_date.to_date.to_s(:long) %>
    </div>
    <% end %>
   </tr>         
  <% end %>   
</tbody>

Step 7

After sorting, tasks are displayed like this:

Tasks

January
task.name due on January 17, 2016
task.name due on January 17, 2016
task.name due on January 17, 2016
February
task.name due on February 17, 2016
task.name due on February 17, 2016
March
task.name due on March 15, 2016
task.name due on March 15, 2016
task.name due on March 15, 2016
April
task.name due on April 17, 2016
May
task.name due on May 17, 2016

Summary

In this article, you learned how to sort tasks by month by using group_by and order in Rails 5.


Related Articles