Class: Magick::Versioning

Inherits:
Object
  • Object
show all
Defined in:
lib/magick/versioning.rb

Defined Under Namespace

Classes: Version

Constant Summary collapse

DEFAULT_MAX_VERSIONS =
50
STORE_PREFIX =

Version history lives under a reserved pseudo-feature namespace so that feature reads (get_all_data) never drag snapshot blobs along, and so deleting a feature does not destroy its ActiveRecord archive row.

'__magick_versions:'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(adapter_registry, max_versions: DEFAULT_MAX_VERSIONS) ⇒ Versioning

Returns a new instance of Versioning.



37
38
39
40
41
42
43
44
# File 'lib/magick/versioning.rb', line 37

def initialize(adapter_registry, max_versions: DEFAULT_MAX_VERSIONS)
  @adapter_registry = adapter_registry
  @max_versions = max_versions.to_i.positive? ? max_versions.to_i : DEFAULT_MAX_VERSIONS
  # Process-local cache of the hot window, rehydrated lazily from the
  # adapters so history survives restarts and is shared across containers.
  @hot = {}
  @mutex = Mutex.new
end

Instance Attribute Details

#max_versionsObject (readonly)

Returns the value of attribute max_versions.



46
47
48
# File 'lib/magick/versioning.rb', line 46

def max_versions
  @max_versions
end

Instance Method Details

#get_versions(feature_name, all: false) ⇒ Object

Hot window (last max_versions, memory/Redis) by default; all: true merges the unlimited ActiveRecord archive when one is configured.



64
65
66
67
68
69
70
71
# File 'lib/magick/versioning.rb', line 64

def get_versions(feature_name, all: false)
  name = feature_name.to_s
  hot = @mutex.synchronize { hot_window(name).dup }
  return hot unless all

  older = archive_versions(name).reject { |a| hot.any? { |h| h.version == a.version } }
  (older + hot).sort_by(&:version)
end

#record_change(feature, action: nil, created_by: nil, snapshot: nil) ⇒ Object

Called by Feature#record_change after every successful mutation. snapshot: allows callers (delete) to capture state before the mutation.



57
58
59
60
# File 'lib/magick/versioning.rb', line 57

def record_change(feature, action: nil, created_by: nil, snapshot: nil)
  data = snapshot ? deep_symbolize(JSON.parse(JSON.generate(snapshot))) : snapshot_of(feature)
  append(feature.name, data, created_by: created_by, action: action)
end

#rollback(feature_name, version) ⇒ Object

Restore the feature to the snapshot stored in the given version, then record the rollback itself as a new version + audit entry (history only ever rolls forward). Restores state wholesale: value (including false/ empty), status, group, the entire targeting hash, and dependencies.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/magick/versioning.rb', line 77

def rollback(feature_name, version)
  entry = find_version(feature_name, version)
  return false unless entry

  feature = Magick.features[feature_name.to_s] || Magick[feature_name]
  Magick.suppress_change_recording do
    feature.restore_snapshot!(entry.feature_data)
  end

  actor = Magick.current_actor
  Magick.audit_log&.log(feature_name, 'rollback', user_id: actor, changes: { rolled_back_to: version })
  record_change(feature, action: 'rollback', created_by: actor)

  if defined?(Magick::Rails::Events) && Magick::Rails::Events.rails8?
    Magick::Rails::Events.rollback(feature_name, version: version)
  end

  true
end

#save_version(feature_name, version: nil, created_by: nil) ⇒ Object

Explicit manual snapshot. Kept as public API from earlier releases; since 1.5.0 every Feature mutation also snapshots automatically.



50
51
52
53
# File 'lib/magick/versioning.rb', line 50

def save_version(feature_name, version: nil, created_by: nil)
  feature = Magick.features[feature_name.to_s] || Magick[feature_name]
  append(feature.name, snapshot_of(feature), version: version, created_by: created_by, action: 'manual')
end