In this article we will show you how to build a calendar in Ruby on Rails. Fortunately it is relatively easy to do so. The calendar that we build will be extendable, meaning you can add your own functionality as needed, so lets get started.

The first thing we need to do is create a calendar.rb file in our lib folder. This file will hold a class that contains the core calendar functionality. Create this new file and add in the code listed below.





lib/calendar.rb:

class Calendar < Struct.new(:view, :date, :callback)
    HEADER = %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]
    START_DAY = :sunday
 
    delegate :content_tag, to: :view
 
    def table
      content_tag :table, class: "calendar table table-bordered table-striped" do
        header + week_rows
      end
    end
 
    def header
      content_tag :tr do
        HEADER.map { |day| content_tag :th, day }.join.html_safe
      end
    end
 
    def week_rows
      weeks.map do |week|
        content_tag :tr do
          week.map { |day| day_cell(day) }.join.html_safe
        end
      end.join.html_safe
    end
 
    def day_cell(day)
      content_tag :td, view.capture(day, &callback), class: day_classes(day)
    end
 
    def day_classes(day)
      classes = []
      classes << "today" if day == Date.today
      classes << "not-month" if day.month != date.month
      classes.empty? ? nil : classes.join(" ")
    end
 
    def weeks
      first = date.beginning_of_month.beginning_of_week(START_DAY)
      last = date.end_of_month.end_of_week(START_DAY)
      (first..last).to_a.in_groups_of(7)
    end
end

The next thing we need to do is modify our application config to auto load .rb files in the lib folder. By default rails doesn't do this. Modify your application config so that it has the line listed below. Be careful not to overwrite the model name for your application as well as other parameters that may be set.

config/application.rb:

require File.expand_path('../boot', __FILE__)

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env)

module CalendarExample
  class Application < Rails::Application
    config.autoload_paths += %W(#{config.root}/lib)
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de
  end
end

The next thing we need to do is include bootstrap to give our calendar a bit of style. Open up your application layout and modify it so that it looks like the code listed below. We load bootstrap from Yandex's CDN for simplicity. Remember, if you want to use your own stylesheets for the calendar, you don't have to use bootstrap. It's used here simply to improve the visual quality.

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title>CalendarExample</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= stylesheet_link_tag "http://yandex.st/bootstrap/3.0.2/css/bootstrap.min.css", media: "all" %>
  <%= javascript_include_tag "http://yandex.st/bootstrap/3.0.2/js/bootstrap.min.js" %>
  <%= csrf_meta_tags %>
</head>
<body>
<div class="container">
<%= yield %>
</div>

</body>
</html>

The next thing we need to do is create a controller. Run the command below to create a calendar controller.

Terminal Commands:

rails g controller Calendar show

Great! Now we need to update our routes. Open up your routes file and modify it so that it looks like the code listed below, once again being careful not to overwrite your existing application's name.

config/routes.rb:

CalendarExample::Application.routes.draw do
  resource :calendar, only: [:show], controller: :calendar
  root to: "calendar#show"
end

Now lets create a helper method that will allow us to easily render the calendar. Open up your calendar helper file and modify it so that it looks like the code listed below.

app/helpers/calendar_helper.rb:

module CalendarHelper
  def calendar(date = Date.today, &block)
    Calendar.new(self, date, block).table
  end
end

Now open up your calendar controller and modify it so that it looks like the code listed below. This code sets up the date that will be used by the calendar.

app/controllers/calendar_controller.rb:

class CalendarController < ApplicationController
  def show
    @date = params[:date] ? Date.parse(params[:date]) : Date.today
  end
end

Great, now finally, open up the show view for the calendar controller and add in the following code.

app/views/calendar/show.html.erb:

<div class="row">
  <div class="col-md-12 text-center">
    <div class="well controls">
      <%= link_to calendar_path(date: @date - 1.month), class: "btn btn-default" do %>
        <i class="glyphicon glyphicon-backward"></i>
      <% end %>
      <%= "#{@date.strftime("%B")} #{@date.year}" %>
      <%= link_to calendar_path(date: @date + 1.month), class: "btn btn-default" do %>
        <i class="glyphicon glyphicon-forward"></i>
      <% end %>
    </div>
  </div>
</div>
<div class="row">
  <div class="col-md-12">
    <%= calendar @date do |date| %>
      <%= date.day %>  
    <% end %>
  </div>
</div>

Of importance to note is the last 6 lines. This actually renders the calendar using the Calendar helper method. The helper method is a block that loops through each day. What if you wanted to add events? That's easy! Assuming your event model is called event, you'd add this code to your calendar controller:


@events_by_date = Event.group_by(&:date)

Then you'd modify your calendar block so that it looks something like the code listed below.


<%= calendar @date do |date| %>
  <%= date.day %>
  <% if @events_by_date[date] %>
    <ul>
      <% @events_by_date[date].each do |event| %>
        <li><%= link_to event.name, event %></li>
      <% end %>
    </ul>
  <% end %>
<% end %>

That's it! That's all there is to it! Thanks for reading!