lib/active_archive/base.rb in active_archive-5.1.7 vs lib/active_archive/base.rb in active_archive-5.2.0

- old
+ new

@@ -8,113 +8,179 @@ base.extend Scopes base.instance_eval { define_model_callbacks(:unarchive) } end + def archivable? + respond_to?(:archived_at) + end + def archived? archivable? ? !archived_at.nil? : destroyed? end - def archivable? - respond_to?(:archived_at) + def unarchived? + !archived? end + 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) } + + 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) end end end - alias_method(:archive, :destroy) - alias_method(:archive!, :destroy) + alias_method :archive, :destroy def to_archival I18n.t("active_archive.archival.#{archived? ? :archived : :unarchived}") 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) } + private - self - end - end + def unarchival + [ + lambda do |validate| + unarchive_destroyed_dependent_records(validate) + end, + lambda do |validate| + run_callbacks(:unarchive) do + set_archived_at(nil, validate) - alias_method(:unarchive!, :unarchive) + each_counter_cache do |assoc_class, counter_cache_column, assoc_id| + assoc_class.increment_counter(counter_cache_column, assoc_id) + end - def unarchived? - !archived? + true + end + end + ] end - def unarchivable? - !archivable? + def get_archived_record + self.class.unscoped.find(id) end - private + def set_archived_at(value, force = nil) + return self unless archivable? + record = get_archived_record + record.archived_at = value - def attempt_notifying_observers(callback) - notify_observers(callback) if respond_to?(:notify_observers) + begin + should_ignore_validations?(force) ? record.save(validate: false) : record.save! + @attributes = record.instance_variable_get('@attributes') + rescue => error + record.destroy + raise error + end end + 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 + end + def destroy_with_active_archive(force = nil) run_callbacks(:destroy) do if archived? || new_record? save else set_archived_at(Time.now, force) + each_counter_cache do |assoc_class, counter_cache_column, assoc_id| assoc_class.decrement_counter(counter_cache_column, assoc_id) end end - return(true) + + true end archived? ? self : false end - def each_counter_cache - _reflections.each do |name, reflection| - next unless respond_to?(name.to_sym) + 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 < ?" - association = send(name.to_sym) + send(name).unscope(where: :archived_at) + .where(query, archived_at - window, archived_at + window) + end - next if association.nil? - next unless reflection.belongs_to? && reflection.counter_cache_column + 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 - associated_class = association.class + reload + end - yield(associated_class, reflection.counter_cache_column, send(reflection.foreign_key)) + # 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 end end + # rubocop:enable Metrics/AbcSize - def retrieve_archived_record - self.class.unscoped.find(id) + def attempt_notifying_observers(callback) + notify_observers(callback) + rescue NoMethodError + # do nothing end - # rubocop:disable Metrics/AbcSize - def retrieve_dependent_records - dependent_records = {} - - self.class.reflections.each do |key, ref| - next unless ref.options[:dependent] == :destroy - - records = send(key) - next unless records - records.respond_to?(:empty?) ? (next if records.empty?) : (records = [] << records) - - dependent_record = records.first - dependent_record.nil? ? next : dependent_records[dependent_record.class] = records.map(&:id) + def dependent_record_ids + dependent_reflections(self.class).reduce({}) do |records, (key, _)| + found = Array(send(key)).compact + next records if found.empty? + records.update(found.first.class => found.map(&:id)) end + end - dependent_records + def permanently_delete_records_after(&block) + dependent_records = dependent_record_ids + result = yield(block) + permanently_delete_records(dependent_records) if result + result end - # rubocop:enable Metrics/AbcSize 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 @@ -123,90 +189,19 @@ record.destroy(:force) end end end - def permanently_delete_records_after(&block) - dependent_records = retrieve_dependent_records - dependent_results = yield(block) - permanently_delete_records(dependent_records) if dependent_results - dependent_results - end - - def unarchival - [ - ->(validate) { unarchive_destroyed_dependent_records(validate) }, - 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 - return(true) - end - end - ] - end - - # rubocop:disable Metrics/LineLength - def dependent_records_for_unarchival(name, reflection) - record = send(name) - - case reflection.macro.to_s.gsub('has_', '').to_sym - when :many - records = archived_at ? set_record_window(record, name, reflection) : record.unscope(where: :archived_at) - when :one, :belongs_to - self.class.unscoped { records = [] << record } + def dependent_reflections(klass) + klass.reflections.select do |_, reflection| + reflection.options[:dependent] == :destroy end - - [records].flatten.compact end - # rubocop:enable Metrics/LineLength - def unarchive_destroyed_dependent_records(force = nil) - self.class.reflections - .select { |_, ref| ref.options[:dependent].to_s == 'destroy' && ref.klass.archivable? } - .each do |name, ref| - dependent_records_for_unarchival(name, ref).each { |rec| rec.try(:unarchive, force) } - reload - end - end - - def set_archived_at(value, force = nil) - return self unless archivable? - record = retrieve_archived_record - record.archived_at = value - - begin - should_ignore_validations?(force) ? record.save(validate: false) : record.save! - - if ::ActiveRecord::VERSION::MAJOR >= 5 && ::ActiveRecord::VERSION::MINOR >= 2 - @mutations_before_last_save = record.send(:mutations_from_database) - @attributes_changed_by_setter = HashWithIndifferentAccess.new - elsif ::ActiveRecord::VERSION::MAJOR >= 5 - @previous_mutation_tracker = record.send(:previous_mutation_tracker) - elsif ::ActiveRecord::VERSION::MAJOR >= 4 - @previously_changed = record.instance_variable_get('@previously_changed') - end - - @changed_attributes = HashWithIndifferentAccess.new - @attributes = record.instance_variable_get('@attributes') - @mutation_tracker = nil - @mutations_from_database = nil - rescue => error - record.destroy - raise(error) + def dependent_permanent_reflections(klass) + dependent_reflections(klass).select do |_name, reflection| + reflection.klass.archivable? end - end - - def set_record_window(_, 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]) end def should_force_destroy?(force) force.is_a?(Hash) ? force[:force] : (force == :force) end