Module: Fosm::DataRetention
- Defined in:
- lib/fosm/data_retention.rb
Overview
Service for discovering and managing FOSM objects under a data retention policy.
Eligibility criteria
A model is “archival-eligible” when it satisfies BOTH of the following:
1. It has at least one *terminal* state whose name contains "archiv"
(case-insensitive — covers :archived, :archival, :archiviert, etc.)
2. Its database table has an `archived_at` datetime/timestamp column.
Retention window
Driven by Fosm.config.data_retention_days (default 3650 = 10 years). Records with a non-nil archived_at older than the cutoff are “eligible for purge”.
Audit-safety guarantee
Purging a business record NEVER touches fosm_transition_logs. The audit trail is intentionally kept forever for compliance purposes. Only the source record (e.g. the Invoice or FaasAccount row) is deleted.
Class Method Summary collapse
-
.all_eligible_for_purge(model_class) ⇒ ActiveRecord::Relation
An unbounded scope of all purge-eligible records for use in batch jobs.
-
.archival_eligible?(model_class) ⇒ Boolean
Returns true when the model has an archival terminal state AND an ‘archived_at` column.
-
.archival_eligible_models ⇒ Array<Class>
All registered FOSM model classes that meet both eligibility criteria.
-
.archival_states_for(model_class) ⇒ Array<String>
All archival terminal state names for the model (as strings).
-
.has_archival_terminal_state?(model_class) ⇒ Boolean
Returns true when at least one terminal state name includes “archiv”.
-
.has_archived_at_column?(model_class) ⇒ Boolean
Returns true when the model’s table has an ‘archived_at` column.
-
.records_eligible_for_purge(model_class, page: 1, per_page: 50) ⇒ ActiveRecord::Relation
Returns a paginated ActiveRecord relation of purge-eligible records, ordered oldest-first (most overdue first).
-
.retention_cutoff_date ⇒ ActiveSupport::TimeWithZone
The cutoff timestamp: records with ‘archived_at` before this moment are eligible for purge.
-
.safe_to_purge?(record) ⇒ Boolean
Returns true when a single record is safe to purge:.
-
.total_eligible_for_purge(model_class) ⇒ Integer
Total records eligible for purge: in an archival state, with a non-nil ‘archived_at` older than the retention cutoff.
-
.total_in_archival_state(model_class) ⇒ Integer
Total records currently in any archival terminal state, regardless of whether they have passed the retention window.
Class Method Details
.all_eligible_for_purge(model_class) ⇒ ActiveRecord::Relation
An unbounded scope of all purge-eligible records for use in batch jobs. Callers should use find_each to avoid loading all rows into memory.
121 122 123 |
# File 'lib/fosm/data_retention.rb', line 121 def all_eligible_for_purge(model_class) eligible_scope(model_class) end |
.archival_eligible?(model_class) ⇒ Boolean
Returns true when the model has an archival terminal state AND an ‘archived_at` column.
36 37 38 |
# File 'lib/fosm/data_retention.rb', line 36 def archival_eligible?(model_class) has_archival_terminal_state?(model_class) && has_archived_at_column?(model_class) end |
.archival_eligible_models ⇒ Array<Class>
All registered FOSM model classes that meet both eligibility criteria.
27 28 29 |
# File 'lib/fosm/data_retention.rb', line 27 def archival_eligible_models Fosm::Registry.model_classes.select { |mc| archival_eligible?(mc) } end |
.archival_states_for(model_class) ⇒ Array<String>
All archival terminal state names for the model (as strings).
66 67 68 69 70 71 72 |
# File 'lib/fosm/data_retention.rb', line 66 def archival_states_for(model_class) lifecycle = model_class.try(:fosm_lifecycle) return [] unless lifecycle lifecycle.states .select { |s| s.terminal? && s.name.to_s.downcase.include?("archiv") } .map { |s| s.name.to_s } end |
.has_archival_terminal_state?(model_class) ⇒ Boolean
Returns true when at least one terminal state name includes “archiv”.
44 45 46 47 48 |
# File 'lib/fosm/data_retention.rb', line 44 def has_archival_terminal_state?(model_class) lifecycle = model_class.try(:fosm_lifecycle) return false unless lifecycle lifecycle.states.any? { |s| s.terminal? && s.name.to_s.downcase.include?("archiv") } end |
.has_archived_at_column?(model_class) ⇒ Boolean
Returns true when the model’s table has an ‘archived_at` column.
Rescues gracefully if the table doesn’t exist yet (e.g. during migrations).
56 57 58 59 60 |
# File 'lib/fosm/data_retention.rb', line 56 def has_archived_at_column?(model_class) model_class.column_names.include?("archived_at") rescue => _e false end |
.records_eligible_for_purge(model_class, page: 1, per_page: 50) ⇒ ActiveRecord::Relation
Returns a paginated ActiveRecord relation of purge-eligible records, ordered oldest-first (most overdue first).
Pagination is offset-based. Pages are 1-indexed.
111 112 113 114 |
# File 'lib/fosm/data_retention.rb', line 111 def records_eligible_for_purge(model_class, page: 1, per_page: 50) offset = ([page.to_i, 1].max - 1) * per_page eligible_scope(model_class).offset(offset).limit(per_page) end |
.retention_cutoff_date ⇒ ActiveSupport::TimeWithZone
The cutoff timestamp: records with ‘archived_at` before this moment are eligible for purge.
78 79 80 |
# File 'lib/fosm/data_retention.rb', line 78 def retention_cutoff_date Fosm.config.data_retention_days.days.ago end |
.safe_to_purge?(record) ⇒ Boolean
Returns true when a single record is safe to purge:
- Its class has an `archived_at` column (defensive belt-and-suspenders).
- +archived_at+ is not nil.
- +archived_at+ is strictly older than the retention cutoff.
- The record's current state is an archival terminal state.
This is the authoritative pre-purge check. Always call this immediately before destroying, even when the controller already checked — the retention window or record state may have changed since the UI check.
138 139 140 141 142 143 144 |
# File 'lib/fosm/data_retention.rb', line 138 def safe_to_purge?(record) return false unless record.class.column_names.include?("archived_at") archived_at = record.archived_at return false if archived_at.nil? return false if archived_at >= retention_cutoff_date archival_states_for(record.class).include?(record.state.to_s) end |
.total_eligible_for_purge(model_class) ⇒ Integer
Total records eligible for purge: in an archival state, with a non-nil ‘archived_at` older than the retention cutoff.
98 99 100 |
# File 'lib/fosm/data_retention.rb', line 98 def total_eligible_for_purge(model_class) eligible_scope(model_class).count end |
.total_in_archival_state(model_class) ⇒ Integer
Total records currently in any archival terminal state, regardless of whether they have passed the retention window.
87 88 89 90 91 |
# File 'lib/fosm/data_retention.rb', line 87 def total_in_archival_state(model_class) states = archival_states_for(model_class) return 0 if states.empty? model_class.where(state: states).count end |