familia 1.0.0.pre.rc4 → 1.0.0.pre.rc5

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: b514a58bc45692f39123cce853a679078eccd78362a78facc397a1df6d94629d
4
- data.tar.gz: d29686d172bec525bee366ab54ad4e81a5903547a9a2d98c68df8ca81853c7dc
3
+ metadata.gz: 3218c877946104986176b606caef17360ba262e22372dd61597982f9b97661db
4
+ data.tar.gz: b699a7b951ccf64c3421c9f79e29f927d3df0a36a0caa005575e118ae8ad000c
5
5
  SHA512:
6
- metadata.gz: cd750e1ee120eb666563e9c8c552f721926ccceaa032cd0cefcf4fad7920225ab1edc4961cf3a5a7c38d81ecab3c21b446e91a75347d51a4bc196c14f5ed94fd
7
- data.tar.gz: 6c52919952e7ce8232cf1023fafe567655580a73e4d36dd0a38b8842cb618387272db6950380f71efa01797439d9542303d3addd8c94e841255923998b93589f
6
+ metadata.gz: a19eca2cdcf6fe40e2c4109199938f95c02af3e3f6f4dda247720c983e6340ed725143a350f72b00a7c0c8d16bdab7dfb82ac4314a4d786249381f34fcb2da35
7
+ data.tar.gz: 2a9b815eed8437ea6514a7d29288ecf151c371ed2013495a5eaa6e2294bf61f75c07c9946c28dbf75fcdd0df7fdc1b5890ab0d83a9354f951c0f43e7718f0df8
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (1.0.0.pre.rc4)
4
+ familia (1.0.0.pre.rc5)
5
5
  redis (>= 4.8.1, < 6.0)
6
6
  uri-redis (~> 1.3)
7
7
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Familia - 1.0.0-rc4 (August 2024)
1
+ # Familia - 1.0.0-rc5 (August 2024)
2
2
 
3
3
  **Organize and store Ruby objects in Redis. A powerful Ruby ORM (of sorts) for Redis.**
4
4
 
data/VERSION.yml CHANGED
@@ -2,4 +2,4 @@
2
2
  :MAJOR: 1
3
3
  :MINOR: 0
4
4
  :PATCH: 0
5
- :PRE: rc4
5
+ :PRE: rc5
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
  module Familia::Features
6
- #
6
+
7
7
  module Expiration
8
8
  @ttl = nil
9
9
 
@@ -57,7 +57,7 @@ module Familia::Features
57
57
  # @raise [Familia::Problem] If you try to feed it non-numbers or time-travel
58
58
  # (negative numbers). It's strict, but fair!
59
59
  #
60
- def update_expiration(ttl = nil)
60
+ def update_expiration(ttl = nil)
61
61
  ttl ||= self.ttl
62
62
  # It's important to raise exceptions here and not just log warnings. We
63
63
  # don't want to silently fail at setting expirations and cause data
@@ -67,14 +67,15 @@ module Familia::Features
67
67
  # good reason for the ttl to not be set in the first place. If the
68
68
  # class doesn't have a ttl, the default comes from Familia.ttl (which
69
69
  # is 0).
70
- raise Familia::Problem, "TTL must be a number (#{ttl.class})" unless ttl.is_a?(Numeric)
71
- raise Familia::Problem, "TTL must be positive (#{ttl})" unless ttl.is_a?(Numeric)
70
+ unless ttl.is_a?(Numeric)
71
+ raise Familia::Problem, "TTL must be a number (#{ttl.class} in #{self.class})"
72
+ end
72
73
 
73
74
  if ttl.zero?
74
75
  return Familia.ld "[update_expiration] No expiration for #{self.class} (#{rediskey})"
75
76
  end
76
77
 
77
- Familia.info "[update_expiration] Expires #{rediskey} in #{ttl} seconds"
78
+ Familia.ld "[update_expiration] Expires #{rediskey} in #{ttl} seconds"
78
79
 
79
80
  # Redis' EXPIRE command returns 1 if the timeout was set, 0 if key does
80
81
  # not exist or the timeout could not be set. Via redis-rb here, it's
@@ -6,6 +6,24 @@ module Familia
6
6
  # Familia::Horreum
7
7
  #
8
8
  class Horreum
9
+ # List of valid return values for Redis commands.
10
+ # This includes:
11
+ # - "OK": Indicates successful execution of a command.
12
+ # - true: Indicates a successful boolean response.
13
+ # - 1: Indicates success for commands that return a count of affected items.
14
+ # - 0: Indicates success for commands that return a count of affected items, but no items were affected.
15
+ # - nil: Indicates the absence of a value, which can be considered a valid outcome in some contexts.
16
+ #
17
+ # This list is used to validate the return values of multiple Redis commands executed within methods.
18
+ # Methods that run multiple Redis commands will check if all return values are included in this list
19
+ # to determine overall success. If any return value is not in this list, it is considered unexpected
20
+ # and may be logged or handled accordingly.
21
+ @valid_command_return_values = ["OK", true, 1, 0, nil]
22
+
23
+ class << self
24
+ attr_accessor :valid_command_return_values
25
+ end
26
+
9
27
  # Serialization: Where Objects Go to Become Strings (and Vice Versa)!
10
28
  #
11
29
  # This module is chock-full of methods that'll make your head spin (in a
@@ -108,12 +126,12 @@ module Familia
108
126
  self.created ||= Familia.now.to_i
109
127
 
110
128
  # Commit our tale to the Redis chronicles
111
- ret = commit_fields # e.g. ["OK"]
129
+ ret = commit_fields # e.g. MultiResult.new(true, ["OK", "OK"])
112
130
 
113
131
  Familia.ld "[save] #{self.class} #{rediskey} #{ret}"
114
132
 
115
133
  # Did Redis accept our offering?
116
- ret.uniq.all? { |value| ["OK", true].include?(value) }
134
+ ret.successful?
117
135
  end
118
136
 
119
137
  # Apply a smattering of fields to this object like fairy dust.
@@ -126,10 +144,10 @@ module Familia
126
144
  # fresh attributes.
127
145
  #
128
146
  # @example Giving your object a makeover
129
- # dragon.apply_fields(name: "Puff", breathes: "fire", loves: "little boys
147
+ # dragon.apply_fields(name: "Puff", breathes: "fire", loves: "Toys
130
148
  # named Jackie")
131
149
  # # => #<Dragon:0x007f8a1c8b0a28 @name="Puff", @breathes="fire",
132
- # @loves="little boys named Jackie">
150
+ # @loves="Toys named Jackie">
133
151
  #
134
152
  def apply_fields(**fields)
135
153
  fields.each do |field, value|
@@ -143,23 +161,48 @@ module Familia
143
161
  #
144
162
  # This method performs a sacred ritual, sending our cherished attributes
145
163
  # on a journey through the ethernet to find their resting place in Redis.
164
+ # It executes a transaction that includes setting field values and,
165
+ # if applicable, updating the expiration time.
166
+ #
167
+ # @return [MultiResult] A mystical object containing:
168
+ # - success: A boolean indicating if all Redis commands succeeded
169
+ # - results: An array of strings, cryptic messages from the Redis gods
146
170
  #
147
- # @return [Array<String>] A mystical array of strings, cryptic messages
148
- # from the Redis gods.
171
+ # The MultiResult object responds to:
172
+ # - successful?: Returns the boolean success value
173
+ # - results: Returns the array of command return values
149
174
  #
150
175
  # @note Be warned, young programmer! This method dabbles in the arcane
151
176
  # art of transactions. Side effects may include data persistence and a
152
- # slight tingling sensation.
177
+ # slight tingling sensation. The method does not raise exceptions for
178
+ # unexpected Redis responses, but logs warnings and returns a failure status.
153
179
  #
154
180
  # @example Offering your changes to the Redis deities
155
181
  # unicorn.name = "Charlie"
156
182
  # unicorn.horn_length = "magnificent"
157
- # unicorn.commit_fields
158
- # # => ["OK", "OK"] (The Redis gods are pleased with your offering)
183
+ # result = unicorn.commit_fields
184
+ # if result.successful?
185
+ # puts "The Redis gods are pleased with your offering"
186
+ # p result.results # => ["OK", "OK"]
187
+ # else
188
+ # puts "The Redis gods frown upon your offering"
189
+ # p result.results # Examine the unexpected values
190
+ # end
191
+ #
192
+ # @see Familia::Horreum.valid_command_return_values for the list of
193
+ # acceptable Redis command return values.
194
+ #
195
+ # @note This method performs logging at various levels:
196
+ # - Debug: Logs the object's class, Redis key, and current state before committing
197
+ # - Warn: Logs any unexpected return values from Redis commands
198
+ # - Debug: Logs the final result, including success status and all return values
199
+ #
200
+ # @note The expiration update is only performed for classes that have
201
+ # the expiration feature enabled. For others, it's a no-op.
159
202
  #
160
203
  def commit_fields
161
204
  Familia.ld "[commit_fields] #{self.class} #{rediskey} #{to_h}"
162
- transaction do |conn|
205
+ command_return_values = transaction do |conn|
163
206
  hmset
164
207
 
165
208
  # Only classes that have the expiration ferature enabled will
@@ -167,6 +210,26 @@ module Familia
167
210
  # this will be a no-op.
168
211
  update_expiration
169
212
  end
213
+
214
+ # The acceptable redis command return values are defined in the
215
+ # Horreum class. This is to ensure that all commands return values
216
+ # are validated against a consistent set of values.
217
+ acceptable_values = Familia::Horreum.valid_command_return_values
218
+
219
+ # Check if all return values are valid
220
+ summary_boolean = command_return_values.uniq.all? { |value|
221
+ acceptable_values.include?(value)
222
+ }
223
+
224
+ # Log the unexpected
225
+ unless summary_boolean
226
+ unexpected_values = command_return_values.reject { |value| acceptable_values.include?(value) }
227
+ Familia.warn "[commit_fields] Unexpected return values: #{unexpected_values}"
228
+ end
229
+
230
+ Familia.ld "[commit_fields] #{self.class} #{rediskey} #{summary_boolean}: #{command_return_values}"
231
+
232
+ MultiResult.new(summary_boolean, command_return_values)
170
233
  end
171
234
 
172
235
  # Dramatically vanquish this object from the face of Redis! (ed: delete it)
@@ -313,6 +376,71 @@ module Familia
313
376
  end
314
377
  # End of Serialization module
315
378
 
379
+ # Represents the result of a multiple Redis commands.
380
+ #
381
+ # This class encapsulates the outcome of a Redis "transaction",
382
+ # providing both a success indicator and the raw results from
383
+ # the Redis commands executed during the transaction ("MULTI").
384
+ #
385
+ # @attr_reader success [Boolean] Indicates whether all Redis commands
386
+ # in the transaction were successful.
387
+ # @attr_reader results [Array<String>] An array of return values from
388
+ # the Redis commands executed in the transaction.
389
+ #
390
+ # @example Creating a MultiResult
391
+ # result = MultiResult.new(true, ["OK", "OK"])
392
+ #
393
+ # @example Checking the success of a commit
394
+ # if result.successful?
395
+ # puts "All commands succeeded"
396
+ # else
397
+ # puts "Some commands failed"
398
+ # end
399
+ #
400
+ # @example Accessing raw results
401
+ # result.results.each_with_index do |value, index|
402
+ # puts "Command #{index + 1} returned: #{value}"
403
+ # end
404
+ class MultiResult
405
+ # @return [Boolean] true if all commands in the transaction succeeded,
406
+ # false otherwise
407
+ attr_reader :success
408
+
409
+ # @return [Array<String>] The raw return values from the Redis commands
410
+ attr_reader :results
411
+
412
+ # Creates a new MultiResult instance.
413
+ #
414
+ # @param success [Boolean] Whether all commands succeeded
415
+ # @param results [Array<String>] The raw results from Redis commands
416
+ def initialize(success, results)
417
+ @success = success
418
+ @results = results
419
+ end
420
+
421
+ # Returns a tuple representing the result of the transaction.
422
+ #
423
+ # @return [Array] A tuple containing the success status and the raw results.
424
+ # The success status is a boolean indicating if all commands succeeded.
425
+ # The raw results is an array of return values from the Redis commands.
426
+ #
427
+ # @example
428
+ # [true, ["OK", true, 1]]
429
+ #
430
+ def tuple
431
+ [successful?, results]
432
+ end
433
+
434
+ # Convenient method to check if the commit was successful.
435
+ #
436
+ # @return [Boolean] true if all commands succeeded, false otherwise
437
+ def successful?
438
+ @success
439
+ end
440
+ alias success? successful?
441
+ end
442
+ # End of MultiResult class
443
+
316
444
  include Serialization # these become Horreum instance methods
317
445
  end
318
446
  end
@@ -233,7 +233,7 @@ module Familia
233
233
  end
234
234
 
235
235
  # If the unique_id is nil, raise an error
236
- raise Problem, "Identifier is nil for #{self.class}" if unique_id.nil?
236
+ raise Problem, "Identifier is nil for #{self.class} #{rediskey}" if unique_id.nil?
237
237
  raise Problem, 'Identifier is empty' if unique_id.empty?
238
238
 
239
239
  unique_id
@@ -102,7 +102,12 @@ module Familia
102
102
 
103
103
  # Apply the options to instance method setters of the same name
104
104
  @opts.each do |k, v|
105
- Familia.ld " [setting] #{k} #{v}"
105
+ # Bewarde logging :parent instance here implicitly calls #to_s which for
106
+ # some classes could include the identifier which could still be nil at
107
+ # this point. This would result in a Familia::Problem being raised. So
108
+ # to be on the safe-side here until we have a better understanding of
109
+ # the issue, we'll just log the class name for each key-value pair.
110
+ Familia.ld " [setting] #{k} #{v.class}"
106
111
  send(:"#{k}=", v) if respond_to? :"#{k}="
107
112
  end
108
113
 
data/lib/familia/utils.rb CHANGED
@@ -38,7 +38,7 @@ module Familia
38
38
  # # => "193tosc85k3u513do2mtmibchpd2ruh5l3nsp6dnl0ov1i91h7m7"
39
39
  #
40
40
  def generate_id(length: 32, encoding: 36)
41
- raise ArgumentError, "Encoding must be between 2 and 32" unless (1..32).include?(encoding)
41
+ raise ArgumentError, "Encoding must be between 2 and 36" unless (1..36).include?(encoding)
42
42
 
43
43
  input = SecureRandom.hex(length)
44
44
  Digest::SHA256.hexdigest(input).to_i(16).to_s(encoding)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: familia
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.rc4
4
+ version: 1.0.0.pre.rc5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum