concurrent_pipeline 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +457 -91
- data/concurrent_pipeline.gemspec +5 -3
- data/lib/concurrent_pipeline/errors.rb +6 -0
- data/lib/concurrent_pipeline/pipeline.rb +5 -9
- data/lib/concurrent_pipeline/pipelines/processors/asynchronous.rb +81 -18
- data/lib/concurrent_pipeline/pipelines/processors/locker.rb +3 -3
- data/lib/concurrent_pipeline/pipelines/processors/result.rb +11 -0
- data/lib/concurrent_pipeline/pipelines/processors/synchronous.rb +74 -5
- data/lib/concurrent_pipeline/pipelines/schema.rb +39 -13
- data/lib/concurrent_pipeline/store.rb +160 -57
- data/lib/concurrent_pipeline/stores/schema.rb +57 -19
- data/lib/concurrent_pipeline/version.rb +1 -1
- data/lib/concurrent_pipeline.rb +1 -1
- metadata +44 -17
- data/lib/concurrent_pipeline/stores/schema/record.rb +0 -47
- data/lib/concurrent_pipeline/stores/storage/yaml/fs.rb +0 -140
- data/lib/concurrent_pipeline/stores/storage/yaml.rb +0 -196
|
@@ -1,34 +1,72 @@
|
|
|
1
1
|
module ConcurrentPipeline
|
|
2
2
|
module Stores
|
|
3
3
|
class Schema
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
4
|
+
Record = Data.define(:name, :table, :block)
|
|
5
|
+
Migration = Data.define(:version, :block)
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
class RecordContext
|
|
8
|
+
attr_reader :schema_instance, :table_name
|
|
9
|
+
|
|
10
|
+
def initialize(schema_instance)
|
|
11
|
+
@schema_instance = schema_instance
|
|
12
|
+
@table_name = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def schema(table_name, &block)
|
|
16
|
+
# Store the table name for later use
|
|
17
|
+
@table_name = table_name
|
|
18
|
+
|
|
19
|
+
# Prepend schema migrations to front of the line
|
|
20
|
+
# Wrap the block in a create_table call
|
|
21
|
+
migration_block = Proc.new do
|
|
22
|
+
create_table(table_name, &block)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
schema_instance.prepend_migration(table_name, &migration_block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def method_missing(...)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def respond_to_missing?(...)
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :migrations, :records
|
|
37
|
+
def initialize
|
|
38
|
+
@migrations = []
|
|
39
|
+
@records = {}
|
|
40
|
+
@migration_counter = 1
|
|
10
41
|
end
|
|
11
42
|
|
|
12
|
-
def
|
|
13
|
-
@
|
|
14
|
-
@
|
|
43
|
+
def dir(path = nil)
|
|
44
|
+
@dir = path if path
|
|
45
|
+
@dir
|
|
15
46
|
end
|
|
16
47
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
define_singleton_method(:record_name) { name }
|
|
48
|
+
def migrate(version = @migration_counter += 1, &block)
|
|
49
|
+
migrations << Migration.new(version: version, block: block)
|
|
50
|
+
end
|
|
21
51
|
|
|
22
|
-
|
|
52
|
+
def prepend_migration(version, &block)
|
|
53
|
+
migrations.unshift(Migration.new(version: version, block: block))
|
|
54
|
+
end
|
|
23
55
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
56
|
+
def record(name, table: nil, &block)
|
|
57
|
+
# If block is given, execute it in RecordContext to extract schema calls
|
|
58
|
+
extracted_table = table
|
|
59
|
+
if block
|
|
60
|
+
context = RecordContext.new(self)
|
|
61
|
+
context.instance_exec(&block)
|
|
62
|
+
extracted_table ||= context.table_name
|
|
27
63
|
end
|
|
28
|
-
end
|
|
29
64
|
|
|
30
|
-
|
|
31
|
-
|
|
65
|
+
records[name] = Record.new(
|
|
66
|
+
name: name,
|
|
67
|
+
table: extracted_table,
|
|
68
|
+
block: block
|
|
69
|
+
)
|
|
32
70
|
end
|
|
33
71
|
end
|
|
34
72
|
end
|
data/lib/concurrent_pipeline.rb
CHANGED
metadata
CHANGED
|
@@ -1,57 +1,85 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: concurrent_pipeline
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pete Kinnecom
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zeitwerk
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '2.7'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '2.7'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: yaml
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
33
|
+
version: '0.4'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
40
|
+
version: '0.4'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: async
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '2.35'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '2.35'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: activerecord
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '8.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '8.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: sqlite3
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '2.4'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.4'
|
|
55
83
|
description:
|
|
56
84
|
email:
|
|
57
85
|
- git@k7u7.com
|
|
@@ -66,17 +94,16 @@ files:
|
|
|
66
94
|
- concurrency.md
|
|
67
95
|
- concurrent_pipeline.gemspec
|
|
68
96
|
- lib/concurrent_pipeline.rb
|
|
97
|
+
- lib/concurrent_pipeline/errors.rb
|
|
69
98
|
- lib/concurrent_pipeline/pipeline.rb
|
|
70
99
|
- lib/concurrent_pipeline/pipelines/processors/asynchronous.rb
|
|
71
100
|
- lib/concurrent_pipeline/pipelines/processors/locker.rb
|
|
101
|
+
- lib/concurrent_pipeline/pipelines/processors/result.rb
|
|
72
102
|
- lib/concurrent_pipeline/pipelines/processors/synchronous.rb
|
|
73
103
|
- lib/concurrent_pipeline/pipelines/schema.rb
|
|
74
104
|
- lib/concurrent_pipeline/shell.rb
|
|
75
105
|
- lib/concurrent_pipeline/store.rb
|
|
76
106
|
- lib/concurrent_pipeline/stores/schema.rb
|
|
77
|
-
- lib/concurrent_pipeline/stores/schema/record.rb
|
|
78
|
-
- lib/concurrent_pipeline/stores/storage/yaml.rb
|
|
79
|
-
- lib/concurrent_pipeline/stores/storage/yaml/fs.rb
|
|
80
107
|
- lib/concurrent_pipeline/version.rb
|
|
81
108
|
homepage: https://github.com/petekinnecom/concurrent_pipeline
|
|
82
109
|
licenses:
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
module ConcurrentPipeline
|
|
2
|
-
module Stores
|
|
3
|
-
class Schema
|
|
4
|
-
class Record
|
|
5
|
-
class << self
|
|
6
|
-
def attribute(name, **options)
|
|
7
|
-
attributes << name
|
|
8
|
-
attribute_defaults[name] = options[:default] if options.key?(:default)
|
|
9
|
-
|
|
10
|
-
define_method(name) do
|
|
11
|
-
attributes[name]
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
define_method("#{name}=") do |value|
|
|
15
|
-
@attributes[name] = value
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def attributes
|
|
20
|
-
@attributes ||= []
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def attribute_defaults
|
|
24
|
-
@attribute_defaults ||= {}
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def inherited(mod)
|
|
28
|
-
mod.attribute(:id)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
attr_reader :attributes
|
|
33
|
-
def initialize(attributes = {})
|
|
34
|
-
# Apply defaults for missing attributes
|
|
35
|
-
defaults = self.class.attribute_defaults
|
|
36
|
-
@attributes = self.class.attributes.each_with_object({}) do |attr_name, hash|
|
|
37
|
-
if attributes.key?(attr_name)
|
|
38
|
-
hash[attr_name] = attributes[attr_name]
|
|
39
|
-
elsif defaults.key?(attr_name)
|
|
40
|
-
hash[attr_name] = defaults[attr_name]
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
require "yaml"
|
|
2
|
-
require "fileutils"
|
|
3
|
-
|
|
4
|
-
module ConcurrentPipeline
|
|
5
|
-
module Stores
|
|
6
|
-
module Storage
|
|
7
|
-
class Yaml
|
|
8
|
-
class Fs
|
|
9
|
-
@@mutex = Mutex.new
|
|
10
|
-
|
|
11
|
-
attr_reader :dir
|
|
12
|
-
|
|
13
|
-
def initialize(dir:)
|
|
14
|
-
@dir = dir
|
|
15
|
-
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
16
|
-
FileUtils.mkdir_p(versions_dir) unless Dir.exist?(versions_dir)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def read_version(version_number)
|
|
20
|
-
@@mutex.synchronize do
|
|
21
|
-
if version_number == 0
|
|
22
|
-
{}
|
|
23
|
-
else
|
|
24
|
-
current_ver = unsafe_current_version_number
|
|
25
|
-
|
|
26
|
-
# If requesting the current/latest version, read from latest.yml
|
|
27
|
-
if version_number == current_ver
|
|
28
|
-
if File.exist?(latest_file_path)
|
|
29
|
-
data = File.read(latest_file_path).then { YAML.load(_1, aliases: true) || {} }
|
|
30
|
-
# Normalize keys: convert record names and ID keys to strings for consistency
|
|
31
|
-
data.transform_keys(&:to_s).transform_values do |records|
|
|
32
|
-
records.transform_keys(&:to_s)
|
|
33
|
-
end
|
|
34
|
-
else
|
|
35
|
-
{}
|
|
36
|
-
end
|
|
37
|
-
else
|
|
38
|
-
# Reading a historical version from versions/ directory
|
|
39
|
-
file_path = version_file_path(version_number)
|
|
40
|
-
if File.exist?(file_path)
|
|
41
|
-
data = File.read(file_path).then { YAML.load(_1, aliases: true) || {} }
|
|
42
|
-
# Normalize keys: convert record names and ID keys to strings for consistency
|
|
43
|
-
data.transform_keys(&:to_s).transform_values do |records|
|
|
44
|
-
records.transform_keys(&:to_s)
|
|
45
|
-
end
|
|
46
|
-
else
|
|
47
|
-
{}
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def write_version(version_number, data)
|
|
55
|
-
@@mutex.synchronize do
|
|
56
|
-
# Copy current latest.yml to versions directory if it exists
|
|
57
|
-
if File.exist?(latest_file_path)
|
|
58
|
-
current_version = unsafe_current_version_number
|
|
59
|
-
if current_version > 0
|
|
60
|
-
version_path = version_file_path(current_version)
|
|
61
|
-
FileUtils.cp(latest_file_path, version_path)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Write new data to latest.yml
|
|
66
|
-
File.write(latest_file_path, YAML.dump(data))
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def current_version_number
|
|
71
|
-
@@mutex.synchronize do
|
|
72
|
-
unsafe_current_version_number
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def version_files
|
|
77
|
-
@@mutex.synchronize do
|
|
78
|
-
unsafe_version_files
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def delete_version(version_number)
|
|
83
|
-
@@mutex.synchronize do
|
|
84
|
-
current_ver = unsafe_current_version_number
|
|
85
|
-
|
|
86
|
-
# If deleting the latest version
|
|
87
|
-
if version_number == current_ver
|
|
88
|
-
File.delete(latest_file_path) if File.exist?(latest_file_path)
|
|
89
|
-
else
|
|
90
|
-
# Deleting an archived version
|
|
91
|
-
file_path = version_file_path(version_number)
|
|
92
|
-
File.delete(file_path) if File.exist?(file_path)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def restore_version(version_number)
|
|
98
|
-
@@mutex.synchronize do
|
|
99
|
-
version_path = version_file_path(version_number)
|
|
100
|
-
|
|
101
|
-
if File.exist?(version_path)
|
|
102
|
-
# Copy the version file to latest.yml
|
|
103
|
-
FileUtils.cp(version_path, latest_file_path)
|
|
104
|
-
else
|
|
105
|
-
raise "Version #{version_number} does not exist"
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
private
|
|
111
|
-
|
|
112
|
-
def unsafe_current_version_number
|
|
113
|
-
if File.exist?(latest_file_path)
|
|
114
|
-
# Count existing version files + 1 for the latest
|
|
115
|
-
unsafe_version_files.length + 1
|
|
116
|
-
else
|
|
117
|
-
0
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def unsafe_version_files
|
|
122
|
-
Dir.glob(File.join(versions_dir, "*.yml")).sort
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def version_file_path(version_num)
|
|
126
|
-
File.join(versions_dir, "%04d.yml" % version_num)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def latest_file_path
|
|
130
|
-
File.join(dir, "data.yml")
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def versions_dir
|
|
134
|
-
File.join(dir, "versions")
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
module ConcurrentPipeline
|
|
2
|
-
module Stores
|
|
3
|
-
module Storage
|
|
4
|
-
class Yaml
|
|
5
|
-
attr_reader :fs, :version_number
|
|
6
|
-
|
|
7
|
-
def initialize(dir:, version_number: nil)
|
|
8
|
-
@fs = Fs.new(dir: dir)
|
|
9
|
-
@version_number = version_number
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def in_transaction?
|
|
13
|
-
!transaction_operations.nil?
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def transaction(&block)
|
|
17
|
-
begin_transaction
|
|
18
|
-
begin
|
|
19
|
-
yield
|
|
20
|
-
commit_transaction
|
|
21
|
-
rescue => e
|
|
22
|
-
rollback_transaction
|
|
23
|
-
raise e
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def create(name:, attrs:)
|
|
28
|
-
in_txn = in_transaction?
|
|
29
|
-
|
|
30
|
-
raise "Cannot write to non-current version" unless writeable?
|
|
31
|
-
|
|
32
|
-
id = attrs[:id] || attrs["id"]
|
|
33
|
-
raise "Record must have an id" unless id
|
|
34
|
-
|
|
35
|
-
# Always buffer the operation
|
|
36
|
-
buffer_operation(
|
|
37
|
-
type: :create,
|
|
38
|
-
name: name.to_s,
|
|
39
|
-
id: id.to_s,
|
|
40
|
-
attrs: attrs.transform_keys(&:to_s)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
# Flush immediately if not in a transaction
|
|
44
|
-
flush_buffer unless in_txn
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def update(name:, id:, attrs:)
|
|
48
|
-
in_txn = in_transaction?
|
|
49
|
-
|
|
50
|
-
raise "Cannot write to non-current version" unless writeable?
|
|
51
|
-
|
|
52
|
-
# Always buffer the operation
|
|
53
|
-
buffer_operation(
|
|
54
|
-
type: :update,
|
|
55
|
-
name: name.to_s,
|
|
56
|
-
id: id.to_s,
|
|
57
|
-
attrs: attrs.transform_keys(&:to_s)
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
# Flush immediately if not in a transaction
|
|
61
|
-
flush_buffer unless in_txn
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def all(name:)
|
|
65
|
-
data = load_data
|
|
66
|
-
records = data[name.to_s] || {}
|
|
67
|
-
records.values.map { |attrs| attrs.transform_keys(&:to_sym) }
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def versions
|
|
71
|
-
current_ver = current_version_number
|
|
72
|
-
(1..current_ver).map { |idx| self.class.new(dir: fs.dir, version_number: idx) }
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def restore
|
|
76
|
-
current_version = version_number || current_version_number
|
|
77
|
-
|
|
78
|
-
# Delete all versions after this one
|
|
79
|
-
fs.version_files.each_with_index do |file, idx|
|
|
80
|
-
version_num = idx + 1
|
|
81
|
-
if version_num > current_version
|
|
82
|
-
fs.delete_version(version_num)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# If restoring to a historical version (not current), move it to latest.yml
|
|
87
|
-
if version_number && version_number < current_version_number
|
|
88
|
-
fs.restore_version(version_number)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Return a new writeable storage at this version
|
|
92
|
-
self.class.new(dir: fs.dir)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def writeable?
|
|
96
|
-
version_number.nil? || version_number == current_version_number
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
def begin_transaction
|
|
102
|
-
raise "Transaction already in progress" if transaction_operations
|
|
103
|
-
|
|
104
|
-
self.transaction_operations = []
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def commit_transaction
|
|
108
|
-
raise "No transaction in progress" unless transaction_operations
|
|
109
|
-
|
|
110
|
-
flush_buffer
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def rollback_transaction
|
|
114
|
-
self.transaction_operations = nil
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
TRANSACTION_KEY = :yaml_storage_transaction_operations
|
|
118
|
-
|
|
119
|
-
def transaction_operations
|
|
120
|
-
Fiber[TRANSACTION_KEY]
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def transaction_operations=(value)
|
|
124
|
-
Fiber[TRANSACTION_KEY] = value
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def buffer_operation(op)
|
|
128
|
-
# If in a transaction, append to the transaction buffer
|
|
129
|
-
if transaction_operations
|
|
130
|
-
transaction_operations << op
|
|
131
|
-
else
|
|
132
|
-
# If not in a transaction, initialize a temporary buffer
|
|
133
|
-
self.transaction_operations = [op]
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def flush_buffer
|
|
138
|
-
return unless transaction_operations
|
|
139
|
-
|
|
140
|
-
# Load current data and apply all buffered operations
|
|
141
|
-
data = load_current_data
|
|
142
|
-
transaction_operations.each do |op|
|
|
143
|
-
apply_operation(data, op)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
write_new_version(data)
|
|
147
|
-
self.transaction_operations = nil
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def apply_operation(data, op)
|
|
151
|
-
case op[:type]
|
|
152
|
-
when :create
|
|
153
|
-
data[op[:name]] ||= {}
|
|
154
|
-
data[op[:name]][op[:id]] = op[:attrs]
|
|
155
|
-
when :update
|
|
156
|
-
records = data[op[:name]] || {}
|
|
157
|
-
if records[op[:id]]
|
|
158
|
-
records[op[:id]].merge!(op[:attrs])
|
|
159
|
-
else
|
|
160
|
-
raise "Record not found: #{op[:name]} with id #{op[:id].inspect}"
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def load_current_data
|
|
166
|
-
load_data
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def load_data
|
|
170
|
-
if version_number
|
|
171
|
-
# Reading a historical version from the versions/ directory
|
|
172
|
-
target_version = version_number
|
|
173
|
-
fs.read_version(target_version)
|
|
174
|
-
else
|
|
175
|
-
# Reading the current latest.yml
|
|
176
|
-
current_ver = current_version_number
|
|
177
|
-
if current_ver == 0
|
|
178
|
-
{}
|
|
179
|
-
else
|
|
180
|
-
fs.read_version(current_ver)
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def write_new_version(data)
|
|
186
|
-
next_version = current_version_number + 1
|
|
187
|
-
fs.write_version(next_version, data)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def current_version_number
|
|
191
|
-
fs.current_version_number
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
end
|