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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |