Monday, February 17, 2014

Buidling Ranges of Date/Time Intervals in Ruby

There are a lot of features that define a good programming language. One of them has to be how it enables a developer to represent and manipulate date and time values. As usual, Ruby has a class (actually several) that abstract the representation of date/time and provide a nice set of methods to manipulate it. Adding in Active Support provides even more functionality. My latest challenge was to divide a period of time into a series of even intervals. Using the built in methods seemed like the right path but I quickly experienced some difficulties:

> 1.week.from_now ).to_a
=> []

After a closer examination of the documentation and I realize that the limit needed to be a Date object, not DateTime. As it turns out, this mix-and-match typing has caused me the most issues when working with dates and times in Ruby. Either you get an error or the results are not what you expected. Based on the documentation on the step function, it should default to an interval of one which I assumed meant one day. Once I switched to the right type, that's what happened:

> 1.week.from_now.to_date ).to_a
=> [Sat, 15 Feb 2014 07:52:23 -1000, 
    Sun, 16 Feb 2014 07:52:23 -1000, 
    Mon, 17 Feb 2014 07:52:23 -1000, 
    Tue, 18 Feb 2014 07:52:23 -1000, 
    Wed, 19 Feb 2014 07:52:23 -1000, 
    Thu, 20 Feb 2014 07:52:23 -1000, 
    Fri, 21 Feb 2014 07:52:23 -1000]

So, what if you want a different time interval? Active Support provides a convenient way to express time, but its not the expected type that the step function wants:

> 1.month.from_now.to_date, 1.week ).to_a
TypeError: expected numeric
    (ripl):40:in `step'
    (ripl):40:in `each'
    (ripl):40:in `to_a'
    (ripl):40:in `

However, even converting the type still doesn't make it work as expected:

> 1.month.from_now.to_date, 1.week.to_i ).to_a
=> [Sat, 15 Feb 2014 07:54:36 -1000]

Remembering that time is stored internally as seconds made me realize that the number being passed was seconds, not days so I added some math to the process:

>> 1.month.from_now.to_date, 1.week / ).to_a
=> [Sat, 15 Feb 2014 07:55:04 -1000, 
    Sat, 22 Feb 2014 07:55:04 -1000, 
    Sat, 01 Mar 2014 07:55:04 -1000, 
    Sat, 08 Mar 2014 07:55:04 -1000]

Now, that enables me to work with intervals over days. But what if I want to create interval ranges during a day in seconds, minutes, or hours? When I try to divide a time value by, the output doesn't make sense:
>> ( ).step( 12.hour.to_f / ).to_a.length
=> 7201

I should get an empty array since I want to step every 12 hours over a 1 hour range. Instead, I get 7201 numbers (not even dates). It appears the Date step function functionality wasn't going to work, but the Range type also has a step function and the above expression can be represented as a Range:

>> ( ).step( 1.week / ).to_a

Which, when coupled with converting to the base representation of time (ie seconds), we can simply work with numbers to build our ranges:

>> ( ).step( 10.minutes ).to_a
=> [1392471205, 1392471805, 1392472405, 1392473005, 1392473605, 1392474205, 1392474805]

And instead of leaving them as numbers, we can switch them back to a DateTime object via the Time::at class method:

>> ( ).step( 10.minutes ).
              map{ |t| t ).to_datetime }

=> [Sun, 16 Feb 2014 07:02:54 -0600, 
    Sun, 16 Feb 2014 07:12:54 -0600, 
    Sun, 16 Feb 2014 07:22:54 -0600, 
    Sun, 16 Feb 2014 07:32:54 -0600, 
    Sun, 16 Feb 2014 07:42:54 -0600, 
    Sun, 16 Feb 2014 07:52:54 -0600, 
    Sun, 16 Feb 2014 08:02:54 -0600]

Unfortunately, we lose some information by switching types like the time zone. Once you convert to a numeric and then back to a Date object, you'll be back in local time, which may not be the zone you started in. If its important to keep that, you'll have to store it before iterating and then restore it on each step:

>> from_tm ='-08:00')
>> to_tm = from_tm + 1.hour
>> tm_zone = from_tm.gmt_offset

>> ( from_tm.to_i..to_tm.to_i ).step( 10.minutes ).
            map{ |t| t ).localtime( tm_zone ).to_datetime }

=> [Sun, 16 Feb 2014 04:01:46 -0800, 
    Sun, 16 Feb 2014 04:11:46 -0800, 
    Sun, 16 Feb 2014 04:21:46 -0800, 
    Sun, 16 Feb 2014 04:31:46 -0800, 
    Sun, 16 Feb 2014 04:41:46 -0800, 
    Sun, 16 Feb 2014 04:51:46 -0800, 
    Sun, 16 Feb 2014 05:01:46 -0800]

With a little experimenting, you can very quickly express a range of time and step through a certain intervals between those points. Instead of building an array, you might perform a series of more complex operations on the generated values. Maybe the biggest issue with some of these examples is the loss of abstraction of the representation of the date/time. The last set of examples for creating time ranges requires you to toss out the DateTime object and directly manipulate an integer representation of the time. It would be nice if the DateTime#step function could take any type of date object as a limit and and properly apply steps of less than a day without coercing the types of any inputs. Depending on your needs, it might make sense to build a few utility functions or classes to assist with these types of operations.

Sunday, February 9, 2014

Backbone Sync: Customizing JSON Payload Options for Persistence Operations

Backbone Models provide a reasonably light-weight layer over raw AJAX calls to make it easier to define data objects that are acquired and persisted using RESTful services. As I've been working Backbone into more complex projects, I've noticed a pattern emerging that requires some minor tweaks to the default persisting behavior provided by the library. Fortunately, Backbone is very flexible and augmenting these features is not exceptionally difficult. Let's begin with a little context on a specific problem. Consider a simple model:

var myModel = Backbone.Model.extend({
   defaults: {
      id: null,
      color: 'blue',
      size: 'big',      


A Model.fetch() call may return more data than what is specified in the defaults like created_on and created_by, where the former is a date stamp set on the server side when the record was originally added and the latter is a person, again, set server-side based on the logged in user. The contents of the created_by attribute could be a hash containing additional details about the user beyond just a name or ID value. These extra fields are useful for rendering information on the screen, but are not required to persist the model back to the server. In the case of the created_by attribute, packing the extra detail into the read operation saves a round trip to the server to fetch the related record. However, the default behavior when Backbone saves the data is to include all the attributes in the model. On more complex models, this can result in fairly large PUT payloads. Not only could they become relatively large, they also become difficult to debug since there is so much extra data that will simply be ignored by the server. My goal is to try to slim down the payload being sent back to the server even if the read operation pulled some extra data.