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