Tuesday, July 22, 2014

May #TMIL - A Touch of eval()

In a Rails app, I've got a Plan that belongs to a Subscription. The Plan has a duration, and the Subscription has an expiration. The expiration gets set in an after_create callback using the current time (DateTime.now) plus the Plan's duration. Then, there's an expired? method on the Subscription which checks if self.expiration < DateTime.now. Pretty straightforward, right? (code snippet below)

When updating seeds for the Plan, I was hoping to get away with using ActiveSupport conveniences, like 2.weeks, for the Plan's duration. However, 2.weeks evaluates, i.e. it is not stored as "2.weeks" in the database. Storing the value of 2.weeks.to_s (1209600, or the number of seconds in two weeks) also wouldn't work, since DateTime.now + 1209600 gives an unexpected result - it adds that many days.

The above makes sense, but this is Ruby, there's gotta be another way. How about serializing 2.weeks and storing it as a lambda in the database? The seemed to work in a console:

[5] pry(main)> now = DateTime.now
=> Tue, 22 Jul 2014 17:48:52 -0400
[6] pry(main)> duration = -> {2.weeks}
=> #<Proc:0x007fc18b3a14a8@(pry):2 (lambda)>
pry(main)> now + duration.call
=> Tue, 05 Aug 2014 17:48:52 -0400

Next, I added serialize :duration into my model, and did the RAILS_ENV=test rake db:drop db:create db:migrate db:seed rigamarole (since the helper tasks for this never seem to work as thoroughly for me) ... BOOM! I pry'ed in where the Plan's were getting created and tried to manually create one, and it appears that ActiveRecord won't let you serialize a proc (or lambda, a flavor of proc). While tempting to go down the rabbit hole of why, I knew there had to be an easier way than converting all my DateTime's into seconds, and done some other conversions, etc.

The solution? Store the Plan duration as a string, then use eval(). Since the Plan's will rarely change or get newly created, and tests are in place, this seemed like an appropriate use of what many consider to be an evil method. Ahh, I love writing in Ruby. Snippet below: