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
When updating seeds for the Plan, I was hoping to get away with using ActiveSupport conveniences, like
The above makes sense, but this is Ruby, there's gotta be another way. How about serializing
[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
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:
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: