Class: VersionedStore::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/versioned_store/base.rb

Direct Known Subclasses

AgentC::Db::Store

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(version: :root, **config) ⇒ Base

Returns a new instance of Base.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/versioned_store/base.rb', line 38

def initialize(version: :root, **config)
  @schema = self.class.schema
  @schema.add_table_migrations!
  @version = version
  @config = Config.new(**config)
  @klasses = {}

  if File.exist?(@config.db_filename)
    @config&.logger&.info("using existing store at: #{@config.db_filename}")
  else
    @config&.logger&.info("creating store at: #{@config.db_filename}")
  end


  # Define accessor methods for each record type
  schema.records.each do |name, spec|
    singleton_class.define_method(name) { class_for(spec) }
  end

  # eagerly construct classes
  schema.records.each_value { class_for(_1) }

  # Run post-initialization hooks for setting up associations
  schema.post_init_hooks.each { |hook| hook.call(self) }
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



37
38
39
# File 'lib/versioned_store/base.rb', line 37

def config
  @config
end

#schemaObject (readonly)

Returns the value of attribute schema.



37
38
39
# File 'lib/versioned_store/base.rb', line 37

def schema
  @schema
end

#versionObject (readonly)

Returns the value of attribute version.



37
38
39
# File 'lib/versioned_store/base.rb', line 37

def version
  @version
end

Class Method Details

.inherited(subclass) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/versioned_store/base.rb', line 8

def inherited(subclass)
  super
  # If parent has a schema, duplicate it for the child
  # Otherwise create a new empty schema
  parent_schema = @schema
  if parent_schema
    subclass.instance_variable_set(:@schema, parent_schema.dup)
  else
    subclass.instance_variable_set(:@schema, Stores::Schema.new)
  end
end

.migrate(version = nil, &block) ⇒ Object



28
29
30
31
32
33
34
# File 'lib/versioned_store/base.rb', line 28

def migrate(version = nil, &block)
  if version.nil?
    schema.migrate(&block)
  else
    schema.migrate(version, &block)
  end
end

.record(name, table: nil, &block) ⇒ Object



24
25
26
# File 'lib/versioned_store/base.rb', line 24

def record(name, table: nil, &block)
  schema.record(name, table: table, &block)
end

.schemaObject



20
21
22
# File 'lib/versioned_store/base.rb', line 20

def schema
  @schema ||= Stores::Schema.new
end

Instance Method Details

#dirObject



68
69
70
# File 'lib/versioned_store/base.rb', line 68

def dir
  @config.dir
end

#restore(name = nil) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/versioned_store/base.rb', line 82

def restore(name = nil)
  if name
    # Named snapshot restore
    raise "Can only restore from root store" unless root?

    snapshot_path = File.join(snapshots_dir, "#{name}.sqlite3")
    raise "Snapshot '#{name}' does not exist" unless File.exist?(snapshot_path)

    # Copy the snapshot to the main database
    main_db = File.join(dir, config.db_filename)
    FileUtils.cp(snapshot_path, main_db)

    # Create a new version snapshot of the restored state
    timestamp = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
    version_file = File.join(versions_dir, "#{timestamp}.sqlite3")
    FileUtils.mkdir_p(File.dirname(version_file))
    FileUtils.cp(main_db, version_file)

    # Return a new root store for the restored state
    self.class.new(version:, **config.to_h)
  else
    # Version-based restore (existing behavior)
    raise "Can only restore from a version snapshot, not from root" if root?

    # Copy the version database to the main database
    main_db = File.join(dir, config.db_filename)
    FileUtils.cp(db_path, main_db)

    # Renumber versions: keep all versions up to and including this one,
    # then create a new version snapshot for the restore
    version_files = Dir.glob(File.join(versions_dir, "*.sqlite3")).sort
    version_files.each do |file|
      file_version = File.basename(file, ".sqlite3").to_i
      if file_version > version
        FileUtils.rm(file)
      end
    end

    # Create a new version snapshot of the restored state
    new_version_num = version + 1
    version_file = File.join(versions_dir, "#{new_version_num}.sqlite3")
    FileUtils.cp(main_db, version_file)

    # Return a new root store for the restored state
    self.class.new(version: :root, **config.to_h)
  end
end

#snapshot(name) ⇒ Object



130
131
132
133
134
135
136
137
138
139
# File 'lib/versioned_store/base.rb', line 130

def snapshot(name)
  raise "Can only create snapshots from root store" unless root?

  # Create snapshots directory if it doesn't exist
  FileUtils.mkdir_p(snapshots_dir)

  # Copy current database to named snapshot
  snapshot_path = File.join(snapshots_dir, "#{name}.sqlite3")
  FileUtils.cp(db_path, snapshot_path)
end

#transactionObject



64
65
66
# File 'lib/versioned_store/base.rb', line 64

def transaction(&)
  base_class.transaction(&)
end

#versionsObject



72
73
74
75
76
77
78
79
80
# File 'lib/versioned_store/base.rb', line 72

def versions
  return [self] unless root?

  version_files = Dir.glob(File.join(versions_dir, "*.sqlite3")).sort
  version_files.map do |file|
    version_num = File.basename(file, ".sqlite3").to_i
    self.class.new(version: version_num, **config.to_h)
  end
end