Recently I needed to present data that relates to a month’s worth of appointments in Ruby. In one scenario, the user might have 6 or 7 appointments on the same day for a dozen days in the month. In another scenario they may only have 2 in a month. Either way, I wanted to show the entire month and the title of each appointment on a calendar that took up the whole screen.
I looked for plugins, but nothing seemed to fit the bill. Most of the plugins are for choosing a specific date (and time). I downloaded and installed the calendar_helper plugin, but it didn’t seem to fit the bill, either. Not until I dig in and played with it did I see that this was just what I was looking for.
Here’s how I got it to work:
First, install calendar helper
script/plugin install http://topfunky.net/svn/plugins/calendar_helper/
Then create the appointment model
script/generate model appointment name:string starts_at:datetime duration:integer
exists app/models/
exists test/unit/
... and so on
Update the database
rake db:migrate
Next, create a new controller, an index page, and a detail page
script/generate controller appointments index detail
exists app/controllers/
exists app/helpers/
create app/views/appointments
exists test/functional/
create app/controllers/appointments_controller.rb
create test/functional/appointments_controller_test.rb
create app/helpers/appointments_helper.rb
create app/views/appointments/index.html.erb
create app/views/appointments/detail.html.erb
And add it to the routes and bounce the app
map.resources :appointments
In the controller, we’ll want to set the default year and month to the current one. We’ll also want to get the list of appointments for the needed month. For simplicity’s sake, I’m just going to hard code some appointments here. They will all be for the current day.
class AppointmentsController < ApplicationController
def index
@cal_time = Time.now
@appointments = []
@appointments << Appointment.new(:name=>"Lunch with the Boss", :starts_at=>Time.now.change(:hour=>12, :minute=>0) )
@appointments << Appointment.new(:name=>"Interview - Big Corporation", :starts_at=>Time.now.change(:hour=>8, :minute=>0) )
@appointments << Appointment.new(:name=>"Interview - Hot Startup", :starts_at=>Time.now.change(:hour=>3, :minute=>30) )
end
...
In the view we will need to do two things:
- We don’t want to loop through all the appointments for every day, so in order to handle the data intelligently, we’ll make an array with an entry for each day of the month. All the appointments that fall on a particular day will be added to an array for that day. This should make sense shortly. If not, you can skip this part for now.
- We want to create a calendar with calendar_helper, and on each day we want a link to each appointment on that day.
To achieve number one here is the code
#Collect all the days into an array of arrays, so we can pull them out by day
appt_by_day = []; # Holds arrays of appointments by day of month
@appointments.each{ |apt|
day = apt.starts_at.mday
if appt_by_day[day].nil? # If this day does not have an array
appt_by_day[day] = [] # Then make one
end
appt_by_day[day].push(apt) # Add this appointment to this day
}
To achieve number two, we use the calendar helper with the block that does processing for each day
calendar(:year => @cal_time.year, :month => @cal_time.month, # 1
:previous_month_text=>"<a href='/make_this_smarter'><<</a>", #2
:next_month_text=>"<a href='/make_this_smarter'>>></a>"
) do |d|
display_string = "" #3
if !appt_by_day[d.mday].nil? #4
apt_for_day = appt_by_day[d.mday]
end
apt_for_day.each{ |a| #5
display_string += "<a href='/appointments/detail/#{a.id}'>#{a.name}</a><br>"
}
end
["<div class='dateBox'>#{d.mday}</div><br><div class='appt'>#{display_string}</div>" ] #6
end
This code does several things
- On this first line it sets the year and month for the calendar. These should be integers.
- At number 2 and the next line it sets the URL for the next and previous month links. These are left as an exercise to the user.
- This block runs for each day of the month, with the day as “d”. The display_string is the string we want to display for this day. It should contain a link to the detailed view for all the appointments for this day.
- The appt_by_day array for each day will either be nil (no appointments) or an array of appointments, since you can have several in the same day.
- The array of today’s appointments is in apt_for_day. For each of these we want to add a link to the display_string for display. Once we have the whole string we’ll send it to the helper.
- This line tells the helper what we want to display in the box. I have put the day and the display_string in different divs, just to make formatting easier.

This works, but it’s pretty ugly. The boxes for each day are too small, and there aren’t enough colors.
To make it nicer we need to install the css.
script/generate calendar_styles
This gives us some pretty css that the calendar can use. Edit the views/layout/appointments.html.erb to look more like this
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml” xml:lang=”en” lang=”en”><head>
<meta http-equiv=”content-type” content=”text/html;charset=UTF-8″ />
<title>Events: <%= controller.action_name %></title>
<%= stylesheet_link_tag ’scaffold’ %>
<%= stylesheet_link_tag ‘calendar/blue/style’ %>
</head>
<body>
<p style=”color: green”><%= flash[:notice] %></p>
<%= yield %>
</body>
</html>
I added some css details for the divs we created and got this (picture was taken the next day):

Next up will be making it as wide as the screen, adjusting the colors, and making the users happy.
Taking full advantage of the flexibility of calendar_helper allowed me to deliver a full-screen view of a month with all the calendar information within easy reach of the user.