Set up the

I recently went to a mobile payments hackathon and worked with my Flock co-founder Reminder app. You enter the name and expiration date of the project. The application then shows you how long it takes you to complete a given task based on the Clear inspired green to red spectrum. The original

As a hackathon project, we wanted to keep the application simple. It contains a Reminder model with two attributes: 1) the name and 2) a day in the month in which you want to complete the task.

The problem

I reached a point where I wanted to sort projects by the number of days left before they were due.

If I have a column named ‘days_until_due’ in the database, I can use the order method:

Reminder.order('days_until_due ASC')

But I don’t have a database column called ‘days_until_due’……

This led me to ask myself, “What is the best way to sort models based on calculated values rather than values stored in the database?”

The solution

After a Google search I hit upon a great solution. It recommends creating an instance method to evaluate the value, and then creating a class method that combines the query with the sort_by method.

Step 1: Create an instance method to calculate the value

In reminder.rb, I created an instance method to find the number of days before the project is due, based on the day the project was supported and the current date:

def days_until_due
  today = Time.now
  simple_today = Time.new(today.year, today.month, today.day)
  if day >= today.day
    month_due = today.month
  else
    month_due = today.month + 1
  end
  day_due = Time.new(today.year,month_due,day)
  return ((day_due - simple_today)/(60*60*24)).to_i
end
Step 2: Create a class method to query and sort the model

Again in reminder.rb, I create a class method that combines a query, sort_by, with the instance method above.

def self.sorted_by_days_until_due
  Reminder.all.sort_by(&:days_until_due)
end

You might ask yourself what’s going on in this line of methods? Let’s break it down step by step. First, reminder.all returns an array (unsorted) of all reminders.

Reminder.all.class # => Array

Next, I call the sort_by method on the array. This method takes a block as an argument and generates a sorted array by mapping the values to the given block. Returns an enumerator if no block is given. Here’s an example:

array = ["Michael", "Adam", "Jen"]

array.sort_by{|word| word.length} # => ["Jen", "Adam", "Michael"] 

array.sort_by # => #<Enumerator: ["Michael", "Adam", "Jen"]:sort_by>

Now you may be asking yourself, but how does ‘& : days_until_due’ translate into blocks? It is associated with procs and blocks. If you’re not familiar with these terms, you should check out the post I wrote about these parts of Ruby. In a sentence, the ‘& : ‘syntax converts instance methods to proc, which then converts proc to a block, which is Array# sort_by as an argument.

Now I’ll make the above example look like the method I used in my reminder application.

array = ["Michael", "Adam", "Jen"]

# Passing in a block directly
array.sort_by{|word| word.length} # => ["Jen", "Adam", "Michael"] 

# Creating a proc and converting the block to a proc using the '&' syntax
proc = :length.to_proc # => #<Proc:0x007f98a225f700> 
array.sort_by(&proc) # => ["Jen", "Adam", "Michael"]

# Creating a proc and converting the block to a proc in one step
array.sort_by(&:length) # => ["Jen", "Adam", "Michael"]

The syntax above works by combining an implicit type conversion with the ‘&’ operator. The ‘&’ operator is used for parameter lists to convert Proc instances to blocks. If you combine an operator with something other than a Proc instance, the implicit conversion will attempt to convert it to a Proc instance using the to_proc method. Because of the presence of Symbol# to_proc, when we passed the symbol after the ‘&’ operator, it was converted to proc and then to block.

Back to my original question, all this talk about procs and block and type conversions has led me to create the following one-line method:

def self.sorted_by_days_until_due
  Reminder.all.sort_by(&:days_until_due)
end

This method allows me to simply include an array of ordered reminders in my view, including:

@reminders = Reminder.sorted_by_days_until_due

Now back to coding!