module Scrivito class WidgetGarbageCollection # @param [BasicObj] obj the Obj that the update applies to # @param [Hash] update the keys are content objects (Obj or Widget) # and the values are their update params. # update params are accepted in the same format as accepted by Obj.create or Obj#update. # each content object must either be the obj itself or a widget that lives inside the obj. def initialize(obj, update) if update.blank? raise ScrivitoError, "blank update!" end update.keys.each do |content| if content.is_a?(BasicObj) raise "invalid obj given" unless content == obj elsif content.is_a?(BasicWidget) raise "invalid widget given" unless content.obj == obj else raise "invalid key" end end @update = update @obj = obj end # @return [Array] list containing the widgets that are no longer needed after the update. def widgets_to_delete dereferenced = [] newreferenced = [] @update.each do |content, params| (deref, newref) = changed_references_from(content, params) dereferenced += deref newreferenced += newref end deleted_by_dereference = dereferenced - newreferenced recursively_deleted = [] deleted_by_dereference.each do |widget| recursively_deleted += all_widgets_contained_in_except(widget, newreferenced) end # we dont want to slow down every request by scanning for orphans, so # we only do this when widgets are being deleted if deleted_by_dereference.present? orphans = @obj.all_widgets_from_pool - @obj.contained_widgets - newreferenced else orphans = [] end deleted_by_dereference + recursively_deleted + orphans end private def all_widgets_contained_in_except(widget, exceptions) widget_children = widget.referenced_widgets - exceptions widget_descendants = widget_children.map do |child| all_widgets_contained_in_except(child, exceptions) end.flatten widget_children + widget_descendants end def changed_references_from(content, params) dereferenced_ids = [] newreferenced_ids = [] if content.kind_of?(BasicWidget) && !content.persisted? params.each_value do |value| if value.kind_of?(Array) newreferenced_ids += value.select { |v| v.kind_of?(BasicWidget) } end end else params.each do |key, value| if content.type_of_attribute(key) == 'widgetlist' current = content[key] || [] new = value || [] dereferenced_ids += current - new newreferenced_ids += new - current end end end [dereferenced_ids, newreferenced_ids] end end end