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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09a0f58842564183f26432cf94556f585303a9e4ad76ec67b614fde10510de3e'
4
- data.tar.gz: 4eb739e7ba22ecb4c04f6b22e4ea7831dab6d05178883ffdef3e7699a78cde9a
3
+ metadata.gz: 181f4e76dd273a5cb36b0f7e3172ce3182d0359bb72162f3be463265b811ea71
4
+ data.tar.gz: 51569c4f0676c83a79649c10bbbf9bc7c409d768cf3e4a8660cdc22d87bd4e0b
5
5
  SHA512:
6
- metadata.gz: e5dd7b40a024e01b7c2324cdb61d49c11c0960c32773c984e91c7e06f51518b0ef2a1ee01db8dc09166a4a42a33a87a46fc78c12eb490843c887eab066e9f9e4
7
- data.tar.gz: c991649ef5fb8baac35d1b94ca33c3b4a88bf7eacaef249d69adbc862c7a66e5e618aa2911e8003b6dfe33cfea9aad9c2c5a72b5249f73c6f5b82dde94393804
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, simple file-based databases, or simple file stores in general.
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.commit :links do
70
- upsert({id: 1, url: "https://one.com"})
71
- upsert({id: 2, url: "https://2.com"})
72
- upsert({id: 3, url: "https://three.com"})
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/updated is answered back)
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.commit(:links) { upsert({id: 2, url: "https://two.com"}) }
90
+ lode.write(:links) { upsert({id: 2, url: "https://two.com"}) }
79
91
  # Success({:id=>2, :url=>"https://two.com"})
80
92
 
81
- lode.commit(:links) { find 1 }
82
- # Success({:id=>1, :url=>"https://one.com"})
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.commit(:links) { find 13 }
99
+ lode.read(:links) { find 13 }
85
100
  # Failure("Unable to find id: 13.")
86
101
 
87
- lode.commit(:links) { delete 2 }
102
+ lode.write(:links) { delete 2 }
88
103
  # Success({:id=>2, :url=>"https://two.com"})
89
104
 
90
- lode.commit(:links) { delete 13 }
105
+ lode.write(:links) { delete 13 }
91
106
  # Failure("Unable to find id: 13.")
92
107
 
93
- lode.commit(:links, &:all)
94
- # Success([{:id=>1, :url=>"https://one.com"}, {:id=>3, :url=>"https://three.com"}])
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.commit(:links) { upsert({id: 1, slug: :demo, url: "https://demo.com"}) }
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.commit(:links) { upsert({id: 1, url: "https://one.com"}) }
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.commit :links do
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.commit(:links, &:all)
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 a record by primary key.
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
- * `#commit`: Allows you to read and write records as a single transaction.
245
- * `#read`: Allows you to _only_ read data from your object store as a single transaction. Write operations will result in an exception.
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 `#read` with a _write_ operation will result in an error. Example:
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 commit and read operation is the equivalent of the following using `PStore` directly:
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/Write
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 commit(key, &) = transact(__method__, key, &)
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
 
@@ -22,18 +22,38 @@ module Lode
22
22
 
23
23
  def all = Success records.dup.freeze
24
24
 
25
- def find value, key: primary_key
26
- fail NoMethodError,
27
- "`#{self.class}##{__method__} #{method(__method__).parameters}` must be implemented."
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 upsert value, key: primary_key
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 value, key: primary_key
36
- find(value, key:).fmap do |record|
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 update existing, record
48
- records.supplant existing, record
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 record
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 find value, key: primary_key
12
- records.find { |record| record[key] == value }
13
- .then do |record|
14
- return Success record if record
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
@@ -8,30 +8,25 @@ module Lode
8
8
  class Value < Abstract
9
9
  include Dry::Monads[:result]
10
10
 
11
- def find value, key: primary_key
12
- records.find { |record| record.public_send(key) == value }
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
- Failure "Unable to find #{key}: #{value.inspect}."
17
- end
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 value
27
+ def record_for change
33
28
  model = setting.model
34
- value.is_a?(model) ? value : model[**value.to_h]
29
+ change.is_a?(model) ? change : model[**change.to_h]
35
30
  end
36
31
  end
37
32
  end
data/lode.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "lode"
5
- spec.version = "1.3.0"
5
+ spec.version = "1.4.0"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/lode"
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.3.0
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-04-21 00:00:00.000000000 Z
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.9
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