familia 1.0.0.pre.rc5 → 1.0.0.pre.rc6

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: 3218c877946104986176b606caef17360ba262e22372dd61597982f9b97661db
4
- data.tar.gz: b699a7b951ccf64c3421c9f79e29f927d3df0a36a0caa005575e118ae8ad000c
3
+ metadata.gz: 4d368f329560a1afd7252a2c8de830a7230334933cd00715b4b952e44d9fbf63
4
+ data.tar.gz: edd7b843ff07cf87aa2fe46766d7b0fd6dfa814b5404c9ca04bc42de18b05619
5
5
  SHA512:
6
- metadata.gz: a19eca2cdcf6fe40e2c4109199938f95c02af3e3f6f4dda247720c983e6340ed725143a350f72b00a7c0c8d16bdab7dfb82ac4314a4d786249381f34fcb2da35
7
- data.tar.gz: 2a9b815eed8437ea6514a7d29288ecf151c371ed2013495a5eaa6e2294bf61f75c07c9946c28dbf75fcdd0df7fdc1b5890ab0d83a9354f951c0f43e7718f0df8
6
+ metadata.gz: 0f4db87dbaa2c12636d8293a477ef1e2a000364d3e6e2515cac90dad7277efc29d3670103cdf4f7697960ec90012ddec7071004146785ec7d3c838863e1a241b
7
+ data.tar.gz: a1812fce749dc485753a10c39281feb1babab24fd95ecdd553398c4a07a800836d7bf4ac4a7574ee987d4d181842e089cd732efa343141a0bf4610594ea8f1cd
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (1.0.0.pre.rc5)
4
+ familia (1.0.0.pre.rc6)
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-rc5 (August 2024)
1
+ # Familia - 1.0.0-rc6 (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: rc5
5
+ :PRE: rc6
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(_ = nil)
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
- # Yo, check it out! We're gonna give our Redis data an expiration date!
40
+ # Sets an expiration time for the Redis data associated with this object.
41
41
  #
42
- # It's like slapping a "Best Before" sticker on your favorite snack,
43
- # but for data. How cool is that?
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. Nil? No worries!
46
- # We'll dig up the default from our secret stash.
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] Did Redis pin that expiry note successfully?
49
- # True for "Yep!", false for "Oops, butter fingers!"
48
+ # @return [Boolean] Returns true if the expiration was set successfully,
49
+ # false otherwise.
50
50
  #
51
- # @example Teaching your pet rock the concept of mortality
52
- # rocky.update_expiration(86400) # Dwayne gets to party in Redis for one whole day!
51
+ # @example Setting an expiration of one day
52
+ # object.update_expiration(86400)
53
53
  #
54
- # @note If TTL is zero, your data gets a VIP pass to the Redis eternity club.
55
- # Fancy, huh?
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] If you try to feed it non-numbers or time-travel
58
- # (negative numbers). It's strict, but fair!
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
@@ -78,7 +78,11 @@ module Familia
78
78
  fast_writer! name
79
79
  end
80
80
 
81
- # Defines a writer method with a bang (!) suffix for a given attribute name.
81
+ # Defines a fast writer method with a bang (!) suffix for a given
82
+ # attribute name. Fast writer methods are used to immediately persist
83
+ # attribute values to Redis. Calling a fast writer method has no
84
+ # effect on any of the object's other attributes and does not trigger
85
+ # a called to update the object's expiration time.
82
86
  #
83
87
  # The dynamically defined method performs the following:
84
88
  # - Checks if the correct number of arguments is provided (exactly one).
@@ -97,18 +101,18 @@ module Familia
97
101
  # Check if the correct number of arguments is provided (exactly one).
98
102
  raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" if args.size != 1
99
103
 
100
- value = args.first
104
+ val = args.first
101
105
 
102
106
  begin
103
107
  # Trace the operation if debugging is enabled.
104
- Familia.trace :FAST_WRITER, redis, "#{name}: #{value.inspect}", caller(1..1) if Familia.debug?
108
+ Familia.trace :FAST_WRITER, redis, "#{name}: #{val.inspect}", caller(1..1) if Familia.debug?
105
109
 
106
110
  # Convert the provided value to a format suitable for Redis storage.
107
- prepared = to_redis(value)
108
- Familia.ld "[.fast_writer!] #{name} val: #{value.class} prepared: #{prepared.class}"
111
+ prepared = to_redis(val)
112
+ Familia.ld "[.fast_writer!] #{name} val: #{val.class} prepared: #{prepared.class}"
109
113
 
110
114
  # Use the existing accessor method to set the attribute value.
111
- send :"#{name}=", value
115
+ send :"#{name}=", val
112
116
 
113
117
  # Persist the value to Redis immediately using the hset command.
114
118
  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 automatically be deleted.
27
- # Returns 1 if the timeout was set, 0 if key does not exist or the timeout could not be set.
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
- # 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.
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
- ret = commit_fields # e.g. MultiResult.new(true, ["OK", "OK"])
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 "[commit_fields] #{self.class} #{rediskey} #{to_h}"
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 "[commit_fields] #{self.class} #{rediskey} #{summary_boolean}: #{command_return_values}"
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
- # Refreshes the object's state by querying Redis and overwriting the
260
- # current field values. This method performs a destructive update on the
261
- # object, regardless of unsaved changes.
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
- # Refreshes the object's state and returns self to allow method chaining.
274
- # This method calls refresh! internally, performing the actual Redis
275
- # query and state update.
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
- # The to_redis method in Familia::Redistype and Familia::Horreum serve
336
- # similar purposes but have some key differences in their implementation:
337
- #
338
- # Similarities:
339
- # - Both methods aim to serialize various data types for Redis storage
340
- # - Both handle basic data types like String, Symbol, and Numeric
341
- # - Both have provisions for custom serialization methods
342
- #
343
- # Differences:
344
- # - Familia::Redistype uses the opts[:class] for type hints
345
- # - Familia::Horreum had more explicit type checking and conversion
346
- # - Familia::Redistype includes more extensive debug tracing
347
- #
348
- # The centralized Familia.distinguisher method accommodates both approaches
349
- # by:
350
- # 1. Handling a wide range of data types, including those from both
351
- # implementations
352
- # 2. Providing a 'strict_values' option for flexible type handling
353
- # 3. Supporting custom serialization through a dump_method
354
- # 4. Including debug tracing similar to Familia::Redistype
355
- #
356
- # By using Familia.distinguisher, we achieve more consistent behavior
357
- # across different parts of the library while maintaining the flexibility
358
- # to handle various data types and custom serialization needs. This
359
- # centralization also makes it easier to extend or modify serialization
360
- # behavior in the future.
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
- # Represents the result of a multiple Redis commands.
412
+ # The magical MultiResult, keeper of Redis's deepest secrets!
380
413
  #
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").
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] 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.
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 Creating a MultiResult
424
+ # @example Summoning a MultiResult from the void
391
425
  # result = MultiResult.new(true, ["OK", "OK"])
392
426
  #
393
- # @example Checking the success of a commit
427
+ # @example Divining the success of your Redis ritual
394
428
  # if result.successful?
395
- # puts "All commands succeeded"
429
+ # puts "Huzzah! The Redis spirits smile upon you!"
396
430
  # else
397
- # puts "Some commands failed"
431
+ # puts "Alas! The Redis gremlins have conspired against us!"
398
432
  # end
399
433
  #
400
- # @example Accessing raw results
434
+ # @example Peering into the raw essence of results
401
435
  # result.results.each_with_index do |value, index|
402
- # puts "Command #{index + 1} returned: #{value}"
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 ||= self.class.db.to_s # TODO: revisit logic (should the horrerum instance know its uri?)
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
@@ -24,7 +24,7 @@ module Familia
24
24
  class Horreum
25
25
  include Familia::Base
26
26
 
27
- # == Singleton Class Context
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.
@@ -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 = true)
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
 
@@ -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 value to return based on the class of the input argument.
128
- # It uses a case statement to handle different classes:
129
- # - For Symbol, String, Integer, and Float classes, it traces the operation and converts the value to a string.
130
- # - For Familia::Horreum class, it traces the operation and returns the identifier of the value.
131
- # - For TrueClass, FalseClass, and NilClass, it traces the operation and converts the value to a string ("true", "false", or "").
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`, or `object`.
135
- def distinguisher(value_to_distinguish, strict_values = true)
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
- #Familia.trace :TOREDIS_DISTINGUISHER, redis, "string", caller(1..1) if Familia.debug?
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
- #Familia.trace :TOREDIS_DISTINGUISHER, redis, "true/false/nil", caller(1..1) if Familia.debug?
145
- # TrueClass, FalseClass, and NilClass are high risk because we can't
146
- # reliably determine the original type of the value from the serialized
147
- # string. This can lead to unexpected behavior when deserializing. For
148
- # example, if a TrueClass value is serialized as "true" and then later
149
- # deserialized as a String, it can cause errors in the application. Worse
150
- # still, if a NilClass value is serialized as an empty string we lose the
151
- # ability to distinguish between a nil value and an empty string when
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
- #Familia.trace :TOREDIS_DISTINGUISHER, redis, "base", caller(1..1) if Familia.debug?
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
- #Familia.trace :TOREDIS_DISTINGUISHER, redis, "else1 #{strict_values}", caller(1..1) if Familia.debug?
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
- #Familia.trace :TOREDIS_DISTINGUISHER, redis, "isabase", caller(1..1) if Familia.debug?
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
- #Familia.trace :TOREDIS_DISTINGUISHER, redis, "else2 #{strict_values}", caller(1..1) if Familia.debug?
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
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.rc5
4
+ version: 1.0.0.pre.rc6
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-20 00:00:00.000000000 Z
11
+ date: 2024-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis