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