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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/VERSION.yml +1 -1
- data/lib/familia/features/expiration.rb +6 -5
- data/lib/familia/horreum/serialization.rb +138 -10
- data/lib/familia/horreum.rb +1 -1
- data/lib/familia/redistype.rb +6 -1
- data/lib/familia/utils.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3218c877946104986176b606caef17360ba262e22372dd61597982f9b97661db
|
4
|
+
data.tar.gz: b699a7b951ccf64c3421c9f79e29f927d3df0a36a0caa005575e118ae8ad000c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a19eca2cdcf6fe40e2c4109199938f95c02af3e3f6f4dda247720c983e6340ed725143a350f72b00a7c0c8d16bdab7dfb82ac4314a4d786249381f34fcb2da35
|
7
|
+
data.tar.gz: 2a9b815eed8437ea6514a7d29288ecf151c371ed2013495a5eaa6e2294bf61f75c07c9946c28dbf75fcdd0df7fdc1b5890ab0d83a9354f951c0f43e7718f0df8
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
data/VERSION.yml
CHANGED
@@ -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
|
-
|
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
|
-
|
71
|
-
|
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.
|
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.
|
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: "
|
147
|
+
# dragon.apply_fields(name: "Puff", breathes: "fire", loves: "Toys
|
130
148
|
# named Jackie")
|
131
149
|
# # => #<Dragon:0x007f8a1c8b0a28 @name="Puff", @breathes="fire",
|
132
|
-
# @loves="
|
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
|
-
#
|
148
|
-
#
|
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
|
-
#
|
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
|
data/lib/familia/horreum.rb
CHANGED
@@ -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
|
data/lib/familia/redistype.rb
CHANGED
@@ -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
|
-
|
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
|
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)
|