Module: Familia::Horreum::Serialization
- Included in:
- Familia::Horreum
- Defined in:
- lib/familia/horreum/serialization.rb
Overview
Serialization: Where Objects Go to Become Strings (and Vice Versa)!
This module is chock-full of methods that’ll make your head spin (in a good way)! We’ve got loaders, dumpers, and refreshers galore. It’s like a laundromat for your data, but instead of quarters, it runs on Redis commands.
A Note on Our Refreshing Refreshers: In the wild world of Ruby, ‘!’ usually means “Watch out! I’m dangerous!” But here in Familia-land, we march to the beat of a different drummer. Our refresh! method is the real deal, doing all the heavy lifting. The non-bang refresh? Oh, it’s just as rowdy, but it plays nice with method chaining. It’s like the polite twin who still knows how to party.
Remember: In Familia, refreshing isn’t just a chore, it’s a chance to dance with data! Whether you bang(!) or not, you’re still invited to the Redis disco.
(P.S. If you’re reading these docs, lol sorry. I asked Claude 3.5 to write in the style of _why the lucky stiff today and got this uncanny valley response. I hope you enjoy reading it as much as I did writing the prompt for it. - @delano).
(Ahem! What I meant to say was that if you’re reading this, congratulations! You’ve stumbled upon the secret garden of documentation. Feel free to smell the Ruby roses, but watch out for the Redis thorns!)
Instance Attribute Summary collapse
-
#redis ⇒ Redis
Summon the mystical Redis connection from the depths of instance or class.
Instance Method Summary collapse
-
#apply_fields(**fields) ⇒ self
Apply a smattering of fields to this object like fairy dust.
-
#batch_update(**kwargs) ⇒ MultiResult
Updates multiple fields atomically in a Redis transaction.
-
#clear_fields! ⇒ void
The Great Nilpocalypse: clear_fields!.
-
#commit_fields(update_expiration: true) ⇒ MultiResult
Commit our precious fields to Redis.
-
#deserialize_value(val, symbolize: true) ⇒ Object
(also: #from_redis)
Converts a Redis string value back to its original Ruby type.
-
#destroy! ⇒ void
Dramatically vanquish this object from the face of Redis! (ed: delete it).
-
#refresh ⇒ self
Ah, the magical refresh dance! It’s like giving your object a sip from the fountain of youth.
-
#refresh! ⇒ void
The Great Redis Refresh-o-matic 3000.
-
#save(update_expiration: true) ⇒ Boolean
Save our precious data to Redis, with a sprinkle of timestamp magic!.
-
#serialize_value(val) ⇒ String
(also: #to_redis)
Behold, the grand tale of two serialization sorcerers: Familia::Redistype and Familia::Horreum!.
-
#to_a ⇒ Array
Line up all our attributes in a neat little array parade!.
-
#to_h ⇒ Hash
Transform this object into a magical hash of wonders!.
-
#transaction {|conn| ... } ⇒ Object
Perform a sacred Redis transaction ritual.
Instance Attribute Details
#redis ⇒ Redis
Summon the mystical Redis connection from the depths of instance or class.
This method is like a magical divining rod, always pointing to the nearest source of Redis goodness. It first checks if we have a personal Redis connection (@redis), and if not, it borrows the class’s connection.
77 78 79 |
# File 'lib/familia/horreum/serialization.rb', line 77 def redis @redis || self.class.redis end |
Instance Method Details
#apply_fields(**fields) ⇒ self
Apply a smattering of fields to this object like fairy dust.
191 192 193 194 195 196 197 |
# File 'lib/familia/horreum/serialization.rb', line 191 def apply_fields(**fields) fields.each do |field, value| # Whisper the new value into the object's ear (if it's listening) send("#{field}=", value) if respond_to?("#{field}=") end self end |
#batch_update(**kwargs) ⇒ MultiResult
Updates multiple fields atomically in a Redis transaction.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/familia/horreum/serialization.rb', line 153 def batch_update(**kwargs) update_expiration = kwargs.delete(:update_expiration) { true } fields = kwargs Familia.trace :BATCH_UPDATE, redis, fields.keys, caller(1..1) if Familia.debug? command_return_values = transaction do |conn| fields.each do |field, value| prepared_value = serialize_value(value) conn.hset rediskey, field, prepared_value # Update instance variable to keep object in sync send("#{field}=", value) if respond_to?("#{field}=") end end # Update expiration if requested and supported self.update_expiration(ttl: nil) if update_expiration && respond_to?(:update_expiration) # Return same MultiResult format as other methods summary_boolean = command_return_values.all? { |ret| %w[OK 0 1].include?(ret.to_s) } MultiResult.new(summary_boolean, command_return_values) end |
#clear_fields! ⇒ void
This method returns an undefined value.
The Great Nilpocalypse: clear_fields!
Imagine your object as a grand old mansion, every room stuffed with trinkets, secrets, and the odd rubber duck. This method? It flings open every window and lets a wild wind of nothingness sweep through, leaving each field as empty as a poet’s wallet.
All your precious attributes—gone! Swept into the void! It’s a spring cleaning for the soul, a reset button for your existential dread.
322 323 324 |
# File 'lib/familia/horreum/serialization.rb', line 322 def clear_fields! self.class.fields.each { |field| send("#{field}=", nil) } end |
#commit_fields(update_expiration: true) ⇒ MultiResult
Be warned, young programmer! This method dabbles in the arcane art of transactions. Side effects may include data persistence and a slight tingling sensation. The method does not raise exceptions for unexpected Redis responses, but logs warnings and returns a failure status.
This method performs logging at various levels:
-
Debug: Logs the object’s class, Redis key, and current state before committing
-
Warn: Logs any unexpected return values from Redis commands
-
Debug: Logs the final result, including success status and all return values
The expiration update is only performed for classes that have the expiration feature enabled. For others, it’s a no-op.
Commit our precious fields to Redis.
This method performs a sacred ritual, sending our cherished attributes on a journey through the ethernet to find their resting place in Redis. It executes a transaction that includes setting field values and, if applicable, updating the expiration time.
The MultiResult object responds to:
- successful?: Returns the boolean success value
- results: Returns the array of command return values
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/familia/horreum/serialization.rb', line 246 def commit_fields update_expiration: true Familia.ld "[commit_fields1] #{self.class} #{rediskey} #{to_h} (update_expiration: #{update_expiration})" command_return_values = transaction do |conn| conn.hmset rediskey(suffix), self.to_h # using the prepared connection end # Only classes that have the expiration ferature enabled will # actually set an expiration time on their keys. Otherwise # this will be a no-op that simply logs the attempt. self.update_expiration(ttl: nil) if update_expiration # The acceptable redis command return values are defined in the # Horreum class. This is to ensure that all commands return values # are validated against a consistent set of values. acceptable_values = Familia::Horreum.valid_command_return_values # Check if all return values are valid summary_boolean = command_return_values.uniq.all? { |value| acceptable_values.include?(value) } # Log the unexpected unless summary_boolean unexpected_values = command_return_values.reject { |value| acceptable_values.include?(value) } Familia.warn "[commit_fields] Unexpected return values: #{unexpected_values.inspect}" end Familia.ld "[commit_fields2] #{self.class} #{rediskey} #{summary_boolean}: #{command_return_values}" MultiResult.new(summary_boolean, command_return_values) end |
#deserialize_value(val, symbolize: true) ⇒ Object Also known as: from_redis
Converts a Redis string value back to its original Ruby type
This method attempts to deserialize JSON strings back to their original Hash or Array types. Simple string values are returned as-is.
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/familia/horreum/serialization.rb', line 488 def deserialize_value(val, symbolize: true) return val if val.nil? || val == "" # Try to parse as JSON first for complex types begin parsed = JSON.parse(val, symbolize_names: symbolize) # Only return parsed value if it's a complex type (Hash/Array) # Simple values should remain as strings return parsed if parsed.is_a?(Hash) || parsed.is_a?(Array) rescue JSON::ParserError # Not valid JSON, return as-is end val end |
#destroy! ⇒ void
If debugging is enabled, this method will leave a trace of its destructive path, like breadcrumbs for future data archaeologists.
This method returns an undefined value.
Dramatically vanquish this object from the face of Redis! (ed: delete it)
This method is the doomsday device of our little data world. It will mercilessly eradicate all traces of our object from Redis, leaving naught but digital dust in its wake. Use with caution, lest you accidentally destroy the wrong data-verse!
This method is part of Familia’s high-level object lifecycle management. While ‘delete!` operates directly on Redis keys, `destroy!` operates at the object level and is used for ORM-style operations. Use `destroy!` when removing complete objects from the system, and `delete!` when working directly with Redis keys.
301 302 303 304 |
# File 'lib/familia/horreum/serialization.rb', line 301 def destroy! Familia.trace :DESTROY, redis, redisuri, caller(1..1) if Familia.debug? delete! end |
#refresh ⇒ self
Caution, young Rubyist! While this method loves to play chain-tag with other methods, it’s still got that refresh! kick. It’ll update your object faster than you can say “matz!”
Ah, the magical refresh dance! It’s like giving your object a sip from the fountain of youth.
This method twirls your object around, dips it into the Redis pool, and brings it back sparkling clean and up-to-date. It’s using the refresh! spell behind the scenes, so expect some Redis whispering.
370 371 372 373 |
# File 'lib/familia/horreum/serialization.rb', line 370 def refresh refresh! self end |
#refresh! ⇒ void
This method returns an undefined value.
The Great Redis Refresh-o-matic 3000
Imagine your object as a forgetful time traveler. This method is like zapping it with a memory ray from Redis-topia. ZAP! New memories!
WARNING: This is not a gentle mind-meld. It’s more like a full brain transplant. Any half-baked ideas floating in your object’s head? POOF! Gone quicker than cake at a hobbit’s birthday party. Unsaved spells will definitely be forgotten.
list of all the brain bits that got a makeover!
Remember: In the game of Redis-Refresh, you win or you… well, you always win, but sometimes you forget why you played in the first place.
346 347 348 349 350 351 352 |
# File 'lib/familia/horreum/serialization.rb', line 346 def refresh! Familia.trace :REFRESH, redis, redisuri, caller(1..1) if Familia.debug? raise Familia::KeyNotFoundError, rediskey unless redis.exists(rediskey) fields = hgetall Familia.ld "[refresh!] #{self.class} #{rediskey} #{fields.keys}" optimistic_refresh(**fields) end |
#save(update_expiration: true) ⇒ Boolean
This method will leave breadcrumbs (traces) if you’re in debug mode. It’s like Hansel and Gretel, but for data operations!
Save our precious data to Redis, with a sprinkle of timestamp magic!
This method is like a conscientious historian, not only recording your object’s current state but also meticulously timestamping when it was created and last updated. It’s the record keeper of your data’s life story!
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/familia/horreum/serialization.rb', line 121 def save update_expiration: true Familia.trace :SAVE, redis, redisuri, caller(1..1) if Familia.debug? # Update our object's life story, keeping the mandatory built-in # key field in sync with the field that is the chosen identifier. self.key = self.identifier self.created ||= Familia.now.to_i if respond_to?(:created) self.updated = Familia.now.to_i if respond_to?(:updated) # Commit our tale to the Redis chronicles # # e.g. `ret` # => MultiResult.new(true, ["OK", "OK"]) ret = commit_fields(update_expiration: update_expiration) Familia.ld "[save] #{self.class} #{rediskey} #{ret} (update_expiration: #{update_expiration})" # Did Redis accept our offering? ret.successful? end |
#serialize_value(val) ⇒ String Also known as: to_redis
Behold, the grand tale of two serialization sorcerers: Familia::Redistype and Familia::Horreum!
These twin wizards, though cut from the same magical cloth, have their own unique spells for turning Ruby objects into Redis-friendly potions. Let’s peek into their spell books:
Shared Incantations:
-
Both transform various data creatures for Redis safekeeping
-
They tame wild Strings, Symbols, and those slippery Numerics
-
Secret rituals (aka custom serialization) are welcome
Mystical Differences:
-
Redistype reads the future in opts tea leaves
-
Horreum prefers to interrogate types more thoroughly
-
Redistype leaves a trail of debug breadcrumbs
But wait! Enter the wise Familia.distinguisher, a grand unifier of serialization magic!
This clever mediator:
-
Juggles a circus of data types from both realms
-
Offers a ‘strict_values’ toggle for the type-obsessed
-
Welcomes custom spells via dump_method
-
Sprinkles debug fairy dust à la Redistype
By channeling the Familia.distinguisher, we’ve created a harmonious serialization symphony, flexible enough to dance with any data type that shimmies our way. And should we need to teach it new tricks, we know just where to wave our wands!
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/familia/horreum/serialization.rb', line 461 def serialize_value(val) prepared = Familia.distinguisher(val, strict_values: false) # If the distinguisher returns nil, try using the dump_method but only # use JSON serialization for complex types that need it. if prepared.nil? && (val.is_a?(Hash) || val.is_a?(Array)) prepared = val.respond_to?(dump_method) ? val.send(dump_method) : JSON.dump(val) end # If both the distinguisher and dump_method return nil, log an error if prepared.nil? Familia.ld "[#{self.class}#serialize_value] nil returned for #{self.class}" end prepared end |
#to_a ⇒ Array
Each value is carefully disguised in its Redis costume
Line up all our attributes in a neat little array parade!
This method marshals all our object’s attributes into an orderly procession, ready to march into Redis in perfect formation. It’s like a little data army, but friendlier and less prone to conquering neighboring databases.
before joining the parade.
417 418 419 420 421 422 423 424 |
# File 'lib/familia/horreum/serialization.rb', line 417 def to_a self.class.fields.map do |field| val = send(field) prepared = serialize_value(val) Familia.ld " [to_a] field: #{field} val: #{val.class} prepared: #{prepared.class}" prepared end end |
#to_h ⇒ Hash
Watch in awe as each field is lovingly prepared for its Redis adventure!
Transform this object into a magical hash of wonders!
This method performs an alchemical transmutation, turning our noble object into a more plebeian hash. But fear not, for in this form, it can slip through the cracks of the universe (or at least, into Redis) with ease.
390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/familia/horreum/serialization.rb', line 390 def to_h self.class.fields.inject({}) do |hsh, field| val = send(field) prepared = serialize_value(val) Familia.ld " [to_h] field: #{field} val: #{val.class} prepared: #{prepared&.class || '[nil]'}" # Only include non-nil values in the hash for Redis hsh[field] = prepared unless prepared.nil? hsh end end |
#transaction {|conn| ... } ⇒ Object
This method temporarily replaces your Redis connection with a multi connection. Don’t worry, it puts everything back where it found it when it’s done.
Perform a sacred Redis transaction ritual.
This method creates a protective circle around your Redis operations, ensuring they all succeed or fail together. It’s like a group hug for your data operations, but with more ACID properties.
99 100 101 102 103 |
# File 'lib/familia/horreum/serialization.rb', line 99 def transaction redis.multi do |conn| yield(conn) end end |