module Sequel::Plugins::Finder::ClassMethods
Public Instance Methods
Source
# File lib/sequel/plugins/finder.rb 103 def finder(meth=OPTS, opts=OPTS, &block) 104 if block 105 raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash) 106 raise Error, "cannot pass two option hashes to Model.finder" unless opts.equal?(OPTS) 107 opts = meth 108 raise Error, "must provide method name via :name option when passing block to Model.finder" unless meth_name = opts[:name] 109 end 110 111 type = opts.fetch(:type, :first) 112 unless prepare = opts[:prepare] 113 raise Error, ":type option to Model.finder must be :first, :all, :each, or :get" unless FINDER_TYPES.include?(type) 114 end 115 limit1 = type == :first || type == :get 116 meth_name ||= opts[:name] || :"#{type}_#{meth}" 117 118 argn = lambda do |model| 119 if arity = opts[:arity] 120 arity 121 else 122 method = block || model.method(meth) 123 (method.arity < 0 ? method.arity.abs - 1 : method.arity) 124 end 125 end 126 127 loader_proc = if prepare 128 proc do |model| 129 args = prepare_method_args('$a', argn.call(model)) 130 ds = if block 131 model.instance_exec(*args, &block) 132 else 133 model.public_send(meth, *args) 134 end 135 ds = ds.limit(1) if limit1 136 model_name = model.name 137 if model_name.to_s.empty? 138 model_name = model.object_id 139 else 140 model_name = model_name.gsub(/\W/, '_') 141 end 142 ds.prepare(type, :"#{model_name}_#{meth_name}") 143 end 144 else 145 proc do |model| 146 n = argn.call(model) 147 block ||= lambda do |pl, model2| 148 args = (0...n).map{pl.arg} 149 ds = model2.public_send(meth, *args) 150 ds = ds.limit(1) if limit1 151 ds 152 end 153 154 model.dataset.placeholder_literalizer_class.loader(model, &block) 155 end 156 end 157 158 @finder_loaders[meth_name] = loader_proc 159 mod = opts[:mod] || singleton_class 160 if prepare 161 def_prepare_method(mod, meth_name) 162 else 163 def_finder_method(mod, meth_name, type) 164 end 165 end
Create an optimized finder method using a dataset placeholder literalizer. This pre-computes the SQL
to use for the query, except for given arguments.
There are two ways to use this. The recommended way is to pass a symbol that represents a model class method that returns a dataset:
def Artist.by_name(name) where(name: name) end Artist.finder :by_name
This creates an optimized first_by_name method, which you can call normally:
Artist.first_by_name("Joe")
The alternative way to use this to pass your own block:
Artist.finder(name: :first_by_name){|pl, ds| ds.where(name: pl.arg).limit(1)}
Note that if you pass your own block, you are responsible for manually setting limits if necessary (as shown above).
Options:
- :arity
-
When using a symbol method name, this specifies the arity of the method. This should be used if if the method accepts an arbitrary number of arguments, or the method has default argument values. Note that if the method is defined as a dataset method, the class method
Sequel
creates accepts an arbitrary number of arguments, so you should use this option in that case. If you want to handle multiple possible arities, you need to call the finder method multiple times with unique :arity and :name methods each time. - :name
-
The name of the method to create. This must be given if you pass a block. If you use a symbol, this defaults to the symbol prefixed by the type.
- :mod
-
The module in which to create the finder method. Defaults to the singleton class of the model.
- :type
-
The type of query to run. Can be :first, :each, :all, or :get, defaults to :first.
Caveats:
This doesn’t handle all possible cases. For example, if you have a method such as:
def Artist.by_name(name) name ? where(name: name) : exclude(name: nil) end
Then calling a finder without an argument will not work as you expect.
Artist.finder :by_name Artist.by_name(nil).first # WHERE (name IS NOT NULL) Artist.first_by_name(nil) # WHERE (name IS NULL)
See Dataset::PlaceholderLiteralizer
for additional caveats. Note that if the model’s dataset does not support placeholder literalizers, you will not be able to use this method.
Source
# File lib/sequel/plugins/finder.rb 167 def freeze 168 @finder_loaders.freeze 169 @finder_loaders.each_key{|k| finder_for(k)} if @dataset 170 @finders.freeze 171 super 172 end
Source
# File lib/sequel/plugins/finder.rb 185 def prepared_finder(meth=OPTS, opts=OPTS, &block) 186 if block 187 raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash) 188 meth = meth.merge(:prepare=>true) 189 else 190 opts = opts.merge(:prepare=>true) 191 end 192 finder(meth, opts, &block) 193 end
Similar to finder, but uses a prepared statement instead of a placeholder literalizer. This makes the SQL
used static (cannot vary per call), but allows binding argument values instead of literalizing them into the SQL
query string.
If a block is used with this method, it is instance_execed by the model, and should accept the desired number of placeholder arguments.
The options are the same as the options for finder, with the following exception:
- :type
-
Specifies the type of prepared statement to create
Private Instance Methods
Source
# File lib/sequel/plugins/finder.rb 201 def def_finder_method(mod, meth, type) 202 mod.send(:define_method, meth){|*args, &block| finder_for(meth).public_send(type, *args, &block)} 203 end
Define a finder method in the given module with the given method name that load rows using the finder with the given name.
Source
# File lib/sequel/plugins/finder.rb 207 def def_prepare_method(mod, meth) 208 mod.send(:define_method, meth){|*args, &block| finder_for(meth).call(prepare_method_arg_hash(args), &block)} 209 end
Define a prepared_finder
method in the given module that will call the associated prepared statement.
Source
# File lib/sequel/plugins/finder.rb 214 def finder_for(meth) 215 unless finder = (frozen? ? @finders[meth] : Sequel.synchronize{@finders[meth]}) 216 finder_loader = @finder_loaders.fetch(meth) 217 finder = finder_loader.call(self) 218 Sequel.synchronize{@finders[meth] = finder} 219 end 220 finder 221 end
Find the finder to use for the give method. If a finder has not been loaded for the method, load the finder and set correctly in the finders hash, then return the finder.
Source
# File lib/sequel/plugins/finder.rb 225 def prepare_method_arg_hash(args) 226 h = {} 227 prepare_method_args('a', args.length).zip(args).each{|k, v| h[k] = v} 228 h 229 end
An hash of prepared argument values for the given arguments, with keys starting at a. Used by the methods created by prepared_finder.
Source
# File lib/sequel/plugins/finder.rb 232 def prepare_method_args(base, n) 233 (0...n).map do 234 s = base.to_sym 235 base = base.next 236 s 237 end 238 end
An array of prepared statement argument names, of length n and starting with base.
Source
# File lib/sequel/plugins/finder.rb 241 def reset_instance_dataset 242 Sequel.synchronize{@finders.clear} if @finders && !@finders.frozen? 243 super 244 end
Clear any finders when reseting the instance dataset