Class: Mistri::Workspace::Single

Inherits:
Object
  • Object
show all
Defined in:
lib/mistri/workspace/single.rb

Overview

One document living wherever the host says: a column on a record, a cache key, anything readable and writable. This is the shape of an agent that edits one page:

workspace = Mistri::Workspace::Single.new(
path: "page.html",
read: -> { page.reload.draft_html },
write: ->(html) { page.update!(draft_html: html) },
synchronize: lambda do |&operation|
  raise "outer transaction" if page.class.connection.transaction_open?
  page.with_lock(&operation)
end
)

The document tools then read and edit that column like any document. synchronize: is optional; without it, the legacy read/write port cannot protect an edit from another process writing between those two calls.

Instance Method Summary collapse

Constructor Details

#initialize(read:, write:, path: "document", synchronize: nil) ⇒ Single

Returns a new instance of Single.



25
26
27
28
29
30
31
32
33
34
# File 'lib/mistri/workspace/single.rb', line 25

def initialize(read:, write:, path: "document", synchronize: nil)
  unless synchronize.nil? || synchronize.respond_to?(:call)
    raise ArgumentError, "synchronize must be callable"
  end

  @path = String.new(path.to_s).freeze
  @read = read
  @write = write
  @synchronize = synchronize
end

Instance Method Details

#atomic_writes?Boolean

Returns:

  • (Boolean)


47
# File 'lib/mistri/workspace/single.rb', line 47

def atomic_writes? = !@synchronize.nil?

#compare_and_write(path, content, expected_revision:) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/mistri/workspace/single.rb', line 56

def compare_and_write(path, content, expected_revision:)
  verify_path!(path)
  unless atomic_writes?
    raise ConfigurationError, "Single needs synchronize: for atomic writes"
  end

  synchronize do
    current = @read.call
    actual = current.nil? ? nil : Snapshot.for(current.to_s).revision
    unless actual == expected_revision
      raise WorkspaceConflictError.new(
        @path, expected_revision:, actual_revision: actual
      )
    end

    @write.call(content.to_s)
    committed = @read.call
    raise SchemaError, "#{@path.inspect} disappeared after write" if committed.nil?

    Snapshot.for(committed.to_s)
  end
end

#delete(path) ⇒ Object



79
80
81
82
83
84
85
86
# File 'lib/mistri/workspace/single.rb', line 79

def delete(path)
  if path.to_s == @path
    raise SchemaError,
          "#{@path.inspect} cannot be deleted, only rewritten"
  end

  nil
end

#list(prefix = nil) ⇒ Object



88
89
90
# File 'lib/mistri/workspace/single.rb', line 88

def list(prefix = nil)
  prefix.nil? || @path.start_with?(prefix.to_s) ? [@path] : []
end

#read(path) ⇒ Object



36
37
38
# File 'lib/mistri/workspace/single.rb', line 36

def read(path)
  path.to_s == @path ? @read.call : nil
end

#snapshot(path) ⇒ Object



49
50
51
52
53
54
# File 'lib/mistri/workspace/single.rb', line 49

def snapshot(path)
  return nil unless path.to_s == @path

  content = @read.call
  content.nil? ? nil : Snapshot.for(content.to_s)
end

#write(path, content) ⇒ Object



40
41
42
43
44
45
# File 'lib/mistri/workspace/single.rb', line 40

def write(path, content)
  verify_path!(path)

  synchronize { @write.call(content.to_s) }
  nil
end