module Concerns # Wraps methods to provide lazy attribute evaluation # If a value evaluates to a proc, the proc is called, and the value # is returned # # @example # class Foo # include Concerns::LazyAttributes # # attr_accessor :bar # lazy_attributes :bar # end # # foo = Foo.new # foo.bar = lambda{ :some_value } # foo.bar == :some_value module LazyAttributes extend ActiveSupport::Concern module ClassMethods # Makes the list of attributes lazy. # If the value of the attribute is a proc, # then it will be evaluated on the first time the method is called def lazy_attributes(*attrs) attrs.each do |attr| var_sym = :"@lazy_attribute_#{attr}" define_method "#{attr}_with_lazy_attribute" do if instance_variable_defined?(var_sym) return instance_variable_get(var_sym) end val = send("#{attr}_without_lazy_attribute") val = Proc === val ? val.call : val instance_variable_set(var_sym, val) val end alias_method_chain attr, :lazy_attribute # update the setter so that we can clear the lazy value if public_instance_methods.include?(:"#{attr}=") define_method "#{attr}_with_lazy_attribute=" do |val| if instance_variable_defined?(var_sym) remove_instance_variable(var_sym) end send "#{attr}_without_lazy_attribute=", val end alias_method_chain "#{attr}=", :lazy_attribute end end end end end end