1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
# frozen-string-literal: true
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 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
# Album.where{id < 100}.all do |a|
# a.review
# end
#
# # You can specify multiple columns to lazily load:
# Album.plugin :lazy_attributes, :review, :tracklist
#
# Note that by default on databases that supporting RETURNING,
# using explicit column selections will cause instance creations
# to use two queries (insert and refresh) instead of a single
# query using RETURNING. You can use the insert_returning_select
# plugin to automatically use RETURNING for instance creations
# for models using the lazy_attributes plugin.
module LazyAttributes
# Lazy attributes requires the tactical_eager_loading plugin
def self.apply(model, *attrs)
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
# Freeze lazy attributes module when freezing model class.
def freeze
@lazy_attributes_module.freeze if @lazy_attributes_module
super
end
# 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)
unless select = dataset.opts[:select]
select = dataset.columns.map{|c| Sequel.qualify(dataset.first_source, c)}
end
db_schema = @db_schema
set_dataset(dataset.select(*select.reject{|c| attrs.include?(dataset.send(:_hash_key_symbol, c))}))
@db_schema = db_schema
attrs.each{|a| define_lazy_attribute_getter(a)}
end
private
# Add a lazy attribute getter method to the lazy_attributes_module. Options:
# :dataset :: The base dataset to use for the lazy attribute lookup
# :table :: The table name to use to qualify the attribute and primary key columns.
def define_lazy_attribute_getter(a, opts=OPTS)
include(@lazy_attributes_module ||= Module.new) unless @lazy_attributes_module
@lazy_attributes_module.class_eval do
define_method(a) do
if !values.has_key?(a) && !new?
lazy_attribute_lookup(a, opts)
else
super()
end
end
alias_method(a, a)
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, opts=OPTS)
table = opts[:table] || model.table_name
selection = Sequel.qualify(table, a)
if base_ds = opts[:dataset]
ds = base_ds.where(qualified_pk_hash(table))
else
base_ds = model.dataset
ds = this
end
if frozen?
return ds.get(selection)
end
if retrieved_with
primary_key = model.primary_key
composite_pk = true if primary_key.is_a?(Array)
id_map = {}
retrieved_with.each{|o| id_map[o.pk] = o unless o.values.has_key?(a) || o.frozen?}
predicate_key = composite_pk ? primary_key.map{|k| Sequel.qualify(table, k)} : Sequel.qualify(table, primary_key)
base_ds.
select(*(Array(primary_key).map{|k| Sequel.qualify(table, k)} + [selection])).
where(predicate_key=>id_map.keys).
naked.
each do |row|
obj = id_map[composite_pk ? row.values_at(*primary_key) : row[primary_key]]
if obj && !obj.values.has_key?(a)
obj.values[a] = row[a]
end
end
end
values[a] = ds.get(selection) unless values.has_key?(a)
values[a]
end
end
end
end
end
|