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

Instance Method Summary collapse

Instance Attribute Details

#redisRedis

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.

Examples:

Finding your Redis way

puts object.redis
# => #<Redis client v4.5.1 for redis://localhost:6379/0>

Returns:

  • (Redis)

    A shimmering Redis connection, ready for your bidding.



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.

Examples:

Giving your object a makeover

dragon.apply_fields(name: "Puff", breathes: "fire", loves: "Toys
named Jackie")
# => #<Dragon:0x007f8a1c8b0a28 @name="Puff", @breathes="fire",
@loves="Toys named Jackie">

Parameters:

  • fields (Hash)

    A magical bag of named attributes to sprinkle onto this instance. Each key-value pair is like a tiny spell, ready to enchant our object’s properties.

Returns:

  • (self)

    Returns the newly bejeweled instance, now sparkling with fresh attributes.



162
163
164
165
166
167
168
# File 'lib/familia/horreum/serialization.rb', line 162

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

#commit_fields(update_expiration: true) ⇒ MultiResult

Note:

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.

Note:

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

Note:

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

Examples:

Offering your changes to the Redis deities

unicorn.name = "Charlie"
unicorn.horn_length = "magnificent"
result = unicorn.commit_fields
if result.successful?
  puts "The Redis gods are pleased with your offering"
  p result.results  # => ["OK", "OK"]
else
  puts "The Redis gods frown upon your offering"
  p result.results  # Examine the unexpected values
end

Returns:

  • (MultiResult)

    A mystical object containing:

    • success: A boolean indicating if all Redis commands succeeded

    • results: An array of strings, cryptic messages from the Redis gods

See Also:



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/familia/horreum/serialization.rb', line 213

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|
    hmset

    # 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 if update_expiration
  end

  # 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

#destroy!void

Note:

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!

Examples:

Bidding a fond farewell to your pet rock

rocky = PetRock.new(name: "Dwayne")
rocky.destroy!
# => *poof* Rocky is no more. A moment of silence, please.

See Also:

  • The actual hitman carrying out the deed.


264
265
266
267
# File 'lib/familia/horreum/serialization.rb', line 264

def destroy!
  Familia.trace :DESTROY, redis, redisuri, caller(1..1) if Familia.debug?
  delete!
end

#refreshself

Note:

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.

Returns:

  • (self)

    Your object, freshly bathed in Redis waters, ready to dance with more methods in a conga line of Ruby joy!



306
307
308
309
# File 'lib/familia/horreum/serialization.rb', line 306

def refresh
  refresh!
  self
end

#refresh!Object

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.

Returns:

  • What do you get for this daring act of digital amnesia? A shiny



285
286
287
288
289
290
# File 'lib/familia/horreum/serialization.rb', line 285

def refresh!
  Familia.trace :REFRESH, redis, redisuri, caller(1..1) if Familia.debug?
  fields = hgetall
  Familia.ld "[refresh!] #{self.class} #{rediskey} #{fields.keys}"
  optimistic_refresh(**fields)
end

#save(update_expiration: true) ⇒ Boolean

Note:

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!

Examples:

Preserving your pet rock for posterity

rocky = PetRock.new(name: "Dwayne")
rocky.save
# => true (Dwayne is now immortalized in Redis)

Returns:

  • (Boolean)

    true if the save was successful, false if Redis was grumpy.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/familia/horreum/serialization.rb', line 128

def save update_expiration: true
  Familia.trace :SAVE, redis, redisuri, caller(1..1) if Familia.debug?

  # Update our object's life story
  self.key ||= self.identifier
  self.updated = Familia.now.to_i
  self.created ||= Familia.now.to_i

  # 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}"

  # Did Redis accept our offering?
  ret.successful?
end

#to_aArray

Note:

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.

Examples:

Arranging your unicorn’s attributes in a line

unicorn.to_a
# => ["Charlie", "magnificent", 5]

Returns:

  • (Array)

    A splendid array of Redis-ready values, in the order of our fields.



351
352
353
354
355
356
357
358
# File 'lib/familia/horreum/serialization.rb', line 351

def to_a
  self.class.fields.map do |field|
    val = send(field)
    prepared = to_redis(val)
    Familia.ld " [to_a] field: #{field} val: #{val.class} prepared: #{prepared.class}"
    prepared
  end
end

#to_hHash

Note:

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.

Examples:

Turning your dragon into a hash

dragon.to_h
# => {"name"=>"Puff", "breathes"=>"fire", "age"=>1000}

Returns:

  • (Hash)

    A glittering hash, each key a field name, each value a Redis-ready treasure.



326
327
328
329
330
331
332
333
334
# File 'lib/familia/horreum/serialization.rb', line 326

def to_h
  self.class.fields.inject({}) do |hsh, field|
    val = send(field)
    prepared = to_redis(val)
    Familia.ld " [to_h] field: #{field} val: #{val.class} prepared: #{prepared.class}"
    hsh[field] = prepared
    hsh
  end
end

#to_redis(val) ⇒ String

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:

  1. Juggles a circus of data types from both realms

  2. Offers a ‘strict_values’ toggle for the type-obsessed

  3. Welcomes custom spells via dump_method

  4. 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!

Parameters:

  • value (Object)

    The mystical object to be transformed

Returns:

  • (String)

    The transformed, Redis-ready value.



395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/familia/horreum/serialization.rb', line 395

def to_redis(val)
  prepared = Familia.distinguisher(val, strict_values: false)

  if prepared.nil? && val.respond_to?(dump_method)
    prepared = val.send(dump_method)
  end

  if prepared.nil?
    Familia.ld "[#{self.class}#to_redis] nil returned for #{self.class}##{name}"
  end

  prepared
end

#transaction {|conn| ... } ⇒ Object

Note:

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.

Examples:

Performing a Redis rain dance

transaction do |conn|
  conn.set("weather", "rainy")
  conn.set("mood", "melancholic")
end

Yields:

  • (conn)

    A block where you can perform your Redis incantations.

Yield Parameters:

  • conn (Redis)

    A Redis connection in multi mode.



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/familia/horreum/serialization.rb', line 99

def transaction
  original_redis = self.redis

  begin
    redis.multi do |conn|
      self.instance_variable_set(:@redis, conn)
      yield(conn)
    end
  ensure
    self.redis = original_redis
  end
end