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:'
VERSION_KEY_PREFIX =

The hot window keeps one store key per snapshot. Appending writes only its own key, so two containers saving at the same time interleave into one history instead of each rewriting the whole list over the other's entries.

'version_'
VERSION_KEY_PATTERN =
/\A#{VERSION_KEY_PREFIX}(\d+)\z/.freeze
ARCHIVE_ROW_INFIX =

The archive keeps one store ROW per version, not one key per version inside the feature's single row. An ActiveRecord write is a read-modify-write of the whole row blob under a row lock, so appending version N to a shared row rewrote all N-1 predecessors with it; its own row makes an append cost one snapshot no matter how long the history is.

'#v'
ARCHIVE_DATA_KEY =
'snapshot'
SEQUENCE_ROW_SUFFIX =

The shared counter that hands out version numbers, in a row of its own so that allocating a number never rewrites archived snapshots alongside it.

'#seq'
SEQUENCE_KEY =
'sequence'
LEGACY_WINDOW_KEY =

1.5.0 wrote the whole hot window as a single JSON list under this key. Still read (and migrated forward on the next append) so history written before the upgrade stays visible.

'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.



61
62
63
64
# File 'lib/magick/versioning.rb', line 61

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
end

Instance Attribute Details

#max_versions ⇒ Object (readonly)

Returns the value of attribute max_versions.



66
67
68
# File 'lib/magick/versioning.rb', line 66

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.



84
85
86
87
88
89
90
# File 'lib/magick/versioning.rb', line 84

def get_versions(feature_name, all: false)
  name = feature_name.to_s
  hot = hot_entries(name)
  return sorted(hot).last(@max_versions) unless all

  sorted(hot.merge(archive_entries(name)))
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.



77
78
79
80
# File 'lib/magick/versioning.rb', line 77

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.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/magick/versioning.rb', line 96

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.



70
71
72
73
# File 'lib/magick/versioning.rb', line 70

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