familia 1.0.0.pre.rc5 → 1.0.0.pre.rc7
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/base.rb +1 -1
- data/lib/familia/features/expiration.rb +13 -13
- data/lib/familia/horreum/class_methods.rb +72 -20
- data/lib/familia/horreum/commands.rb +3 -2
- data/lib/familia/horreum/serialization.rb +108 -73
- data/lib/familia/horreum/utils.rb +1 -1
- data/lib/familia/horreum.rb +16 -11
- data/lib/familia/redistype/serialization.rb +3 -3
- data/lib/familia/redistype.rb +1 -1
- data/lib/familia/types/sorted_set.rb +4 -4
- data/lib/familia/utils.rb +46 -21
- data/try/27_redis_horreum_try.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 518b9d319e506964416166f2d84cc05d9d4a603de80b79a2f512fa9b751c4680
|
4
|
+
data.tar.gz: 9f1c9737d5d141050169da6123a977921e80768f056a0da138c299ccd7716abc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fed4bf1187d6d9ad477504343341c1f9a5bd59d0b7c276fbb8539086ddacfd2fa2306a55f9b2b10881cdd884ad38e6a9f392f79ed822fff270d97836d681260
|
7
|
+
data.tar.gz: 448a16b7b71afa58a27f0dfd464d85c495ddd517bf9eba5c622fab6c7b364566595e34d71934a2b9cd67b522e4b53ad94289a8097c8d819c011ad7e3e61bd6fe
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
data/VERSION.yml
CHANGED
data/lib/familia/base.rb
CHANGED
@@ -43,7 +43,7 @@ module Familia
|
|
43
43
|
#
|
44
44
|
# @note This method is a no-op. It's like shouting into the void, but less echo-y.
|
45
45
|
#
|
46
|
-
def update_expiration(
|
46
|
+
def update_expiration(*)
|
47
47
|
Familia.info "[update_expiration] Skipped for #{rediskey}. #{self.class} data is immortal!"
|
48
48
|
nil
|
49
49
|
end
|
@@ -37,25 +37,25 @@ module Familia::Features
|
|
37
37
|
@ttl || self.class.ttl
|
38
38
|
end
|
39
39
|
|
40
|
-
#
|
40
|
+
# Sets an expiration time for the Redis data associated with this object.
|
41
41
|
#
|
42
|
-
#
|
43
|
-
#
|
42
|
+
# This method allows setting a Time To Live (TTL) for the data in Redis,
|
43
|
+
# after which it will be automatically removed.
|
44
44
|
#
|
45
|
-
# @param ttl [Integer, nil] The Time To Live in seconds.
|
46
|
-
#
|
45
|
+
# @param ttl [Integer, nil] The Time To Live in seconds. If nil, the default
|
46
|
+
# TTL will be used.
|
47
47
|
#
|
48
|
-
# @return [Boolean]
|
49
|
-
#
|
48
|
+
# @return [Boolean] Returns true if the expiration was set successfully,
|
49
|
+
# false otherwise.
|
50
50
|
#
|
51
|
-
# @example
|
52
|
-
#
|
51
|
+
# @example Setting an expiration of one day
|
52
|
+
# object.update_expiration(86400)
|
53
53
|
#
|
54
|
-
# @note If TTL is zero,
|
55
|
-
#
|
54
|
+
# @note If TTL is set to zero, the expiration will be removed, making the
|
55
|
+
# data persist indefinitely.
|
56
56
|
#
|
57
|
-
# @raise [Familia::Problem]
|
58
|
-
#
|
57
|
+
# @raise [Familia::Problem] Raises an error if the TTL is not a non-negative
|
58
|
+
# integer.
|
59
59
|
#
|
60
60
|
def update_expiration(ttl = nil)
|
61
61
|
ttl ||= self.ttl
|
@@ -74,41 +74,93 @@ module Familia
|
|
74
74
|
fields << name
|
75
75
|
attr_accessor name
|
76
76
|
|
77
|
-
# Every field gets a fast
|
78
|
-
|
77
|
+
# Every field gets a fast attribute method for immediately persisting
|
78
|
+
fast_attribute! name
|
79
79
|
end
|
80
80
|
|
81
|
-
# Defines a
|
81
|
+
# Defines a fast attribute method with a bang (!) suffix for a given
|
82
|
+
# attribute name. Fast attribute methods are used to immediately read or
|
83
|
+
# write attribute values from/to Redis. Calling a fast attribute method
|
84
|
+
# has no effect on any of the object's other attributes and does not
|
85
|
+
# trigger a call to update the object's expiration time.
|
82
86
|
#
|
83
87
|
# The dynamically defined method performs the following:
|
84
|
-
# -
|
88
|
+
# - Acts as both a reader and a writer method.
|
89
|
+
# - When called without arguments, retrieves the current value from Redis.
|
90
|
+
# - When called with an argument, persists the value to Redis immediately.
|
91
|
+
# - Checks if the correct number of arguments is provided (zero or one).
|
85
92
|
# - Converts the provided value to a format suitable for Redis storage.
|
86
|
-
# - Uses the existing accessor method to set the attribute value
|
87
|
-
#
|
88
|
-
# -
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
93
|
+
# - Uses the existing accessor method to set the attribute value when
|
94
|
+
# writing.
|
95
|
+
# - Persists the value to Redis immediately using the hset command when
|
96
|
+
# writing.
|
97
|
+
# - Includes custom error handling to raise an ArgumentError if the wrong
|
98
|
+
# number of arguments is given.
|
99
|
+
# - Raises a custom error message if an exception occurs during the
|
100
|
+
# execution of the method.
|
101
|
+
#
|
102
|
+
# @param [Symbol, String] name the name of the attribute for which the
|
103
|
+
# fast method is defined.
|
104
|
+
# @return [Object] the current value of the attribute when called without
|
105
|
+
# arguments.
|
106
|
+
# @raise [ArgumentError] if more than one argument is provided.
|
107
|
+
# @raise [RuntimeError] if an exception occurs during the execution of the
|
108
|
+
# method.
|
109
|
+
#
|
110
|
+
def fast_attribute!(name = nil)
|
111
|
+
# Fast attribute accessor method for the '#{name}' attribute.
|
112
|
+
# This method provides immediate read and write access to the attribute
|
113
|
+
# in Redis.
|
114
|
+
#
|
115
|
+
# When called without arguments, it retrieves the current value of the
|
116
|
+
# attribute from Redis.
|
117
|
+
# When called with an argument, it immediately persists the new value to
|
118
|
+
# Redis.
|
119
|
+
#
|
120
|
+
# @overload #{name}!
|
121
|
+
# Retrieves the current value of the attribute from Redis.
|
122
|
+
# @return [Object] the current value of the attribute.
|
123
|
+
#
|
124
|
+
# @overload #{name}!(value)
|
125
|
+
# Sets and immediately persists the new value of the attribute to
|
126
|
+
# Redis.
|
127
|
+
# @param value [Object] the new value to set for the attribute.
|
128
|
+
# @return [Object] the newly set value.
|
129
|
+
#
|
130
|
+
# @raise [ArgumentError] if more than one argument is provided.
|
131
|
+
# @raise [RuntimeError] if an exception occurs during the execution of
|
132
|
+
# the method.
|
133
|
+
#
|
134
|
+
# @note This method bypasses any object-level caching and interacts
|
135
|
+
# directly with Redis. It does not trigger updates to other attributes
|
136
|
+
# or the object's expiration time.
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
#
|
140
|
+
# def #{name}!(*args)
|
141
|
+
# # Method implementation
|
142
|
+
# end
|
143
|
+
#
|
96
144
|
define_method :"#{name}!" do |*args|
|
97
145
|
# Check if the correct number of arguments is provided (exactly one).
|
98
|
-
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" if args.size
|
146
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0 or 1)" if args.size > 1
|
147
|
+
|
148
|
+
val = args.first
|
99
149
|
|
100
|
-
value
|
150
|
+
# If no value is provided to this fast attribute method, make a call
|
151
|
+
# to redis to return the current stored value of the hash field.
|
152
|
+
return hget name if val.nil?
|
101
153
|
|
102
154
|
begin
|
103
155
|
# Trace the operation if debugging is enabled.
|
104
|
-
Familia.trace :FAST_WRITER, redis, "#{name}: #{
|
156
|
+
Familia.trace :FAST_WRITER, redis, "#{name}: #{val.inspect}", caller(1..1) if Familia.debug?
|
105
157
|
|
106
158
|
# Convert the provided value to a format suitable for Redis storage.
|
107
|
-
prepared = to_redis(
|
108
|
-
Familia.ld "[.
|
159
|
+
prepared = to_redis(val)
|
160
|
+
Familia.ld "[.fast_attribute!] #{name} val: #{val.class} prepared: #{prepared.class}"
|
109
161
|
|
110
162
|
# Use the existing accessor method to set the attribute value.
|
111
|
-
send :"#{name}=",
|
163
|
+
send :"#{name}=", val
|
112
164
|
|
113
165
|
# Persist the value to Redis immediately using the hset command.
|
114
166
|
hset name, prepared
|
@@ -23,8 +23,9 @@ module Familia
|
|
23
23
|
self.class.exists? identifier, suffix
|
24
24
|
end
|
25
25
|
|
26
|
-
# Sets a timeout on key. After the timeout has expired, the key will
|
27
|
-
# Returns 1 if the timeout was set, 0 if key
|
26
|
+
# Sets a timeout on key. After the timeout has expired, the key will
|
27
|
+
# automatically be deleted. Returns 1 if the timeout was set, 0 if key
|
28
|
+
# does not exist or the timeout could not be set.
|
28
29
|
#
|
29
30
|
def expire(ttl = nil)
|
30
31
|
ttl ||= self.class.ttl
|
@@ -6,18 +6,26 @@ module Familia
|
|
6
6
|
# Familia::Horreum
|
7
7
|
#
|
8
8
|
class Horreum
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# -
|
14
|
-
# -
|
15
|
-
# -
|
9
|
+
# The Sacred Scrolls of Redis Responses
|
10
|
+
#
|
11
|
+
# Behold! The mystical runes that Redis whispers back to us:
|
12
|
+
#
|
13
|
+
# "OK" - The sweet sound of success, like a tiny "ding!" from the depths of data.
|
14
|
+
# true - The boolean Buddha nods in agreement.
|
15
|
+
# 1 - A lonely digit, standing tall and proud. "I did something!" it proclaims.
|
16
|
+
# 0 - The silent hero. It tried its best, bless its heart.
|
17
|
+
# nil - The zen master of responses. It's not nothing, it's... enlightenment!
|
18
|
+
#
|
19
|
+
# These sacred signs are our guide through the Redis wilderness. When we cast
|
20
|
+
# our spells (er, commands), we seek these friendly faces in the returned
|
21
|
+
# smoke signals.
|
22
|
+
#
|
23
|
+
# Should our Redis rituals summon anything else, we pause. We ponder. We
|
24
|
+
# possibly panic. For the unexpected in Redis-land is like finding a penguin
|
25
|
+
# in your pasta - delightfully confusing, but probably not what you ordered.
|
26
|
+
#
|
27
|
+
# May your Redis returns be ever valid, and your data ever flowing!
|
16
28
|
#
|
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
29
|
@valid_command_return_values = ["OK", true, 1, 0, nil]
|
22
30
|
|
23
31
|
class << self
|
@@ -117,7 +125,7 @@ module Familia
|
|
117
125
|
# @note This method will leave breadcrumbs (traces) if you're in debug mode.
|
118
126
|
# It's like Hansel and Gretel, but for data operations!
|
119
127
|
#
|
120
|
-
def save
|
128
|
+
def save update_expiration: true
|
121
129
|
Familia.trace :SAVE, redis, redisuri, caller(1..1) if Familia.debug?
|
122
130
|
|
123
131
|
# Update our object's life story
|
@@ -126,7 +134,9 @@ module Familia
|
|
126
134
|
self.created ||= Familia.now.to_i
|
127
135
|
|
128
136
|
# Commit our tale to the Redis chronicles
|
129
|
-
|
137
|
+
#
|
138
|
+
# e.g. `ret` # => MultiResult.new(true, ["OK", "OK"])
|
139
|
+
ret = commit_fields(update_expiration: update_expiration)
|
130
140
|
|
131
141
|
Familia.ld "[save] #{self.class} #{rediskey} #{ret}"
|
132
142
|
|
@@ -200,15 +210,15 @@ module Familia
|
|
200
210
|
# @note The expiration update is only performed for classes that have
|
201
211
|
# the expiration feature enabled. For others, it's a no-op.
|
202
212
|
#
|
203
|
-
def commit_fields
|
204
|
-
Familia.ld "[
|
213
|
+
def commit_fields update_expiration: true
|
214
|
+
Familia.ld "[commit_fields1] #{self.class} #{rediskey} #{to_h} (update_expiration: #{update_expiration})"
|
205
215
|
command_return_values = transaction do |conn|
|
206
216
|
hmset
|
207
217
|
|
208
218
|
# Only classes that have the expiration ferature enabled will
|
209
219
|
# actually set an expiration time on their keys. Otherwise
|
210
|
-
# this will be a no-op.
|
211
|
-
update_expiration
|
220
|
+
# this will be a no-op that simply logs the attempt.
|
221
|
+
self.update_expiration if update_expiration
|
212
222
|
end
|
213
223
|
|
214
224
|
# The acceptable redis command return values are defined in the
|
@@ -224,10 +234,10 @@ module Familia
|
|
224
234
|
# Log the unexpected
|
225
235
|
unless summary_boolean
|
226
236
|
unexpected_values = command_return_values.reject { |value| acceptable_values.include?(value) }
|
227
|
-
Familia.warn "[commit_fields] Unexpected return values: #{unexpected_values}"
|
237
|
+
Familia.warn "[commit_fields] Unexpected return values: #{unexpected_values.inspect}"
|
228
238
|
end
|
229
239
|
|
230
|
-
Familia.ld "[
|
240
|
+
Familia.ld "[commit_fields2] #{self.class} #{rediskey} #{summary_boolean}: #{command_return_values}"
|
231
241
|
|
232
242
|
MultiResult.new(summary_boolean, command_return_values)
|
233
243
|
end
|
@@ -256,13 +266,22 @@ module Familia
|
|
256
266
|
delete!
|
257
267
|
end
|
258
268
|
|
259
|
-
#
|
260
|
-
#
|
261
|
-
# object
|
269
|
+
# The Great Redis Refresh-o-matic 3000
|
270
|
+
#
|
271
|
+
# Imagine your object as a forgetful time traveler. This method is like
|
272
|
+
# zapping it with a memory ray from Redis-topia. ZAP! New memories!
|
273
|
+
#
|
274
|
+
# WARNING: This is not a gentle mind-meld. It's more like a full brain
|
275
|
+
# transplant. Any half-baked ideas floating in your object's head? POOF!
|
276
|
+
# Gone quicker than cake at a hobbit's birthday party. Unsaved spells
|
277
|
+
# will definitely be forgotten.
|
278
|
+
#
|
279
|
+
# @return What do you get for this daring act of digital amnesia? A shiny
|
280
|
+
# list of all the brain bits that got a makeover!
|
281
|
+
#
|
282
|
+
# Remember: In the game of Redis-Refresh, you win or you... well, you
|
283
|
+
# always win, but sometimes you forget why you played in the first place.
|
262
284
|
#
|
263
|
-
# @note This is a destructive operation that will overwrite any unsaved
|
264
|
-
# changes.
|
265
|
-
# @return The list of field names that were updated.
|
266
285
|
def refresh!
|
267
286
|
Familia.trace :REFRESH, redis, redisuri, caller(1..1) if Familia.debug?
|
268
287
|
fields = hgetall
|
@@ -270,14 +289,20 @@ module Familia
|
|
270
289
|
optimistic_refresh(**fields)
|
271
290
|
end
|
272
291
|
|
273
|
-
#
|
274
|
-
#
|
275
|
-
#
|
292
|
+
# Ah, the magical refresh dance! It's like giving your object a
|
293
|
+
# sip from the fountain of youth.
|
294
|
+
#
|
295
|
+
# This method twirls your object around, dips it into the Redis pool,
|
296
|
+
# and brings it back sparkling clean and up-to-date. It's using the
|
297
|
+
# refresh! spell behind the scenes, so expect some Redis whispering.
|
298
|
+
#
|
299
|
+
# @note Caution, young Rubyist! While this method loves to play
|
300
|
+
# chain-tag with other methods, it's still got that refresh! kick.
|
301
|
+
# It'll update your object faster than you can say "matz!"
|
302
|
+
#
|
303
|
+
# @return [self] Your object, freshly bathed in Redis waters, ready
|
304
|
+
# to dance with more methods in a conga line of Ruby joy!
|
276
305
|
#
|
277
|
-
# @note While this method allows chaining, it still performs a
|
278
|
-
# destructive update like refresh!.
|
279
|
-
# @return [self] Returns the object itself after refreshing, allowing
|
280
|
-
# method chaining.
|
281
306
|
def refresh
|
282
307
|
refresh!
|
283
308
|
self
|
@@ -332,35 +357,43 @@ module Familia
|
|
332
357
|
end
|
333
358
|
end
|
334
359
|
|
335
|
-
#
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
# -
|
341
|
-
#
|
342
|
-
#
|
343
|
-
#
|
344
|
-
# -
|
345
|
-
# -
|
346
|
-
#
|
347
|
-
#
|
348
|
-
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
352
|
-
#
|
353
|
-
#
|
354
|
-
#
|
355
|
-
#
|
356
|
-
#
|
357
|
-
#
|
358
|
-
#
|
359
|
-
#
|
360
|
-
#
|
360
|
+
# Behold, the grand tale of two serialization sorcerers:
|
361
|
+
# Familia::Redistype and Familia::Horreum!
|
362
|
+
#
|
363
|
+
# These twin wizards, though cut from the same magical cloth,
|
364
|
+
# have their own unique spells for turning Ruby objects into
|
365
|
+
# Redis-friendly potions. Let's peek into their spell books:
|
366
|
+
#
|
367
|
+
# Shared Incantations:
|
368
|
+
# - Both transform various data creatures for Redis safekeeping
|
369
|
+
# - They tame wild Strings, Symbols, and those slippery Numerics
|
370
|
+
# - Secret rituals (aka custom serialization) are welcome
|
371
|
+
#
|
372
|
+
# Mystical Differences:
|
373
|
+
# - Redistype reads the future in opts[:class] tea leaves
|
374
|
+
# - Horreum prefers to interrogate types more thoroughly
|
375
|
+
# - Redistype leaves a trail of debug breadcrumbs
|
376
|
+
#
|
377
|
+
# But wait! Enter the wise Familia.distinguisher,
|
378
|
+
# a grand unifier of serialization magic!
|
379
|
+
#
|
380
|
+
# This clever mediator:
|
381
|
+
# 1. Juggles a circus of data types from both realms
|
382
|
+
# 2. Offers a 'strict_values' toggle for the type-obsessed
|
383
|
+
# 3. Welcomes custom spells via dump_method
|
384
|
+
# 4. Sprinkles debug fairy dust à la Redistype
|
385
|
+
#
|
386
|
+
# By channeling the Familia.distinguisher, we've created a
|
387
|
+
# harmonious serialization symphony, flexible enough to dance
|
388
|
+
# with any data type that shimmies our way. And should we need
|
389
|
+
# to teach it new tricks, we know just where to wave our wands!
|
390
|
+
#
|
391
|
+
# @param value [Object] The mystical object to be transformed
|
392
|
+
#
|
393
|
+
# @return [String] The transformed, Redis-ready value.
|
361
394
|
#
|
362
395
|
def to_redis(val)
|
363
|
-
prepared = Familia.distinguisher(val, false)
|
396
|
+
prepared = Familia.distinguisher(val, strict_values: false)
|
364
397
|
|
365
398
|
if prepared.nil? && val.respond_to?(dump_method)
|
366
399
|
prepared = val.send(dump_method)
|
@@ -376,31 +409,33 @@ module Familia
|
|
376
409
|
end
|
377
410
|
# End of Serialization module
|
378
411
|
|
379
|
-
#
|
412
|
+
# The magical MultiResult, keeper of Redis's deepest secrets!
|
380
413
|
#
|
381
|
-
# This class
|
382
|
-
#
|
383
|
-
#
|
414
|
+
# This quirky little class wraps up the outcome of a Redis "transaction"
|
415
|
+
# (or as I like to call it, a "Redis dance party") with a bow made of
|
416
|
+
# pure Ruby delight. It knows if your commands were successful and
|
417
|
+
# keeps the results safe in its pocket dimension.
|
384
418
|
#
|
385
|
-
# @attr_reader success [Boolean]
|
386
|
-
# in the transaction
|
387
|
-
# @attr_reader results [Array<String>]
|
388
|
-
#
|
419
|
+
# @attr_reader success [Boolean] The golden ticket! True if all your
|
420
|
+
# Redis wishes came true in the transaction.
|
421
|
+
# @attr_reader results [Array<String>] A mystical array of return values,
|
422
|
+
# each one a whisper from the Redis gods.
|
389
423
|
#
|
390
|
-
# @example
|
424
|
+
# @example Summoning a MultiResult from the void
|
391
425
|
# result = MultiResult.new(true, ["OK", "OK"])
|
392
426
|
#
|
393
|
-
# @example
|
427
|
+
# @example Divining the success of your Redis ritual
|
394
428
|
# if result.successful?
|
395
|
-
# puts "
|
429
|
+
# puts "Huzzah! The Redis spirits smile upon you!"
|
396
430
|
# else
|
397
|
-
# puts "
|
431
|
+
# puts "Alas! The Redis gremlins have conspired against us!"
|
398
432
|
# end
|
399
433
|
#
|
400
|
-
# @example
|
434
|
+
# @example Peering into the raw essence of results
|
401
435
|
# result.results.each_with_index do |value, index|
|
402
|
-
# puts "Command #{index + 1}
|
436
|
+
# puts "Command #{index + 1} whispered back: #{value}"
|
403
437
|
# end
|
438
|
+
#
|
404
439
|
class MultiResult
|
405
440
|
# @return [Boolean] true if all commands in the transaction succeeded,
|
406
441
|
# false otherwise
|
@@ -14,7 +14,7 @@ module Familia
|
|
14
14
|
|
15
15
|
def redisuri(suffix = nil)
|
16
16
|
u = Familia.redisuri(self.class.uri) # returns URI::Redis
|
17
|
-
u.db
|
17
|
+
u.db = db if db # override the db if we have one
|
18
18
|
u.key = rediskey(suffix)
|
19
19
|
u
|
20
20
|
end
|
data/lib/familia/horreum.rb
CHANGED
@@ -24,7 +24,7 @@ module Familia
|
|
24
24
|
class Horreum
|
25
25
|
include Familia::Base
|
26
26
|
|
27
|
-
#
|
27
|
+
# Singleton Class Context
|
28
28
|
#
|
29
29
|
# The code within this block operates on the singleton class (also known as
|
30
30
|
# eigenclass or metaclass) of the current class. This means:
|
@@ -73,6 +73,18 @@ module Familia
|
|
73
73
|
Familia.ld "[Horreum] Initializing #{self.class}"
|
74
74
|
initialize_relatives
|
75
75
|
|
76
|
+
# Automatically add a 'key' field if it's not already defined. This ensures
|
77
|
+
# that every object horreum class has a unique identifier field. Ideally
|
78
|
+
# this logic would live somewhere else b/c we only need to call it once
|
79
|
+
# per class definition. Here it gets called every time an instance is
|
80
|
+
# instantiated/
|
81
|
+
unless self.class.fields.include?(:key)
|
82
|
+
# Define the 'key' field for this class
|
83
|
+
# This approach allows flexibility in how identifiers are generated
|
84
|
+
# while ensuring each object has a consistent way to be referenced
|
85
|
+
self.class.field :key # , default: -> { identifier }
|
86
|
+
end
|
87
|
+
|
76
88
|
# If there are positional arguments, they should be the field
|
77
89
|
# values in the order they were defined in the implementing class.
|
78
90
|
#
|
@@ -94,15 +106,6 @@ module Familia
|
|
94
106
|
# end
|
95
107
|
end
|
96
108
|
|
97
|
-
# Automatically add a 'key' field if it's not already defined
|
98
|
-
# This ensures that every object has a unique identifier
|
99
|
-
unless self.class.fields.include?(:key)
|
100
|
-
# Define the 'key' field for this class
|
101
|
-
# This approach allows flexibility in how identifiers are generated
|
102
|
-
# while ensuring each object has a consistent way to be referenced
|
103
|
-
self.class.field :key # , default: -> { identifier }
|
104
|
-
end
|
105
|
-
|
106
109
|
# Implementing classes can define an init method to do any
|
107
110
|
# additional initialization. Notice that this is called
|
108
111
|
# after the fields are set.
|
@@ -139,9 +142,11 @@ module Familia
|
|
139
142
|
#
|
140
143
|
opts[:parent] = self # unless opts.key(:parent)
|
141
144
|
|
145
|
+
suffix_override = opts.fetch(:suffix, name)
|
146
|
+
|
142
147
|
# Instantiate the RedisType object and below we store it in
|
143
148
|
# an instance variable.
|
144
|
-
redis_type = klass.new
|
149
|
+
redis_type = klass.new suffix_override, opts
|
145
150
|
|
146
151
|
# Freezes the redis_type, making it immutable.
|
147
152
|
# This ensures the object's state remains consistent and prevents any modifications,
|
@@ -26,19 +26,19 @@ class Familia::RedisType
|
|
26
26
|
# @raise [Familia::HighRiskFactor] If serialization fails under strict
|
27
27
|
# mode.
|
28
28
|
#
|
29
|
-
def to_redis(val, strict_values
|
29
|
+
def to_redis(val, strict_values: true)
|
30
30
|
prepared = nil
|
31
31
|
|
32
32
|
Familia.trace :TOREDIS, redis, "#{val}<#{val.class}|#{opts[:class]}>", caller(1..1) if Familia.debug?
|
33
33
|
|
34
34
|
if opts[:class]
|
35
|
-
prepared = Familia.distinguisher(opts[:class], strict_values)
|
35
|
+
prepared = Familia.distinguisher(opts[:class], strict_values: strict_values)
|
36
36
|
Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared||'<nil>'}"
|
37
37
|
end
|
38
38
|
|
39
39
|
if prepared.nil?
|
40
40
|
# Enforce strict values when no class option is specified
|
41
|
-
prepared = Familia.distinguisher(val, true)
|
41
|
+
prepared = Familia.distinguisher(val, strict_values: true)
|
42
42
|
Familia.ld " from <#{val.class}> => <#{prepared.class}>"
|
43
43
|
end
|
44
44
|
|
data/lib/familia/redistype.rb
CHANGED
@@ -50,7 +50,7 @@ module Familia
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def score(val)
|
53
|
-
ret = redis.zscore rediskey, to_redis(val, false)
|
53
|
+
ret = redis.zscore rediskey, to_redis(val, strict_values: false)
|
54
54
|
ret&.to_f
|
55
55
|
end
|
56
56
|
alias [] score
|
@@ -63,13 +63,13 @@ module Familia
|
|
63
63
|
|
64
64
|
# rank of member +v+ when ordered lowest to highest (starts at 0)
|
65
65
|
def rank(v)
|
66
|
-
ret = redis.zrank rediskey, to_redis(v, false)
|
66
|
+
ret = redis.zrank rediskey, to_redis(v, strict_values: false)
|
67
67
|
ret&.to_i
|
68
68
|
end
|
69
69
|
|
70
70
|
# rank of member +v+ when ordered highest to lowest (starts at 0)
|
71
71
|
def revrank(v)
|
72
|
-
ret = redis.zrevrank rediskey, to_redis(v, false)
|
72
|
+
ret = redis.zrevrank rediskey, to_redis(v, strict_values: false)
|
73
73
|
ret&.to_i
|
74
74
|
end
|
75
75
|
|
@@ -208,7 +208,7 @@ module Familia
|
|
208
208
|
# the identifier and not a serialized version of the object. So either
|
209
209
|
# the value exists in the sorted set or it doesn't -- we don't need to
|
210
210
|
# raise an error if it's not found.
|
211
|
-
redis.zrem rediskey, to_redis(val, false)
|
211
|
+
redis.zrem rediskey, to_redis(val, strict_values: false)
|
212
212
|
end
|
213
213
|
alias remove delete
|
214
214
|
alias rem delete
|
data/lib/familia/utils.rb
CHANGED
@@ -69,6 +69,7 @@ module Familia
|
|
69
69
|
# @param uri [String, URI] URI to convert
|
70
70
|
# @return [URI::Redis] Redis URI object
|
71
71
|
def redisuri(uri)
|
72
|
+
uri ||= Familia.uri
|
72
73
|
generic_uri = URI.parse(uri.to_s)
|
73
74
|
|
74
75
|
# Create a new URI::Redis object
|
@@ -77,7 +78,7 @@ module Familia
|
|
77
78
|
userinfo: generic_uri.userinfo,
|
78
79
|
host: generic_uri.host,
|
79
80
|
port: generic_uri.port,
|
80
|
-
path: generic_uri.path,
|
81
|
+
path: generic_uri.path, # the db is stored in the path
|
81
82
|
query: generic_uri.query,
|
82
83
|
fragment: generic_uri.fragment
|
83
84
|
)
|
@@ -124,37 +125,60 @@ module Familia
|
|
124
125
|
DIGEST_CLASS.hexdigest(concatenated_string)
|
125
126
|
end
|
126
127
|
|
127
|
-
# This method determines the appropriate
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
128
|
+
# This method determines the appropriate transformation to apply based on
|
129
|
+
# the class of the input argument.
|
130
|
+
#
|
131
|
+
# @param [Object] value_to_distinguish The value to be processed. Keep in
|
132
|
+
# mind that all data in redis is stored as a string so whatever the type
|
133
|
+
# of the value, it will be converted to a string.
|
134
|
+
# @param [Boolean] strict_values Whether to enforce strict value handling.
|
135
|
+
# Defaults to true.
|
136
|
+
# @return [String, nil] The processed value as a string or nil for unsupported
|
137
|
+
# classes.
|
138
|
+
#
|
139
|
+
# The method uses a case statement to handle different classes:
|
140
|
+
# - For `Symbol`, `String`, `Integer`, and `Float` classes, it traces the
|
141
|
+
# operation and converts the value to a string.
|
142
|
+
# - For `Familia::Horreum` class, it traces the operation and returns the
|
143
|
+
# identifier of the value.
|
144
|
+
# - For `TrueClass`, `FalseClass`, and `NilClass`, it traces the operation and
|
145
|
+
# converts the value to a string ("true", "false", or "").
|
132
146
|
# - For any other class, it traces the operation and returns nil.
|
133
147
|
#
|
134
|
-
# Alternative names for `value_to_distinguish` could be `input_value`, `value`,
|
135
|
-
|
148
|
+
# Alternative names for `value_to_distinguish` could be `input_value`, `value`,
|
149
|
+
# or `object`.
|
150
|
+
#
|
151
|
+
def distinguisher(value_to_distinguish, strict_values: true)
|
136
152
|
case value_to_distinguish
|
137
153
|
when ::Symbol, ::String, ::Integer, ::Float
|
138
|
-
|
154
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "string", caller(1..1) if Familia.debug?
|
155
|
+
|
139
156
|
# Symbols and numerics are naturally serializable to strings
|
140
157
|
# so it's a relatively low risk operation.
|
141
158
|
value_to_distinguish.to_s
|
142
159
|
|
143
160
|
when ::TrueClass, ::FalseClass, ::NilClass
|
144
|
-
|
145
|
-
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
161
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "true/false/nil", caller(1..1) if Familia.debug?
|
162
|
+
|
163
|
+
# TrueClass, FalseClass, and NilClass are considered high risk because their
|
164
|
+
# original types cannot be reliably determined from their serialized string
|
165
|
+
# representations. This can lead to unexpected behavior during deserialization.
|
166
|
+
# For instance, a TrueClass value serialized as "true" might be deserialized as
|
167
|
+
# a String, causing application errors. Even more problematic, a NilClass value
|
168
|
+
# serialized as an empty string makes it impossible to distinguish between a
|
169
|
+
# nil value and an empty string upon deserialization. Such scenarios can result
|
170
|
+
# in subtle, hard-to-diagnose bugs. To mitigate these risks, we raise an
|
171
|
+
# exception when encountering these types unless the strict_values option is
|
172
|
+
# explicitly set to false.
|
152
173
|
#
|
153
174
|
raise Familia::HighRiskFactor, value_to_distinguish if strict_values
|
154
175
|
value_to_distinguish.to_s #=> "true", "false", ""
|
155
176
|
|
156
177
|
when Familia::Base, Class
|
157
|
-
|
178
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "base", caller(1..1) if Familia.debug?
|
179
|
+
|
180
|
+
# When called with a class we simply transform it to its name. For
|
181
|
+
# instances of Familia class, we store the identifier.
|
158
182
|
if value_to_distinguish.is_a?(Class)
|
159
183
|
value_to_distinguish.name
|
160
184
|
else
|
@@ -162,14 +186,15 @@ module Familia
|
|
162
186
|
end
|
163
187
|
|
164
188
|
else
|
165
|
-
|
189
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "else1 #{strict_values}", caller(1..1) if Familia.debug?
|
166
190
|
|
167
191
|
if value_to_distinguish.class.ancestors.member?(Familia::Base)
|
168
|
-
|
192
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "isabase", caller(1..1) if Familia.debug?
|
193
|
+
|
169
194
|
value_to_distinguish.identifier
|
170
195
|
|
171
196
|
else
|
172
|
-
|
197
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "else2 #{strict_values}", caller(1..1) if Familia.debug?
|
173
198
|
raise Familia::HighRiskFactor, value_to_distinguish if strict_values
|
174
199
|
nil
|
175
200
|
end
|
data/try/27_redis_horreum_try.rb
CHANGED
@@ -47,12 +47,12 @@ Familia.debug = false
|
|
47
47
|
@customer.save
|
48
48
|
#=> true
|
49
49
|
|
50
|
-
## Horreum object fields have a fast
|
50
|
+
## Horreum object fields have a fast attribute method (1 of 2)
|
51
51
|
Familia.trace :LOAD, @customer.redis, @customer.redisuri, caller if Familia.debug?
|
52
52
|
@customer.name! 'Jane Doe'
|
53
53
|
#=> 0
|
54
54
|
|
55
|
-
## Horreum object fields have a fast
|
55
|
+
## Horreum object fields have a fast attribute method (2 of 2)
|
56
56
|
@customer.refresh!
|
57
57
|
@customer.name
|
58
58
|
#=> "Jane Doe"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: familia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.pre.
|
4
|
+
version: 1.0.0.pre.rc7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|