Module: Familia::Horreum::Serialization
- Included in:
- Familia::Horreum
- Defined in:
- lib/familia/horreum/serialization.rb
Overview
Serialization - Instance-level methods for object serialization Handles conversion between Ruby objects and Valkey hash storage
Instance Method Summary collapse
-
#debug_fields ⇒ Hash{String => Hash}
Returns a diagnostic hash showing Ruby values vs stored JSON side-by-side.
-
#deserialize_value(val, symbolize: false, field_name: nil) ⇒ Object
Converts a Redis string value back to its original Ruby type.
-
#serialize_value(val) ⇒ String
Serializes a Ruby object for Valkey storage.
-
#to_a ⇒ Array
Converts the object's persistent fields to an array.
-
#to_h ⇒ Hash
Converts the object's persistent fields to a hash for external use.
-
#to_h_for_storage ⇒ Hash
Converts the object's persistent fields to a hash for database storage.
Instance Method Details
#debug_fields ⇒ Hash{String => Hash}
Returns a diagnostic hash showing Ruby values vs stored JSON side-by-side.
Useful for debugging double-encoding issues or understanding the serialization boundary. Each field maps to a hash showing the Ruby value, the JSON string that would be stored, and the Ruby type.
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/familia/horreum/serialization.rb', line 96 def debug_fields self.class.persistent_fields.each_with_object({}) do |field, hsh| field_type = self.class.field_types[field] val = send(field_type.method_name) stored = serialize_value(val) hsh[field.to_s] = { ruby: val, stored: stored, type: val.class.name, } end end |
#deserialize_value(val, symbolize: false, field_name: nil) ⇒ Object
Converts a Redis string value back to its original Ruby type
This method deserializes JSON strings back to their original Ruby types (Integer, Boolean, Float, nil, Hash, Array). Plain strings that cannot be parsed as JSON are returned as-is.
This pairs with serialize_value which JSON-encodes all non-string values. The contract ensures type preservation across Redis storage:
- Strings stored as-is → returned as-is
- All other types JSON-encoded → JSON-decoded back to original type
195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/familia/horreum/serialization.rb', line 195 def deserialize_value(val, symbolize: false, field_name: nil) return nil if val.nil? || val == '' # Handle Redis::Future objects during transactions return val if val.is_a?(Redis::Future) begin Familia::JsonSerializer.parse(val, symbolize_names: symbolize) rescue Familia::SerializerError log_deserialization_issue(val, field_name) val end end |
#serialize_value(val) ⇒ String
Strings are JSON-encoded to prevent type coercion bugs where string "123" would be indistinguishable from integer 123 in storage
This method integrates with Familia's type system and supports custom serialization methods when available on the object
Serializes a Ruby object for Valkey storage.
Converts ALL Ruby values (including strings) to JSON-encoded strings for type-safe storage. This ensures round-trip type preservation: the type you save is the type you get back.
The serialization process:
- ConcealedStrings (encrypted values) → extract encrypted_value
- ALL other types → JSON serialization (String, Integer, Boolean, Float, nil, Hash, Array)
170 171 172 173 174 175 176 177 |
# File 'lib/familia/horreum/serialization.rb', line 170 def serialize_value(val) # Security: Handle ConcealedString safely - extract encrypted data for storage return val.encrypted_value if val.respond_to?(:encrypted_value) # ALWAYS write valid JSON for type preservation # This includes strings, which get JSON-encoded with wrapping quotes Familia::JsonSerializer.dump(val) end |
#to_a ⇒ Array
Values are serialized using the same process as other persistence methods to maintain data consistency across operations.
Converts the object's persistent fields to an array.
Serializes all persistent field values in field definition order, preparing them for Valkey storage. Each value is processed through the serialization pipeline to ensure Valkey compatibility.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/familia/horreum/serialization.rb', line 126 def to_a self.class.persistent_fields.map do |field| field_type = self.class.field_types[field] # Security: Skip non-loggable fields (e.g., encrypted fields) next unless field_type.loggable method_name = field_type.method_name val = send(method_name) Familia.debug " [to_a] field: #{field} method: #{method_name} val: #{val.class}" # Return actual Ruby values, including nil to maintain array positions val end end |
#to_h ⇒ Hash
Only loggable fields are included. Encrypted fields are excluded.
Nil values are excluded from the returned hash (storage optimization)
Converts the object's persistent fields to a hash for external use.
Returns actual Ruby values (String, Integer, Hash, etc.) for API consumption, NOT JSON-encoded strings. Excludes non-loggable fields like encrypted fields for security.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/familia/horreum/serialization.rb', line 27 def to_h self.class.persistent_fields.each_with_object({}) do |field, hsh| field_type = self.class.field_types[field] # Security: Skip non-loggable fields (e.g., encrypted fields) next unless field_type.loggable val = send(field_type.method_name) Familia.debug " [to_h] field: #{field} val: #{val.class}" # Use string key for external API compatibility # Return Ruby values, not JSON-encoded strings hsh[field.to_s] = val end end |
#to_h_for_storage ⇒ Hash
This is an internal method used by commit_fields and hmset
Nil values are excluded to optimize Redis storage
Converts the object's persistent fields to a hash for database storage.
Returns JSON-encoded strings for ALL persistent field values, ready for Redis storage. Unlike to_h, this includes encrypted fields and serializes values using serialize_value (JSON encoding).
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/familia/horreum/serialization.rb', line 60 def to_h_for_storage self.class.persistent_fields.each_with_object({}) do |field, hsh| field_type = self.class.field_types[field] val = send(field_type.method_name) prepared = serialize_value(val) if Familia.debug? Familia.debug " [to_h_for_storage] field: #{field} val: #{val.class} prepared: #{prepared&.class || '[nil]'}" end # Use string key for database compatibility hsh[field.to_s] = prepared end end |