LazyGenerate: How to lazily generate properties in ActiveRecord

At times in the Posterous days we wanted to add new properties but a migration would be prohibitively expensive given a large corpus of data.

Here's a simple module you can include in your ActiveRecord objects to do just that — lazily generate potentially costly computation and save it to an object only when it is actually called. 

Dragons in these hills — but sometimes you want that. We're going to steer clear of these kinds of hacks for Posthaven, but as a minor exercise in metaprogramming maybe this'll be useful in the future:

# LazyGenerate makes it easy to make new ActiveRecord objects with accessors
# that are generated and saved lazily
#
# To use, add this to your ActiveRecord class:
# extend LazyGenerate
# lazy_generate :my_lazy_property, :generate_lazy_property
#
# Side effect: Save is called on the object upon generation.
#
module LazyGenerate
def lazy_generate(column, generate_method)
class_eval <<-RUBY, __FILE__, __LINE__+1
def #{column}_with_laziness
if existing_value = #{column}_without_laziness
existing_value
else
self.#{column} = new_value = #{generate_method}
save
new_value
end
end
alias_method_chain :#{column}, :laziness
RUBY
end
end
require 'spec_helper'
describe LazyGenerate do
describe '.lazy_generate' do
class LazyGenerateTest
extend LazyGenerate
attr_accessor :value
def generator
'xyz987'
end
def save; end
lazy_generate :value, :generator
end
context 'with an existing value' do
it 'should pass the value if already set' do
obj = LazyGenerateTest.new
obj.value = 'abc123'
obj.value.should eql 'abc123'
end
end
context 'with an existing value' do
it 'should generate the value if not already set' do
LazyGenerateTest.new.value.should eql 'xyz987'
end
end
end
end

views