# frozen-string-literal: true
module Sequel
module Plugins
# The association_multi_add_remove plugin allows adding, removing and setting
# multiple associated objects in a single method call.
# By default Sequel::Model defines singular add_* and remove_*
# methods that operate on a single associated object, this adds plural forms
# that operate on multiple associated objects. Example:
#
# artist.albums # => [album1]
# artist.add_albums([album2, album3])
# artist.albums # => [album1, album2, album3]
# artist.remove_albums([album3, album1])
# artist.albums # => [album2]
# artist.albums = [album2, album3]
# artist.albums # => [album2, album3]
#
# It can handle all situations that the normal singular methods handle, but there is
# no attempt to optimize behavior, so using these methods will not improve performance.
#
# The add/remove/set methods defined by this plugin use a transaction,
# so if one add/remove/set fails and raises an exception, all adds/removes/set
# will be rolled back. If you are using database sharding and want to save
# to a specific shard, call Model#set_server to set the server for this instance,
# as the transaction will be opened on that server.
#
# You can customize the method names used for adding/removing multiple associated
# objects using the :multi_add_method and :multi_remove_method association options.
#
# Usage:
#
# # Allow adding/removing/setting multiple associated objects in a single call
# # for all model subclass instances (called before loading subclasses):
# Sequel::Model.plugin :association_multi_add_remove
#
# # Allow adding/removing/setting multiple associated objects in a single call
# # for Album instances (called before defining associations in the class):
# Album.plugin :association_multi_add_remove
module AssociationMultiAddRemove
module ClassMethods
private
# Define the methods use to add/remove/set multiple associated objects
# in a single method call.
def def_association_instance_methods(opts)
super
if opts[:adder]
add_method = opts[:add_method]
multi_add_method = opts[:multi_add_method] || :"add_#{opts[:name]}"
multi_add_method = nil if add_method == multi_add_method
if multi_add_method
association_module_def(multi_add_method, opts) do |objs, *args|
db.transaction(:server=>@server){objs.map{|obj| send(add_method, obj, *args)}.compact}
end
end
end
if opts[:remover]
remove_method = opts[:remove_method]
multi_remove_method = opts[:multi_remove_method] || :"remove_#{opts[:name]}"
multi_remove_method = nil if remove_method == multi_remove_method
if multi_remove_method
association_module_def(multi_remove_method, opts) do |objs, *args|
db.transaction(:server=>@server){objs.map{|obj| send(remove_method, obj, *args)}.compact}
end
end
end
if multi_add_method && multi_remove_method
association_module_def(:"#{opts[:name]}=", opts) do |objs, *args|
db.transaction(:server=>@server) do
existing_objs = send(opts.association_method)
send(multi_remove_method, (existing_objs - objs), *args)
send(multi_add_method, (objs - existing_objs), *args)
nil
end
end
end
end
end
end
end
end