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.



51
52
53
# File 'lib/familia/horreum/serialization.rb', line 51

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: "little boys named Jackie")
# => #<Dragon:0x007f8a1c8b0a28 @name="Puff", @breathes="fire", @loves="little boys 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.



130
131
132
133
134
135
136
# File 'lib/familia/horreum/serialization.rb', line 130

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_fieldsArray<String>

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.

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.

Examples:

Offering your changes to the Redis deities

unicorn.name = "Charlie"
unicorn.horn_length = "magnificent"
unicorn.commit_fields
# => ["OK", "OK"] (The Redis gods are pleased with your offering)

Returns:

  • (Array<String>)

    A mystical array of strings, cryptic messages from the Redis gods.



154
155
156
157
158
159
160
# File 'lib/familia/horreum/serialization.rb', line 154

def commit_fields
  Familia.ld "[commit_fields] #{self.class} #{rediskey} #{to_h}"
  transaction do |conn|
    hmset
    update_expiration
  end
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.


181
182
183
184
# File 'lib/familia/horreum/serialization.rb', line 181

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

#refreshself

Note:

While this method allows chaining, it still performs a destructive update like refresh!.

Refreshes the object’s state and returns self to allow method chaining. This method calls refresh! internally, performing the actual Redis query and state update.

Returns:

  • (self)

    Returns the object itself after refreshing, allowing method chaining.



209
210
211
212
# File 'lib/familia/horreum/serialization.rb', line 209

def refresh
  refresh!
  self
end

#refresh!Object

Note:

This is a destructive operation that will overwrite any unsaved changes.

Refreshes the object’s state by querying Redis and overwriting the current field values. This method performs a destructive update on the object, regardless of unsaved changes.

Returns:

  • The list of field names that were updated.



194
195
196
197
198
199
# File 'lib/familia/horreum/serialization.rb', line 194

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

#saveBoolean

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.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/familia/horreum/serialization.rb', line 102

def save
  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
  ret = commit_fields # e.g. ["OK"]

  Familia.ld "[save] #{self.class} #{rediskey} #{ret}"

  # Did Redis accept our offering?
  ret.uniq.all? { |value| value == "OK" }
end

#to_aArray

Note:

Each value is carefully disguised in its Redis costume before joining the parade.

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.

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.



252
253
254
255
256
257
258
259
# File 'lib/familia/horreum/serialization.rb', line 252

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.



228
229
230
231
232
233
234
235
236
# File 'lib/familia/horreum/serialization.rb', line 228

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) ⇒ Object

The to_redis method in Familia::Redistype and Familia::Horreum serve similar purposes but have some key differences in their implementation:

Similarities:

  • Both methods aim to serialize various data types for Redis storage

  • Both handle basic data types like String, Symbol, and Numeric

  • Both have provisions for custom serialization methods

Differences:

  • Familia::Redistype uses the opts for type hints

  • Familia::Horreum had more explicit type checking and conversion

  • Familia::Redistype includes more extensive debug tracing

The centralized Familia.distinguisher method accommodates both approaches by:

  1. Handling a wide range of data types, including those from both implementations

  2. Providing a ‘strict_values’ option for flexible type handling

  3. Supporting custom serialization through a dump_method

  4. Including debug tracing similar to Familia::Redistype

By using Familia.distinguisher, we achieve more consistent behavior across different parts of the library while maintaining the flexibility to handle various data types and custom serialization needs. This centralization also makes it easier to extend or modify serialization behavior in the future.



288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/familia/horreum/serialization.rb', line 288

def to_redis(val)
  prepared = Familia.distinguisher(val, 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.



73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/familia/horreum/serialization.rb', line 73

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

#update_expiration(ttl = nil) ⇒ Boolean

Note:

If the TTL is zero, we assume our data wants to live forever. Immortality in Redis! Who wouldn’t want that?

Set an expiration date for our data, like a “best before” sticker for Redis!

This method gives our data a lifespan in Redis. It’s like telling Redis, “Hey, this data is fresh now, but it might get stale after a while!”

Examples:

Making your pet rock data mortal

rocky.update_expiration(86400) # Dwayne will live in Redis for one day

Parameters:

  • ttl (Integer, nil) (defaults to: nil)

    The Time To Live in seconds. If nil, we’ll check our options for a default expiration time.

Returns:

  • (Boolean)

    true if the expiration was set successfully, false otherwise. It’s like asking Redis, “Did you stick that expiration label on properly?”



319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/familia/horreum/serialization.rb', line 319

def update_expiration(ttl = nil)
  ttl ||= opts[:ttl]
  ttl = ttl.to_i

  return if ttl.zero?

  Familia.ld "Setting expiration for #{rediskey} to #{ttl} seconds"

  # EXPIRE command returns 1 if the timeout was set, 0 if key does not
  # exist or the timeout could not be set.
  expire(ttl).positive?
end