1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
|
# frozen-string-literal: true
module Sequel
module Plugins
# The association_dependencies plugin allows you do easily set up before and/or after destroy hooks
# for destroying, deleting, or nullifying associated model objects. The following
# association types support the following dependency actions:
#
# :many_to_many :: :nullify (removes all related entries in join table)
# :many_to_one :: :delete, :destroy
# :one_to_many, one_to_one :: :delete, :destroy, :nullify (sets foreign key to NULL for all associated objects)
#
# This plugin works directly with the association datasets and does not use any cached association values.
# The :delete action will delete all associated objects from the database in a single SQL call.
# The :destroy action will load each associated object from the database and call the destroy method on it.
#
# To set up an association dependency, you must provide a hash with association name symbols
# and dependency action values. You can provide the hash to the plugin call itself or
# to the add_association_dependencies method:
#
# Business.plugin :association_dependencies, address: :delete
# # or:
# Artist.plugin :association_dependencies
# Artist.add_association_dependencies albums: :destroy, reviews: :delete, tags: :nullify
module AssociationDependencies
# Mapping of association types to when the dependency calls should be made (either
# :before for in before_destroy or :after for in after_destroy)
ASSOCIATION_MAPPING = {:one_to_many=>:before, :many_to_one=>:after, :many_to_many=>:before, :one_to_one=>:before}.freeze
# The valid dependence actions
DEPENDENCE_ACTIONS = [:delete, :destroy, :nullify].freeze
# Initialize the association_dependencies hash for this model.
def self.apply(model, hash=OPTS)
model.instance_exec{@association_dependencies = {:before_delete=>[], :before_destroy=>[], :before_nullify=>[], :after_delete=>[], :after_destroy=>[]}}
end
# Call add_association_dependencies with any dependencies given in the plugin call.
def self.configure(model, hash=OPTS)
model.add_association_dependencies(hash) unless hash.empty?
end
module ClassMethods
# A hash specifying the association dependencies for each model. The keys
# are symbols indicating the type of action and when it should be executed
# (e.g. :before_delete). Values are an array of method symbols.
# For before_nullify, the symbols are remove_all_association methods. For other
# types, the symbols are association_dataset methods, on which delete or
# destroy is called.
attr_reader :association_dependencies
# Add association dependencies to this model. The hash should have association name
# symbol keys and dependency action symbol values (e.g. albums: :destroy).
def add_association_dependencies(hash)
hash.each do |association, action|
raise(Error, "Nonexistent association: #{association}") unless r = association_reflection(association)
type = r[:type]
raise(Error, "Invalid dependence action type: association: #{association}, dependence action: #{action}") unless DEPENDENCE_ACTIONS.include?(action)
raise(Error, "Invalid association type: association: #{association}, type: #{type}") unless time = ASSOCIATION_MAPPING[type]
association_dependencies[:"#{time}_#{action}"] << if action == :nullify
case type
when :one_to_many , :many_to_many
[r[:remove_all_method]]
when :one_to_one
[r[:setter_method], nil]
else
raise(Error, "Can't nullify many_to_one associated objects: association: #{association}")
end
else
raise(Error, "Can only nullify many_to_many associations: association: #{association}") if type == :many_to_many
r[:dataset_method]
end
end
end
# Freeze association dependencies when freezing model class.
def freeze
@association_dependencies.freeze.each_value(&:freeze)
super
end
Plugins.inherited_instance_variables(self, :@association_dependencies=>:hash_dup)
end
module InstanceMethods
# Run the delete and destroy association dependency actions for
# many_to_one associations.
def after_destroy
super
model.association_dependencies[:after_delete].each{|m| public_send(m).delete}
model.association_dependencies[:after_destroy].each{|m| public_send(m).destroy}
end
# Run the delete, destroy, and nullify association dependency actions for
# *_to_many associations.
def before_destroy
model.association_dependencies[:before_delete].each{|m| public_send(m).delete}
model.association_dependencies[:before_destroy].each{|m| public_send(m).destroy}
model.association_dependencies[:before_nullify].each{|args| public_send(*args)}
super
end
end
end
end
end
|