lode 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.adoc +53 -29
- data/lib/lode/client.rb +8 -1
- data/lib/lode/tables/abstract.rb +34 -9
- data/lib/lode/tables/dictionary.rb +8 -13
- data/lib/lode/tables/value.rb +10 -15
- data/lode.gemspec +1 -1
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 181f4e76dd273a5cb36b0f7e3172ce3182d0359bb72162f3be463265b811ea71
|
4
|
+
data.tar.gz: 51569c4f0676c83a79649c10bbbf9bc7c409d768cf3e4a8660cdc22d87bd4e0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb2e66a93e557a04445f3b8e17dabc734932c4a135ecfd000637e7c34d349134f83edc741b16a479694cdf267bc77122a2f18cadac5d16e846143ad3de8709df
|
7
|
+
data.tar.gz: cb6e4da211f9e5a4e1a6c99f0182bbed352a46c94bec3ed9200cbd2552eed6801b105d85c2fc96e0d633b3f306839c341fe269582b40d894d49cdd20430e3452
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -20,7 +20,7 @@ toc::[]
|
|
20
20
|
- Built atop {pstore_link}.
|
21
21
|
- Uses the _Railway Pattern_ via {dry_monads_link} for fault tolerant pipelines.
|
22
22
|
- Emphasizes use of `Hash`, `Data`, `Struct`, or link:https://alchemists.io/projects/wholable[whole value objects] in general.
|
23
|
-
- Great for {xdg_link} caches,
|
23
|
+
- Great for {xdg_link} caches, lightweight file-based databases, or simple file stores in general.
|
24
24
|
|
25
25
|
== Requirements
|
26
26
|
|
@@ -66,32 +66,54 @@ To use, create a Lode instance and then use database-like messages to interact w
|
|
66
66
|
----
|
67
67
|
lode = Lode.new "demo.store"
|
68
68
|
|
69
|
-
lode.
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
lode.write :links do
|
70
|
+
create({id: 1, url: "https://one.com"})
|
71
|
+
create({id: 2, url: "https://2.com"})
|
72
|
+
create({id: 3, url: "https://three.com"})
|
73
73
|
end
|
74
74
|
|
75
75
|
# Success({:id=>3, :url=>"https://three.com"})
|
76
|
-
# (only the last record created
|
76
|
+
# (only the last record created is answered back)
|
77
|
+
|
78
|
+
lode.write(:links) { create({id: 4, url: "https://four.io"}) }
|
79
|
+
# Success({:id=>4, :url=>"https://four.io"})
|
80
|
+
|
81
|
+
lode.write(:links) { create({id: 4, url: "https://four.io"}) }
|
82
|
+
# Failure("Record exists for id: 4.")
|
83
|
+
|
84
|
+
lode.write(:links) { update({id: 1, url: "https://one.demo"}) }
|
85
|
+
# Success({:id=>1, :url=>"https://one.demo"})
|
86
|
+
|
87
|
+
lode.write(:links) { update({id: 5, url: "https://five.bogus"}) }
|
88
|
+
# Failure("Unable to find id: 5.")
|
77
89
|
|
78
|
-
lode.
|
90
|
+
lode.write(:links) { upsert({id: 2, url: "https://two.com"}) }
|
79
91
|
# Success({:id=>2, :url=>"https://two.com"})
|
80
92
|
|
81
|
-
lode.
|
82
|
-
# Success({:id=>
|
93
|
+
lode.write(:links) { upsert({id: 5, url: "https://five.demo"}) }
|
94
|
+
# Success({:id=>5, :url=>"https://five.demo"})
|
95
|
+
|
96
|
+
lode.read(:links) { find 1 }
|
97
|
+
# Success({:id=>1, :url=>"https://one.demo"})
|
83
98
|
|
84
|
-
lode.
|
99
|
+
lode.read(:links) { find 13 }
|
85
100
|
# Failure("Unable to find id: 13.")
|
86
101
|
|
87
|
-
lode.
|
102
|
+
lode.write(:links) { delete 2 }
|
88
103
|
# Success({:id=>2, :url=>"https://two.com"})
|
89
104
|
|
90
|
-
lode.
|
105
|
+
lode.write(:links) { delete 13 }
|
91
106
|
# Failure("Unable to find id: 13.")
|
92
107
|
|
93
|
-
lode.
|
94
|
-
# Success(
|
108
|
+
lode.read(:links, &:all)
|
109
|
+
# Success(
|
110
|
+
# [
|
111
|
+
# {:id=>1, :url=>"https://one.demo"},
|
112
|
+
# {:id=>3, :url=>"https://three.com"},
|
113
|
+
# {:id=>4, :url=>"https://four.io"},
|
114
|
+
# {:id=>5, :url=>"https://five.demo"}
|
115
|
+
# ]
|
116
|
+
# )
|
95
117
|
----
|
96
118
|
|
97
119
|
The default configuration is set up to use a primitive `Hash` which is the default behavior when using {pstore_link}. Everything answered back is a result monad as provided by the {dry_monads_link} gem so you can leverage the _Railway Pattern_ to build robust, fault tolerant, pipelines.
|
@@ -178,7 +200,7 @@ Given the above, you could now create and find _link_ records by _slug_ like so:
|
|
178
200
|
|
179
201
|
[source,ruby]
|
180
202
|
----
|
181
|
-
lode.
|
203
|
+
lode.write(:links) { upsert({id: 1, slug: :demo, url: "https://demo.com"}) }
|
182
204
|
lode.read(:links) { find :demo }
|
183
205
|
|
184
206
|
# Success({:id=>1, :slug=>:demo, :url=>"https://demo.com"})
|
@@ -201,7 +223,7 @@ As mentioned when configuring a Lode instance, two _types_ of tables are availab
|
|
201
223
|
[source,ruby]
|
202
224
|
----
|
203
225
|
lode = Lode.new "demo.store"
|
204
|
-
lode.
|
226
|
+
lode.write(:links) { upsert({id: 1, url: "https://one.com"}) }
|
205
227
|
# Success({:id=>1, :url=>"https://one.com"})
|
206
228
|
----
|
207
229
|
|
@@ -216,13 +238,13 @@ lode = Lode.new("demo.store") do |config|
|
|
216
238
|
config.register :links, model: Model
|
217
239
|
end
|
218
240
|
|
219
|
-
lode.
|
241
|
+
lode.write :links do
|
220
242
|
upsert({id: 1, url: "https://one.com"})
|
221
|
-
upsert Model[id: 2, url: "https://"]
|
243
|
+
upsert Model[id: 2, url: "https://two.com"]
|
222
244
|
end
|
223
245
|
|
224
|
-
lode.
|
225
|
-
# Success([#<data Model id=1, url="https://one.com">, #<data Model id=2, url="https://">])
|
246
|
+
lode.read(:links, &:all)
|
247
|
+
# Success([#<data Model id=1, url="https://one.com">, #<data Model id=2, url="https://two.com">])
|
226
248
|
----
|
227
249
|
|
228
250
|
The above would work with a `Struct` or any value object. One of many conveniences when using value objects -- as shown above -- is you can upsert records using a `Hash` or an instance of your value object.
|
@@ -231,7 +253,9 @@ Each table supports the following methods:
|
|
231
253
|
|
232
254
|
* `#primary_key`: Answers the primary key as defined when the table was registered or the default key (i.e. `:id`).
|
233
255
|
* `#all`: Answers all records for a table.
|
234
|
-
* `#find`: Finds
|
256
|
+
* `#find`: Finds an existing record by primary key or answers a failure if not found.
|
257
|
+
* `#create`: Creates a new record by primary key or answers a failure if record already exists.
|
258
|
+
* `#update`: Updates an existing record by primary key or answers a failure if the record can't be found.
|
235
259
|
* `#upsert`: Creates or updates a new or existing record by primary key.
|
236
260
|
* `#delete`: Deletes an existing record by primary key.
|
237
261
|
|
@@ -241,8 +265,8 @@ All of the above (except `#primary_key`) support an optional `:key` keyword argu
|
|
241
265
|
|
242
266
|
You've already seen a few examples of how to read and write to your object store but, to be explicit, the following are supported:
|
243
267
|
|
244
|
-
* `#
|
245
|
-
* `#
|
268
|
+
* `#read`: Allows you to _only_ read data from your object store in a single transaction. Any write operation will result in an exception.
|
269
|
+
* `#write`: Allows you to write (and read) records in a single transaction.
|
246
270
|
|
247
271
|
Both of the above methods require you to supply the table name and a block with operations. Since a table name must always be supplied this means you can interact with multiple tables within the same file store or you can write different tables to different files. Up to you. Here's an example of a basic write and read operation:
|
248
272
|
|
@@ -250,14 +274,14 @@ Both of the above methods require you to supply the table name and a block with
|
|
250
274
|
----
|
251
275
|
lode = Lode.new "demo.store"
|
252
276
|
|
253
|
-
# Read/Write
|
254
|
-
lode.commit(:links) { upsert({id: 1, url: "https://demo.com"}) }
|
255
|
-
|
256
277
|
# Read Only
|
257
278
|
lode.read(:links) { find 1 }
|
279
|
+
|
280
|
+
# Write/Read
|
281
|
+
lode.write(:links) { upsert({id: 1, url: "https://demo.com"}) }
|
258
282
|
----
|
259
283
|
|
260
|
-
Attempting to
|
284
|
+
Attempting to _write_ within a _read_ transaction will result in an error. For example, notice `delete` is being used within the `read` transaction which causes an exception:
|
261
285
|
|
262
286
|
[source,ruby]
|
263
287
|
----
|
@@ -265,7 +289,7 @@ lode.read(:links) { delete 1 }
|
|
265
289
|
# in read-only transaction (PStore::Error)
|
266
290
|
----
|
267
291
|
|
268
|
-
For those familiar with {pstore_link} behavior, a
|
292
|
+
For those familiar with {pstore_link} behavior, a write and read operation is the equivalent of the following using `PStore` directly:
|
269
293
|
|
270
294
|
[source,ruby]
|
271
295
|
----
|
@@ -273,7 +297,7 @@ require "pstore"
|
|
273
297
|
|
274
298
|
store = PStore.new "demo.store"
|
275
299
|
|
276
|
-
# Read
|
300
|
+
# Write/Read
|
277
301
|
store.transaction do |store|
|
278
302
|
store[:links] = store.fetch(:links, []).append({id: 1, url: "https://demo.com"})
|
279
303
|
end
|
data/lib/lode/client.rb
CHANGED
@@ -24,7 +24,14 @@ module Lode
|
|
24
24
|
|
25
25
|
def read(key, &) = transact(__method__, key, &)
|
26
26
|
|
27
|
-
def
|
27
|
+
def write(key, &) = transact(:commit, key, &)
|
28
|
+
|
29
|
+
def commit(key, &)
|
30
|
+
warn "`#{self.class}##{__method__}` is deprecated, use `#write` instead.",
|
31
|
+
category: :deprecated
|
32
|
+
|
33
|
+
write(key, &)
|
34
|
+
end
|
28
35
|
|
29
36
|
private
|
30
37
|
|
data/lib/lode/tables/abstract.rb
CHANGED
@@ -22,18 +22,38 @@ module Lode
|
|
22
22
|
|
23
23
|
def all = Success records.dup.freeze
|
24
24
|
|
25
|
-
def find
|
26
|
-
|
27
|
-
|
25
|
+
def find id, key: primary_key
|
26
|
+
records.find { |record| primary_id(record, key:) == id }
|
27
|
+
.then do |record|
|
28
|
+
return Success record if record
|
29
|
+
|
30
|
+
Failure "Unable to find #{key}: #{id.inspect}."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def create record, key: primary_key
|
35
|
+
id = primary_id(record, key:)
|
36
|
+
|
37
|
+
find(id, key:).bind { Failure "Record exists for #{key}: #{id}." }
|
38
|
+
.or do |error|
|
39
|
+
return append record if error.include? "Unable to find"
|
40
|
+
|
41
|
+
Failure error
|
42
|
+
end
|
28
43
|
end
|
29
44
|
|
30
|
-
def
|
45
|
+
def update change, key: primary_key
|
46
|
+
id = primary_id(change, key:)
|
47
|
+
find(id, key:).bind { |existing| revise existing, change }
|
48
|
+
end
|
49
|
+
|
50
|
+
def upsert record, key: primary_key
|
31
51
|
fail NoMethodError,
|
32
52
|
"`#{self.class}##{__method__} #{method(__method__).parameters}` must be implemented."
|
33
53
|
end
|
34
54
|
|
35
|
-
def delete
|
36
|
-
find(
|
55
|
+
def delete id, key: primary_key
|
56
|
+
find(id, key:).fmap do |record|
|
37
57
|
records.delete record
|
38
58
|
store[key] = records
|
39
59
|
record
|
@@ -44,10 +64,15 @@ module Lode
|
|
44
64
|
|
45
65
|
attr_reader :store, :key, :setting, :records
|
46
66
|
|
47
|
-
def
|
48
|
-
|
67
|
+
def primary_id record, key: primary_key
|
68
|
+
fail NoMethodError,
|
69
|
+
"`#{self.class}##{__method__} #{method(__method__).parameters}` must be implemented."
|
70
|
+
end
|
71
|
+
|
72
|
+
def revise existing, change
|
73
|
+
records.supplant existing, change
|
49
74
|
store[key] = records
|
50
|
-
Success
|
75
|
+
Success change
|
51
76
|
end
|
52
77
|
|
53
78
|
def append record
|
@@ -8,21 +8,16 @@ module Lode
|
|
8
8
|
class Dictionary < Abstract
|
9
9
|
include Dry::Monads[:result]
|
10
10
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
Failure "Unable to find #{key}: #{value.inspect}."
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def upsert value, key: primary_key
|
21
|
-
find(value[key]).either(
|
22
|
-
-> existing { update existing, value },
|
23
|
-
proc { append value }
|
11
|
+
def upsert change, key: primary_key
|
12
|
+
find(change[key], key:).either(
|
13
|
+
-> existing { revise existing, change },
|
14
|
+
proc { append change }
|
24
15
|
)
|
25
16
|
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def primary_id(record, key: primary_key) = record[key]
|
26
21
|
end
|
27
22
|
end
|
28
23
|
end
|
data/lib/lode/tables/value.rb
CHANGED
@@ -8,30 +8,25 @@ module Lode
|
|
8
8
|
class Value < Abstract
|
9
9
|
include Dry::Monads[:result]
|
10
10
|
|
11
|
-
def
|
12
|
-
|
13
|
-
.then do |record|
|
14
|
-
return Success record if record
|
11
|
+
def upsert change, key: primary_key
|
12
|
+
record = record_for change
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def upsert value, key: primary_key
|
21
|
-
record = record_for value
|
22
|
-
|
23
|
-
find(record.public_send(key)).either(
|
24
|
-
-> existing { update existing, record },
|
14
|
+
find(primary_id(record, key:)).either(
|
15
|
+
-> existing { revise existing, record },
|
25
16
|
proc { append record }
|
26
17
|
)
|
27
18
|
end
|
28
19
|
|
20
|
+
protected
|
21
|
+
|
22
|
+
def primary_id(record, key: primary_key) = record.public_send(key)
|
23
|
+
|
29
24
|
private
|
30
25
|
|
31
26
|
# :reek:FeatureEnvy
|
32
|
-
def record_for
|
27
|
+
def record_for change
|
33
28
|
model = setting.model
|
34
|
-
|
29
|
+
change.is_a?(model) ? change : model[**change.to_h]
|
35
30
|
end
|
36
31
|
end
|
37
32
|
end
|
data/lode.gemspec
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brooke Kuhlmann
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
|
36
36
|
gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2024-
|
38
|
+
date: 2024-05-16 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: dry-monads
|
@@ -125,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
125
|
- !ruby/object:Gem::Version
|
126
126
|
version: '0'
|
127
127
|
requirements: []
|
128
|
-
rubygems_version: 3.5.
|
128
|
+
rubygems_version: 3.5.10
|
129
129
|
signing_key:
|
130
130
|
specification_version: 4
|
131
131
|
summary: A monadic store of marshaled objects.
|
metadata.gz.sig
CHANGED
Binary file
|