module Sequel module Plugins # The AssociationDependencies 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 - :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} # The valid dependence actions DEPENDENCE_ACTIONS = [:delete, :destroy, :nullify] # Initialize the association_dependencies hash for this model. def self.apply(model, hash={}) model.instance_eval{@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={}) 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) raise(Error, "Invalid dependence action type: association: #{association}, dependence action: #{action}") unless DEPENDENCE_ACTIONS.include?(action) raise(Error, "Invalid association type: association: #{association}, type: #{r[:type]}") unless time = ASSOCIATION_MAPPING[r[:type]] association_dependencies[:"#{time}_#{action}"] << if action == :nullify raise(Error, "Can't nullify many_to_one associated objects: association: #{association}") if r[:type] == :many_to_one r.remove_all_method else raise(Error, "Can only nullify many_to_many associations: association: #{association}") if r[:type] == :many_to_many r.dataset_method end end end # Copy the current model object's association_dependencies into the subclass. def inherited(subclass) super ad = association_dependencies.dup ad.keys.each{|k| ad[k] = ad[k].dup} subclass.instance_variable_set(:@association_dependencies, ad) end 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| send(m).delete} model.association_dependencies[:after_destroy].each{|m| 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| send(m).delete} model.association_dependencies[:before_destroy].each{|m| send(m).destroy} model.association_dependencies[:before_nullify].each{|m| send(m)} super end end end end end