module Sequel::Plugins::Finder::ClassMethods

Public Instance Methods

finder(meth=OPTS, opts=OPTS, &block) click to toggle source

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.

    # 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
freeze() click to toggle source
Calls superclass method
    # 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
prepared_finder(meth=OPTS, opts=OPTS, &block) click to toggle source

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

    # 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

Private Instance Methods

def_finder_method(mod, meth, type) click to toggle source

Define a finder method in the given module with the given method name that load rows using the finder with the given name.

    # 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
def_prepare_method(mod, meth) click to toggle source

Define a prepared_finder method in the given module that will call the associated prepared statement.

    # 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
finder_for(meth) click to toggle source

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.

    # 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
prepare_method_arg_hash(args) click to toggle source

An hash of prepared argument values for the given arguments, with keys starting at a. Used by the methods created by prepared_finder.

    # 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
prepare_method_args(base, n) click to toggle source

An array of prepared statement argument names, of length n and starting with base.

    # 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
reset_instance_dataset() click to toggle source

Clear any finders when reseting the instance dataset

Calls superclass method
    # File lib/sequel/plugins/finder.rb
241 def reset_instance_dataset
242   Sequel.synchronize{@finders.clear} if @finders && !@finders.frozen?
243   super
244 end