Class: ColdStorage::Policy
- Inherits:
-
Object
- Object
- ColdStorage::Policy
- Defined in:
- lib/cold_storage/policy.rb
Overview
The archivable options of a single model, and the relation they describe.
archivable after: 18.months # older than 18 months
archivable after: 30.days, on: :closed_at # ... measured on another column
archivable deleted: true # soft-deleted rows only
archivable deleted: true, after: 90.days # soft-deleted 90+ days ago
archivable after: 1.year, every: 1.month # ... and only run monthly
archivable after: 2.years, cascade: [:items] # take the children along
archivable after: 2.years, cascade: { items: [:taxes] } # ... and theirs
archivable on_destroy: true # catch hard deletes
Constant Summary collapse
- OPTIONS =
%i[ after older_than on deleted deleted_column every scope batch_size delete_after_archive delete_method cascade on_destroy hard_delete ].freeze
Instance Attribute Summary collapse
-
#after ⇒ Object
readonly
Returns the value of attribute after.
-
#batch_size ⇒ Object
readonly
Returns the value of attribute batch_size.
-
#cascade ⇒ Object
readonly
Returns the value of attribute cascade.
-
#delete_method ⇒ Object
readonly
Returns the value of attribute delete_method.
-
#deleted_column ⇒ Object
readonly
Returns the value of attribute deleted_column.
-
#every ⇒ Object
readonly
Returns the value of attribute every.
-
#model_name ⇒ Object
readonly
Returns the value of attribute model_name.
-
#on ⇒ Object
readonly
Returns the value of attribute on.
-
#scope ⇒ Object
readonly
Returns the value of attribute scope.
Class Method Summary collapse
Instance Method Summary collapse
- #batch_size_value ⇒ Object
- #cascade_reflection!(klass, name) ⇒ Object
-
#cutoff(now = Time.current) ⇒ Time?
Rows on the
on:column older than this are archivable. - #delete_after_archive? ⇒ Boolean
- #delete_method_value ⇒ Object
- #deleted? ⇒ Boolean
-
#due?(last_run_at) ⇒ Boolean
Has enough time passed since the last successful run?.
-
#initialize(model, **options) ⇒ Policy
constructor
A new instance of Policy.
- #model ⇒ Object
-
#on_destroy? ⇒ Boolean
Copy the row to the archive when it is really destroyed.
-
#relation(klass = model) ⇒ ActiveRecord::Relation
The rows that are archivable right now.
-
#sweeps? ⇒ Boolean
Does this policy describe rows a scheduled run should sweep up?
on_destroy: trueon its own does not: it only reacts to deletes. -
#to_s ⇒ String
One-line summary used by the status report.
-
#validate_against_schema!(klass = model) ⇒ Object
Checks the options against the real table.
Constructor Details
#initialize(model, **options) ⇒ Policy
Returns a new instance of Policy.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/cold_storage/policy.rb', line 24 def initialize(model, **) unknown = .keys - OPTIONS unless unknown.empty? raise InvalidPolicyError, "unknown archivable option(s) #{unknown.map(&:inspect).join(', ')} for #{model}. " \ "Known options: #{OPTIONS.map(&:inspect).join(', ')}" end @model_name = model.name @after = normalize_duration([:after] || [:older_than]) @deleted = .fetch(:deleted, false) @deleted_column = ([:deleted_column] || ColdStorage.config.deleted_column).to_sym @on = ([:on] || default_column).to_sym @every = normalize_duration([:every]) @scope = [:scope] @batch_size = [:batch_size] @delete_after_archive = [:delete_after_archive] @delete_method = [:delete_method] @cascade = AssociationTree.normalize([:cascade]) @on_destroy = .fetch(:on_destroy) { .fetch(:hard_delete, false) } validate! end |
Instance Attribute Details
#after ⇒ Object (readonly)
Returns the value of attribute after.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def after @after end |
#batch_size ⇒ Object (readonly)
Returns the value of attribute batch_size.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def batch_size @batch_size end |
#cascade ⇒ Object (readonly)
Returns the value of attribute cascade.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def cascade @cascade end |
#delete_method ⇒ Object (readonly)
Returns the value of attribute delete_method.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def delete_method @delete_method end |
#deleted_column ⇒ Object (readonly)
Returns the value of attribute deleted_column.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def deleted_column @deleted_column end |
#every ⇒ Object (readonly)
Returns the value of attribute every.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def every @every end |
#model_name ⇒ Object (readonly)
Returns the value of attribute model_name.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def model_name @model_name end |
#on ⇒ Object (readonly)
Returns the value of attribute on.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def on @on end |
#scope ⇒ Object (readonly)
Returns the value of attribute scope.
21 22 23 |
# File 'lib/cold_storage/policy.rb', line 21 def scope @scope end |
Class Method Details
.cascade_reflection!(klass, name) ⇒ ActiveRecord::Reflection::AssociationReflection
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/cold_storage/policy.rb', line 129 def self.cascade_reflection!(klass, name) reflection = klass.reflect_on_association(name) raise InvalidPolicyError, "#{klass} has no association #{name.inspect} to cascade to" if reflection.nil? if reflection.through_reflection? raise InvalidPolicyError, "cascade does not support :through associations (#{klass}##{name})" end unless reflection.collection? || reflection.has_one? raise InvalidPolicyError, "cascade only supports has_many / has_one associations (#{klass}##{name} is a #{reflection.macro})" end reflection end |
Instance Method Details
#batch_size_value ⇒ Object
67 68 69 |
# File 'lib/cold_storage/policy.rb', line 67 def batch_size_value batch_size || ColdStorage.config.batch_size end |
#cascade_reflection!(klass, name) ⇒ Object
124 125 126 |
# File 'lib/cold_storage/policy.rb', line 124 def cascade_reflection!(klass, name) self.class.cascade_reflection!(klass, name) end |
#cutoff(now = Time.current) ⇒ Time?
Returns rows on the on: column older than this are archivable.
95 96 97 98 99 |
# File 'lib/cold_storage/policy.rb', line 95 def cutoff(now = Time.current) return nil unless after now - after end |
#delete_after_archive? ⇒ Boolean
71 72 73 |
# File 'lib/cold_storage/policy.rb', line 71 def delete_after_archive? @delete_after_archive.nil? ? ColdStorage.config.delete_after_archive : @delete_after_archive end |
#delete_method_value ⇒ Object
75 76 77 |
# File 'lib/cold_storage/policy.rb', line 75 def delete_method_value delete_method || ColdStorage.config.delete_method end |
#deleted? ⇒ Boolean
48 49 50 |
# File 'lib/cold_storage/policy.rb', line 48 def deleted? @deleted end |
#due?(last_run_at) ⇒ Boolean
Has enough time passed since the last successful run?
102 103 104 105 106 |
# File 'lib/cold_storage/policy.rb', line 102 def due?(last_run_at) return true if every.nil? || last_run_at.nil? last_run_at <= Time.current - every end |
#model ⇒ Object
63 64 65 |
# File 'lib/cold_storage/policy.rb', line 63 def model @model_name.constantize end |
#on_destroy? ⇒ Boolean
Copy the row to the archive when it is really destroyed.
53 54 55 |
# File 'lib/cold_storage/policy.rb', line 53 def on_destroy? @on_destroy end |
#relation(klass = model) ⇒ ActiveRecord::Relation
The rows that are archivable right now.
Always starts from unscoped: a soft-delete default scope would
otherwise hide exactly the rows we are asked to archive.
85 86 87 88 89 90 91 92 |
# File 'lib/cold_storage/policy.rb', line 85 def relation(klass = model) return klass.none unless sweeps? relation = klass.unscoped relation = relation.where.not(deleted_column => nil) if deleted? relation = relation.where(klass.arel_table[on].lt(cutoff)) if after apply_scope(relation) end |
#sweeps? ⇒ Boolean
Does this policy describe rows a scheduled run should sweep up?
on_destroy: true on its own does not: it only reacts to deletes.
59 60 61 |
# File 'lib/cold_storage/policy.rb', line 59 def sweeps? !after.nil? || deleted? || !scope.nil? end |
#to_s ⇒ String
Returns one-line summary used by the status report.
146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/cold_storage/policy.rb', line 146 def to_s parts = [] parts << 'deleted' if deleted? parts << "#{on} < #{humanize_duration(after)} ago" if after parts << "scope: #{scope.is_a?(Symbol) ? scope : 'custom'}" if scope parts << 'on destroy' if on_destroy? parts << "every #{humanize_duration(every)}" if every && sweeps? parts << "cascade: #{cascade.keys.join(', ')}" if cascade.any? parts << 'keeps source rows' unless delete_after_archive? parts.join(', ') end |
#validate_against_schema!(klass = model) ⇒ Object
Checks the options against the real table. Raises with an actionable message instead of failing later with a SQL error.
110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/cold_storage/policy.rb', line 110 def validate_against_schema!(klass = model) columns = klass.column_names check_column!(klass, on, columns) check_column!(klass, deleted_column, columns) if deleted? if klass.primary_key.nil? || klass.primary_key.is_a?(Array) raise InvalidPolicyError, "#{klass} must have a single-column primary key to be archivable (got #{klass.primary_key.inspect})" end validate_cascade!(klass, cascade) true end |