On Bike2020.com, we needed a select box to allow dealers to enter the model year of a bike. The select box should have upcoming model year through 15 years in the past, descending.
We are using the simple_form gem with our project, so our first try went something like this.
<%= simple_form_for @bike do |f| %> <%= f.input :year, collection: ((Date.today.year + 1)..(Date.today.year - 15)) %> <% end %>
Unfortunately, that gave us an empty drop-down.
To make sure our range was generating correctly, we jumped into the
rails console and ran our command.
irb(main):001:0> ((Date.today.year + 1)..(Date.today.year - 15)) => 2015..1999
That looked ok, but why were we not getting any results? We ran
#to_a to see if we could get an array of numbers that count down.
irb(main):002:0> ((Date.today.year + 1)..(Date.today.year - 15)).to\_a => 
That wasn’t working either. Time to look up the Ruby Range documentation. Under the Custom Objects in Ranges section, there was an interesting section about the interface you have to define in order to use a range with a custom object.
Methods that treat the range as a sequence (#each and methods inherited from Enumerable) expect the begin object to implement a
#succmethod to return the next object in sequence.
Since it talked about a method to get the next object in sequence, but not the previous, we wondered if ranges couldn’t count backwards. We decided to double-check and make sure we could build a range that counts up. For simplicity’s sake, we just hard-coded the years.
irb(main):003:0> (2015..1999).to_a # Counting down with hardcoded years =>  irb(main):004:0> (1999..2015).to_a # Counting up with hardcoded years => [1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015]
Ah hah! It’s true that Ruby Ranges only count up.
The Count Down
After a bit of searching, we found that Integer#downto that should to do the trick. You call it on the top number, and pass in the number it should count down to.
irb(main):005:0> 2015.downto(1999).to_a => [2015, 2014, 2013, 2012, 2011, 2010, 2009, 2008, 2007, 2006, 2005, 2004, 2003, 2002, 2001, 2000, 1999]
Sweet! That did it. We ran one more time using the correct logic to get the current year.
irb(main):010:0> (Time.now.year + 1).downto(Time.now.year - 15).to_a => [2015, 2014, 2013, 2012, 2011, 2010, 2009, 2008, 2007, 2006, 2005, 2004, 2003, 2002, 2001, 2000, 1999]
Now that we figured out how to build a collection of decending years, we finished things up by putting the code into our form.
<%= simple_form_for @bike do |f| %> <%= f.input :year, collection: (Date.today.year + 1).downto(Date.today.year - 15)) %> <% end %>
And here was the result.
In going through this process, we learned several things.
First, Ruby’s Range class only counts up, not down. That’s because
the interface only defines a
#succ method to get the next value, and not a method to get the previous value.
rails console can be very helpful in doing troubleshooting to figure out why a bit of code doesn’t work. By copying the code in question to a rails console, we were able to try several different variations and easily see the results.
Integer#downto is a great
built-in method for counting backwards between two integers. Although it doesn’t technically return a
Range, it returns an
Enumerator which acts the same as a
Range. Once again Ruby shines by having built in methods
to make your life easier. RubyTapas is a great screencast series that shows
you simple tricks like this. The key is just knowing they exist.
We’d love to have your feedback on this article. If you have any comments or questions, please contact us on Twitter.
Originally published at blog.animascodelabs.com on September 20, 2014.