Weekday select, time of day select in Rails?

2,873 views
Skip to first unread message

Andrew

unread,
Jul 12, 2011, 1:16:14 AM7/12/11
to pdxruby-beginners
Hey PDX Ruby beginners,

I hope I'm posting this in the right place. I don't want to bother the
main mailing list with such simple questions, but I'm hoping someone
with enough experience is a member of this list and can answer my
questions.

I'm using Rails to build a "weekly schedule of events". These are
recurring weekly activities, rather than a calendar of events, so I
only need to keep track of the day(s) of the week (sun-sat), and the
time of day that the activity begins. So far, I've generated some
rails scaffolding for a Group model and each group "has many"
Activities. I have a form that stores the name/type of the activity as
a string, day of the week as an integer (0-6), and time of day as a
string. I'm not sure if this is the best way to go long term, so I'm
looking for some "best practice" tips. I started building out some
form select menus for selecting the day of the week and the time of
day and couldn't find anything in rails that was already built, so I
tried building something myself. What would be the best way to create
these drop downs? Here's a bit of what I have so far:

<%= form_for(@activity) do |f| %>
<div class="field">
<%= f.label :day %><br />
<%= f.select :day, [['Sunday', 0], ['Monday', 1], ['Tuesday', 2],
['Wednesday', 3], ['Thursday', 4], ['Friday', 5], ['Saturday', 6]] %>
</div>
<% end %>

And in my view script:

<% days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday'] %>
<% @activities.each do |activity| %>
<%= days[activity.day.to_i] %>
<% end %>

It works, but it doesn't feel like the clean, DRY, "Ruby way". I'm
surprised there isn't a "day of the week select" in Rails already.
Maybe there is and I don't know about it. I can't be the first one to
do this sort of thing. I started trying to create a "time of day
select" and ran into a similar issue.

Okay, so once I've got that working, I will want to display the
activities, and group them if they are displayed on more than one day,
for example, if an activity occurs multiple days a week at the same
time (i.e. Mon-Fri at 10am) I would prefer to display it as a range
(Mon-Fri) rather than individually list all 5 days. Likewise, I will
want to list multiple activities that occur on the same day. So
anyway, there will be some things that I want to do so I can format it
nicely.

At one point, I was researching "temporal expression" libraries for
storing the recurring date data, but it just seemed like overkill. I
will also need to query the data, and I didn't think the temporal
expression would be easy to query once it was serialized in the
database. For example, I will want to know what Groups have activities
in the next few hours, this day, etc.

So I hope I didn't overwhelm you with too much. Any help would be
appreciated. Have you needed to do anything like this before? What did
you do to keep your Ruby code clean and DRY?

Thanks,
Andrew

Bryan Donovan

unread,
Jul 12, 2011, 1:31:45 AM7/12/11
to pdxruby-beginners
Yeah, there probably are built-in features for this, or third-party
gems. I did come across this:
http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-select_day

However, you don't really need to use that if you don't want. Some
options to dry this up are:

- move the day-select method to a helper method in a helper file
- store the array of day names and values in a class method or
constant somewhere, like a lib module:

module MyNamespace::DateHelper
DAYS_FOR_SELECT = [['Sunday', 0], ['Monday', 1], ['Tuesday', 2],
['Wednesday', 3], ['Thursday', 4], ['Friday', 5], ['Saturday', 6]]
end

- depending on if you need to query based on days and times, and how
granular the times need to be (e.g., 30-minute intervals?) you might
want to build some tables of days and times. I've done that in the
past with great results.

As for the day ranges in user-friendly form, I had the same question
about 5 years ago.. and submitted it to RubyQuiz. I ended up keeping
my solution since it was the fastest, but in retrospect I should have
used someone else's more readable solution. Here's the quiz:
http://www.rubyquiz.com/quiz92.html

Bryan

Shawn Balestracci

unread,
Jul 12, 2011, 1:34:01 AM7/12/11
to pdxruby-...@googlegroups.com
Hello Andrew,
There's a constant in Rails, DATE:DAYNAMES

<%= f.select :day, [['Sunday', 0], ['Monday', 1], ['Tuesday', 2],
['Wednesday', 3], ['Thursday', 4], ['Friday', 5], ['Saturday', 6]] %>

can be rewritten as

<%= f.select :day, DATE::DAYNAMES %>

You can also use it instead of creating the variable for days in the
second part.

Shawn

Sam Livingston-Gray

unread,
Jul 12, 2011, 1:16:14 PM7/12/11
to pdxruby-...@googlegroups.com
In my Rails 2.3 console, this constant appears to actually be
Date::DAYNAMES ('DATE' raises a NameError).

Note also that (again, in the 2.3 app I'm looking at), Shawn's code
produces a different set of tags than Andrew's code does:
<select id="object_field" name="object[field]">
<option value="Sunday">Sunday</option>
<option value="Monday">Monday</option>
[snip]
</select>

This is a good opportunity to point out something interesting about
Ruby: it's got a lot of functional constructs just sitting around
waiting for you to pick them up.

For example, there's Array#zip (and its sibling, Enumerable#zip),
which you could use to reconstruct that array:

<%= f.select :day, Date::DAYNAMES.zip((0..6).to_a) %>

Note that, if you're not sure how many things are in the array you're
zipping with, #zip will return a zipped array that is as long as the
thing you're calling #zip on. So you could do this and get the exact
same result:

<%= f.select :day, Date::DAYNAMES.zip((0..20).to_a) %>

I didn't really think about how many functional constructs Ruby had
borrowed (many of them, ironically, from Smalltalk) until I typed
something like...

[user.last_name, user.first_name].reject(&:blank?).join(', ')

...and the person I was pairing with said something like, "What do you
think this is, a functional language?"

Hope this helps,
-Sam

Sam Livingston-Gray

unread,
Jul 12, 2011, 1:18:58 PM7/12/11
to pdxruby-...@googlegroups.com
On Mon, Jul 11, 2011 at 10:31 PM, Bryan Donovan <bdo...@gmail.com> wrote:
> Yeah, there probably are built-in features for this, or third-party
> gems.  I did come across this:
> http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-select_day

I'd just like to point out that this, too, doesn't actually do what
Andrew was asking for -- it gives you a select box for the number of
the day of the month (i.e., in the range (1..31)), not the name of the
day of the week.

The rest of Bryan's advice was great, though. (=

-Sam

Matthew Boeh

unread,
Jul 12, 2011, 1:35:14 PM7/12/11
to pdxruby-...@googlegroups.com
Or even:

Date::DAYNAMES.each_with_index

>> puts helper.options_for_select Date::DAYNAMES.each_with_index
<option value="0">Sunday</option>
<option value="1">Monday</option>
<option value="2">Tuesday</option>
<option value="3">Wednesday</option>
<option value="4">Thursday</option>
<option value="5">Friday</option>
<option value="6">Saturday</option>

Matthew Boeh

Sam Livingston-Gray

unread,
Jul 12, 2011, 1:49:06 PM7/12/11
to pdxruby-...@googlegroups.com
Interesting. I always forget about Enumerable::Enumerator. In this
case, I did think briefly about the #map_with_index idiom, but since
that's not built in (although it is a one-liner), I decided against
mentioning it. I kinda like this code, but I always think of 'each'
as *doing* something iteratively, and 'map' as *returning* something
through a possibly-iterative process. Using #each_with_index to
return an enumerator is a perfectly reasonable thing to do... except
that its name violates that expectation (to the extent that I didn't
even think to check if you could do this).

...And now we're well off into Nerdsville, population me. ;>

Matthew Boeh

unread,
Jul 12, 2011, 1:56:21 PM7/12/11
to pdxruby-...@googlegroups.com
It works if you think of the argument to options_for_select as being
an Enumerable that yields name/value pairs, not an array of names and
values.

You could also do Date::DAYNAMES.to_enum.with_index, but I like
.each_with_index better.

The API docs for options_for_select[1] expressly indicates that you
can pass an Enumerable. The actual code uses #map.

[1]: http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-options_for_select

Matthew Boeh

Andrew

unread,
Jul 12, 2011, 11:14:35 PM7/12/11
to pdxruby-beginners
Thanks guys! You are all awesome! I am able to get the #zip example to
work:

<%= f.select :day, Date::DAYNAMES.zip((0..6).to_a) %>

I'm having a little trouble with the #each_with_index example. I tried
these and none of them worked:

<%= f.select :day, Date::DAYNAMES.each_with_index %>
<%= f.options_for_select :day, Date::DAYNAMES.each_with_index %>
<%= f.select :day, options_for_select(Date::DAYNAMES.each_with_index)
%>

I'm not familiar with the terms "enumerable" and "enumerator". Is this
similar to object iteration in PHP? (http://php.net/manual/en/
language.oop5.iterations.php)

Anyway, once I've got that part figured out, I'm wondering if there is
a way to move some of this logic out of my view and into a model or
something. Is it easy to create a custom form element/method? Is this
a common thing in Rails? Something like this:

<%= f.weekday_select :day %>

I'd also be interested in doing this sort of thing for my "time of day
select". I'm thinking of creating 3 select fields: one for selecting
the hour, one for minute, and one for AM/PM. Then when the form is
submitted, something needs to stitch these fields back into a 24 hour
time format. How can I create this sort of form element?

Thanks again for your help,
Andrew

Sam Livingston-Gray

unread,
Jul 13, 2011, 12:54:17 AM7/13/11
to pdxruby-...@googlegroups.com
It would help to know the versions of Ruby and Rails that you're
using. You can get the Ruby version by typing 'ruby --version' at the
command line, and in Rails, you should be able to type 'Rails.version'
in the console.

More inline...

> I'm not familiar with the terms "enumerable" and "enumerator". Is this
> similar to object iteration in PHP? (http://php.net/manual/en/
> language.oop5.iterations.php)

Aha! To quote a sticker floating around my office, "Welcome to Club Awesome."

Enumerable is a mixin module in Ruby. Documentation for it is here:
http://ruby-doc.org/core/classes/Enumerable.html

Basically, if you have some kind of collection class (let's say you
feel the need to write a binary tree in Ruby), and that class is able
to provide an #each method that takes a block and yields each member
of the collection, then you can include the Enumerable module in your
class, and you get about thirty extra methods for free: things like
#any?, #map, #each_cons, and so on. (That last one, by the way, is
short for "each consecutive" and is really useful if you need to do
pairwise comparisons of stuff in a collection.)

Now, because Ruby lets you throw around blocks, and that's an
incredibly powerful construct, you can get really far with just #each
and #map and so on. But sometimes it's more convenient, or maybe just
reads better, to ask a collection for an Enumerator, and then use that
Enumerator to get members of the collection whenever you're ready for
them. If you're familiar with the concept of cursors in SQL, this is
vaguely like that; there's a construct like this that gets used all
over the place in Java. An Enumerator has a #next method on it, which
(according to the docs) will do one of two things when called: (1)
Give you the next element in the collection that it came from, or (2)
raise a StopIteration exception when it doesn't have anything else to
give you.

Check out the docs for more details on how to use these, but I hope
that gives you the basic idea.

> Anyway, once I've got that part figured out, I'm wondering if there is
> a way to move some of this logic out of my view and into a model or
> something. Is it easy to create a custom form element/method? Is this
> a common thing in Rails? Something like this:
>
> <%= f.weekday_select :day %>

You can do this by subclassing ActionView::Helpers::FormBuilder and
defining a #weekday_select method on it. You can then tell the
#form_for method to use your subclass instead of the basic one, or you
can define a helper method that does that for you. This is one of the
recipes in the book "Rails Recipes"; if you're downtown, I've got a
copy at my office that I'd be happy to lend out. (Caveat: the book
dates back to Rails 1.x, so some of its code requires adaptation. I
think this part of Rails has been fairly stable, though.) More
information on that class here:
http://apidock.com/rails/ActionView/Helpers/FormBuilder

> I'd also be interested in doing this sort of thing for my "time of day
> select". I'm thinking of creating 3 select fields: one for selecting
> the hour, one for minute, and one for AM/PM. Then when the form is
> submitted, something needs to stitch these fields back into a 24 hour
> time format. How can I create this sort of form element?

This one gets a little bit tricky. You can DIY by using the *_tag
helpers and crafting your tag names carefully; Rails' controller
plumbing will use the field names to build the params hash. You
should be able to reverse-engineer this from one of the existing
date-picker controls -- which is usually what I do, since I don't work
with forms often enough to keep the magic pattern memorized. ;>
Then, in your model, you'll need to write a #time_of_day= method that
accepts the data structure accessible via
params[:object][:time_of_day] and uses that to build a Time object.

I realize that last is a bit handwavy, but I'm hungry and tired. (=
I'm planning to be at next week's beginner meetup; if you haven't
figured this out by then, I'd be happy to work through it with you.

-Sam

Matthew Boeh

unread,
Jul 13, 2011, 10:11:02 AM7/13/11
to pdxruby-...@googlegroups.com
Have you tried looking for a plugin that provides the functionality
you need? A little Googling brought up this:

https://github.com/attinteractive/rails-twelve-hour-time-plugin

Matthew Boeh

Andrew

unread,
Jul 14, 2011, 12:31:34 AM7/14/11
to pdxruby-beginners
FYI: Ruby 1.9.2, Rails 3.0.7

> Have you tried looking for a plugin...
> https://github.com/attinteractive/rails-twelve-hour-time-plugin

That looks very promising (and exactly what I had in mind). However,
it hasn't been updated in a while and I'm not sure that it's Rails 3
compatible. After I installed it, I ran into a bug. Not sure if it was
the plugin or me. The form element displayed correctly, but when I
went to save the form I got this error:

1 error(s) on assignment of multiparameter attributes

Here's a bit of the stack trace if that helps:

activerecord (3.0.7) lib/active_record/base.rb:1808:in
`execute_callstack_for_multiparameter_attributes'
activerecord (3.0.7) lib/active_record/base.rb:1763:in
`assign_multiparameter_attributes'
activerecord (3.0.7) lib/active_record/base.rb:1563:in `attributes='
activerecord (3.0.7) lib/active_record/base.rb:1407:in `initialize'
activerecord (3.0.7) lib/active_record/associations/
association_collection.rb:444:in `new'
activerecord (3.0.7) lib/active_record/associations/
association_collection.rb:444:in `block in method_missing'
activerecord (3.0.7) lib/active_record/base.rb:1122:in `with_scope'
activerecord (3.0.7) lib/active_record/associations/
association_proxy.rb:207:in `with_scope'
activerecord (3.0.7) lib/active_record/associations/
association_collection.rb:440:in `method_missing'
app/controllers/my_controller.rb:48:in `create'
Reply all
Reply to author
Forward
0 new messages