Module: Sequel::Plugins::Finder::ClassMethods
- Defined in:
- lib/sequel/plugins/finder.rb
Instance Method Summary collapse
-
#finder(meth = OPTS, opts = OPTS, &block) ⇒ Object
Create an optimized finder method using a dataset placeholder literalizer.
- #freeze ⇒ Object
-
#prepared_finder(meth = OPTS, opts = OPTS, &block) ⇒ Object
Similar to finder, but uses a prepared statement instead of a placeholder literalizer.
Instance Method Details
#finder(meth = OPTS, opts = OPTS, &block) ⇒ Object
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.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/sequel/plugins/finder.rb', line 103 def finder(meth=OPTS, opts=OPTS, &block) if block raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash) raise Error, "cannot pass two option hashes to Model.finder" unless opts.equal?(OPTS) opts = meth raise Error, "must provide method name via :name option when passing block to Model.finder" unless meth_name = opts[:name] end type = opts.fetch(:type, :first) unless prepare = opts[:prepare] raise Error, ":type option to Model.finder must be :first, :all, :each, or :get" unless FINDER_TYPES.include?(type) end limit1 = type == :first || type == :get meth_name ||= opts[:name] || :"#{type}_#{meth}" argn = lambda do |model| if arity = opts[:arity] arity else method = block || model.method(meth) (method.arity < 0 ? method.arity.abs - 1 : method.arity) end end loader_proc = if prepare proc do |model| args = prepare_method_args('$a', argn.call(model)) ds = if block model.instance_exec(*args, &block) else model.public_send(meth, *args) end ds = ds.limit(1) if limit1 model_name = model.name if model_name.to_s.empty? model_name = model.object_id else model_name = model_name.gsub(/\W/, '_') end ds.prepare(type, :"#{model_name}_#{meth_name}") end else proc do |model| n = argn.call(model) block ||= lambda do |pl, model2| args = (0...n).map{pl.arg} ds = model2.public_send(meth, *args) ds = ds.limit(1) if limit1 ds end model.dataset.placeholder_literalizer_class.loader(model, &block) end end @finder_loaders[meth_name] = loader_proc mod = opts[:mod] || singleton_class if prepare def_prepare_method(mod, meth_name) else def_finder_method(mod, meth_name, type) end end |
#freeze ⇒ Object
167 168 169 170 171 172 |
# File 'lib/sequel/plugins/finder.rb', line 167 def freeze @finder_loaders.freeze @finder_loaders.each_key{|k| finder_for(k)} if @dataset @finders.freeze super end |
#prepared_finder(meth = OPTS, opts = OPTS, &block) ⇒ Object
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
185 186 187 188 189 190 191 192 193 |
# File 'lib/sequel/plugins/finder.rb', line 185 def prepared_finder(meth=OPTS, opts=OPTS, &block) if block raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash) meth = meth.merge(:prepare=>true) else opts = opts.merge(:prepare=>true) end finder(meth, opts, &block) end |