lib/sequel/plugins/association_proxies.rb in sequel-3.47.0 vs lib/sequel/plugins/association_proxies.rb in sequel-3.48.0
- old
+ new
@@ -4,45 +4,101 @@
# method for *_to_many associations returns an array, and the association_dataset
# method returns a dataset. This plugin makes the association method return a proxy
# that will load the association and call a method on the association array if sent
# an array method, and otherwise send the method to the association's dataset.
#
+ # You can override which methods to forward to the dataset by passing a block to the plugin:
+ #
+ # plugin :association_proxies do |opts|
+ # [:find, :where, :create].include?(opts[:method])
+ # end
+ #
+ # If the block returns false or nil, the method is sent to the array of associated
+ # objects. Otherwise, the method is sent to the association dataset. Here are the entries
+ # in the hash passed to the block:
+ #
+ # :method :: The name of the method
+ # :arguments :: The arguments to the method
+ # :block :: The block given to the method
+ # :instance :: The model instance related to the call
+ # :reflection :: The reflection for the association related to the call
+ # :proxy_argument :: The argument given to the association method call
+ # :proxy_block :: The block given to the association method call
+ #
+ # For example, in a call like:
+ #
+ # artist.albums(1){|ds| ds}.foo(2){|x| 3}
+ #
+ # The opts passed to the block would be:
+ #
+ # {
+ # :method => :foo,
+ # :arguments => [2],
+ # :block => {|x| 3},
+ # :instance => artist,
+ # :reflection => {:name=>:albums, ...},
+ # :proxy_argument => 1,
+ # :proxy_block => {|ds| ds}
+ # }
+ #
# Usage:
#
# # Use association proxies in all model subclasses (called before loading subclasses)
# Sequel::Model.plugin :association_proxies
#
# # Use association proxies in a specific model subclass
# Album.plugin :association_proxies
module AssociationProxies
+ def self.configure(model, &block)
+ model.instance_eval do
+ @association_proxy_to_dataset = block if block
+ @association_proxy_to_dataset ||= AssociationProxy::DEFAULT_PROXY_TO_DATASET
+ end
+ end
+
# A proxy for the association. Calling an array method will load the
# associated objects and call the method on the associated object array.
# Calling any other method will call that method on the association's dataset.
class AssociationProxy < BasicObject
- # Empty array used to check if an array responds to the given method.
- ARRAY = []
+ array = []
+ # Default proc used to determine whether to sent the method to the dataset.
+ # If the array would respond to it, sends it to the array instead of the dataset.
+ DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
+
# Set the association reflection to use, and whether the association should be
# reloaded if an array method is called.
- def initialize(instance, reflection, reload=nil)
+ def initialize(instance, reflection, proxy_argument, &proxy_block)
@instance = instance
@reflection = reflection
- @reload = reload
+ @proxy_argument = proxy_argument
+ @proxy_block = proxy_block
end
# Call the method given on the array of associated objects if the method
# is an array method, otherwise call the method on the association's dataset.
def method_missing(meth, *args, &block)
- (ARRAY.respond_to?(meth) ? @instance.send(:load_associated_objects, @reflection, @reload) : @instance.send(@reflection.dataset_method)).
- send(meth, *args, &block)
+ v = if @instance.model.association_proxy_to_dataset.call(:method=>meth, :arguments=>args, :block=>block, :instance=>@instance, :reflection=>@reflection, :proxy_argument=>@proxy_argument, :proxy_block=>@proxy_block)
+ @instance.send(@reflection.dataset_method)
+ else
+ @instance.send(:load_associated_objects, @reflection, @proxy_argument, &@proxy_block)
+ end
+ v.send(meth, *args, &block)
end
end
module ClassMethods
+ # Proc that accepts a method name, array of arguments, and block and
+ # should return a truthy value to send the method to the dataset instead of the
+ # array of associated objects.
+ attr_reader :association_proxy_to_dataset
+
+ Plugins.inherited_instance_variables(self, :@association_proxy_to_dataset=>nil)
+
# Changes the association method to return a proxy instead of the associated objects
# directly.
def def_association_method(opts)
- opts.returns_array? ? association_module_def(opts.association_method, opts){|*r| AssociationProxy.new(self, opts, r[0])} : super
+ opts.returns_array? ? association_module_def(opts.association_method, opts){|*r, &block| AssociationProxy.new(self, opts, r[0], &block)} : super
end
end
end
end
end