module Sequel module Plugins # The lazy_attributes plugin allows users to easily set that some attributes # should not be loaded by default when loading model objects. If the attribute # is needed after the instance has been retrieved, a database query is made to # retreive the value of the attribute. # # This plugin depends on the identity_map and tactical_eager_loading plugin, and allows you to # eagerly load lazy attributes for all objects retrieved with the current object. # So the following code should issue one query to get the albums and one query to # get the reviews for all of those albums: # # Album.plugin :lazy_attributes, :review # Sequel::Model.with_identity_map do # Album.filter{id<100}.all do |a| # a.review # end # end module LazyAttributes # Tactical eager loading requires the tactical_eager_loading plugin def self.apply(model, *attrs) model.plugin :identity_map model.plugin :tactical_eager_loading end # Set the attributes given as lazy attributes def self.configure(model, *attrs) model.lazy_attributes(*attrs) unless attrs.empty? end module ClassMethods # Module to store the lazy attribute getter methods, so they can # be overridden and call super to get the lazy attribute behavior attr_accessor :lazy_attributes_module # Remove the given attributes from the list of columns selected by default. # For each attribute given, create an accessor method that allows a lazy # lookup of the attribute. Each attribute should be given as a symbol. def lazy_attributes(*attrs) set_dataset(dataset.select(*(columns - attrs))) attrs.each{|a| define_lazy_attribute_getter(a)} end private # Add a lazy attribute getter method to the lazy_attributes_module def define_lazy_attribute_getter(a) include(self.lazy_attributes_module ||= Module.new) unless lazy_attributes_module lazy_attributes_module.class_eval do define_method(a) do if !values.include?(a) && !new? lazy_attribute_lookup(a) else super() end end end end end module InstanceMethods private # If the model was selected with other model objects, eagerly load the # attribute for all of those objects. If not, query the database for # the attribute for just the current object. Return the value of # the attribute for the current object. def lazy_attribute_lookup(a) primary_key = model.primary_key model.select(*(Array(primary_key) + [a])).filter(primary_key=>retrieved_with.map{|o| o.pk}.sql_array).all if model.identity_map && retrieved_with values[a] = this.select(a).first[a] unless values.include?(a) values[a] end end end end end