lib/active_archive/base.rb in active_archive-5.3.0 vs lib/active_archive/base.rb in active_archive-6.0.0

- old
+ new

@@ -1,250 +1,140 @@ # frozen_string_literal: true -# require "active_record/attribute_mutation_tracker" - module ActiveArchive module Base def self.included(base) base.extend Methods base.extend Scopes - base.instance_eval { define_model_callbacks(:unarchive) } + base.instance_eval do + define_model_callbacks :archive, only: %i[before after] + define_model_callbacks :unarchive, only: %i[before after] + end end + def archival? + return destroyed? if unarchivable? + + (will_save_change_to_archived_at? || saved_change_to_archived_at?) && archived? + end + def archivable? respond_to?(:archived_at) end def archived? archivable? ? !archived_at.nil? : destroyed? end - def unarchived? - !archived? - end + def archive + return destroy if unarchivable? - def unarchivable? - !archivable? - end - - def unarchive(opts = nil) with_transaction_returning_status do - records = should_unarchive_parent_first?(opts) ? unarchival.reverse : unarchival - records.each { |rec| rec.call(opts) } + run_callbacks :archive do + mark_as_archived + mark_relections_as_archived - self - end - end - - alias_method :undestroy, :unarchive - - def destroy(force = nil) - with_transaction_returning_status do - if unarchivable? || should_force_destroy?(force) - permanently_delete_records_after { super() } - else - destroy_with_active_archive(force) - - if ::ActiveRecord::VERSION::MAJOR >= 5 - archived_at_will_change! - elsif ::ActiveRecord::VERSION::MAJOR >= 4 - attribute_will_change!('archived_at') - end + self end end end - alias_method :archive, :destroy + def unarchival? + return !destroyed? if unarchivable? - def to_archival - I18n.t("active_archive.archival.#{archived? ? :archived : :unarchived}") + (will_save_change_to_archived_at? || saved_change_to_archived_at?) && unarchived? end - private - - def unarchival - [ - lambda do |validate| - unarchive_destroyed_dependent_records(validate) - end, - lambda do |validate| - run_callbacks(:unarchive) do - set_archived_at(nil, validate) - - each_counter_cache do |assoc_class, counter_cache_column, assoc_id| - assoc_class.increment_counter(counter_cache_column, assoc_id) - end - - true - end - end - ] + def unarchived? + !archived? end - def get_archived_record - self.class.unscoped.find(id) + def unarchivable? + !archivable? end - # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - def set_archived_at(value, force = nil) - return self unless archivable? + def unarchive + return self if unarchivable? - record = get_archived_record + with_transaction_returning_status do + run_callbacks :unarchive do + mark_as_unarchived + mark_relections_as_unarchived - if ::ActiveRecord::VERSION::MAJOR >= 5 - record.archived_at_will_change! - elsif ::ActiveRecord::VERSION::MAJOR >= 4 - record.attribute_will_change!('archived_at') - end - - record.archived_at = value - - begin - should_ignore_validations?(force) ? record.save(validate: false) : record.save! - - if ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 2 - @attributes_changed_by_setter = record.send(:saved_changes) - elsif ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 1 - @changed_attributes = record.send(:saved_changes) - elsif ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 0 - @changed_attributes = record.send(:previous_changes) - elsif ::ActiveRecord::VERSION::MAJOR >= 4 - @previously_changed = record.instance_variable_get('@previously_changed') + self end - - @attributes = record.instance_variable_get('@attributes') - rescue => error - record.destroy - raise error end end - # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - def each_counter_cache - _reflections.each do |name, reflection| - next unless respond_to?(name.to_sym) - - association = send(name.to_sym) - - next if association.nil? - next unless reflection.belongs_to? && reflection.counter_cache_column - - yield(association.class, reflection.counter_cache_column, send(reflection.foreign_key)) - end + def to_archival + I18n.t("active_archive.archival.#{archived? ? :archived : :unarchived}") end - def destroy_with_active_archive(force = nil) - run_callbacks(:destroy) do - if archived? || new_record? - save - else - set_archived_at(Time.now, force) + private - each_counter_cache do |assoc_class, counter_cache_column, assoc_id| - assoc_class.decrement_counter(counter_cache_column, assoc_id) - end - end - - true - end - - archived? ? self : false + def mark_as_archived + self.archived_at = Time.now + save(validate: false) end - def add_record_window(_request, name, reflection) - qtn = reflection.table_name - window = ActiveArchive.configuration.dependent_record_window - query = "#{qtn}.archived_at > ? AND #{qtn}.archived_at < ?" - - send(name).unscope(where: :archived_at) - .where(query, archived_at - window, archived_at + window) + def mark_as_unarchived + self.archived_at = nil + save(validate: false) end - def unarchive_destroyed_dependent_records(force = nil) - destroyed_dependent_relations.each do |relation| - relation.to_a.each do |destroyed_dependent_record| - destroyed_dependent_record.try(:unarchive, force) - end - end + def mark_relections_as_archived + self.class.reflections.each do |table_name, reflection| + next unless dependent_destroy?(reflection) - reload - end + dependents = reflection_dependents(table_name) + next if dependents.nil? - # rubocop:disable Metrics/AbcSize - def destroyed_dependent_relations - dependent_permanent_reflections(self.class).map do |name, relation| - case relation.macro.to_s.gsub('has_', '').to_sym - when :many - if archived_at - add_record_window(send(name), name, relation) - else - send(name).unscope(where: :archived_at) - end - when :one, :belongs_to - self.class.unscoped { Array(send(name)) } - end + action = case [reflection_marco(reflection), archivable?] + when [:one, true] then :archive + when [:one, false] then :destroy + when [:many, true] then :archive_all + when [:many, false] then :destroy_all + end + + dependents.send(action) end end - # rubocop:enable Metrics/AbcSize - def attempt_notifying_observers(callback) - notify_observers(callback) - rescue NoMethodError - # do nothing - end + def mark_relections_as_unarchived + self.class.reflections.each do |table_name, reflection| + next unless dependent_destroy?(reflection) - def dependent_record_ids - dependent_reflections(self.class).reduce({}) do |records, (key, _)| - found = Array(send(key)).compact - next records if found.empty? + klass = relection_klass(table_name) + next unless klass.archivable? - records.update(found.first.class => found.map(&:id)) - end - end + dependents = reflection_dependents(table_name) + next if dependents.nil? - def permanently_delete_records_after(&block) - dependent_records = dependent_record_ids - result = yield(block) - permanently_delete_records(dependent_records) if result - result - end + action = case reflection_marco(reflection) + when :one then :unarchive + when :many then :unarchive_all + end - def permanently_delete_records(dependent_records) - dependent_records.each do |klass, ids| - ids.each do |id| - record = klass.unscoped.where(klass.primary_key => id).first - next unless record - - record.archived_at = nil - record.destroy(:force) - end + dependents.send(action) end end - def dependent_reflections(klass) - klass.reflections.select do |_, reflection| - reflection.options[:dependent] == :destroy - end + def dependent_destroy?(reflection) + reflection.options[:dependent] == :destroy end - def dependent_permanent_reflections(klass) - dependent_reflections(klass).select do |_name, reflection| - reflection.klass.archivable? - end + def reflection_dependents(table_name) + send(table_name) end - def should_force_destroy?(force) - force.is_a?(Hash) ? force[:force] : (force == :force) + def relection_klass(table_name) + table_name.classify.constantize end - def should_ignore_validations?(force) - force.is_a?(Hash) && (force[:validate] == false) - end - - def should_unarchive_parent_first?(order) - order.is_a?(Hash) && (order[:reverse] == true) + def reflection_marco(reflection) + reflection.macro.to_s.gsub('has_', '').to_sym end end end