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.
110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/familia/horreum/serialization.rb', line 110 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
209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/familia/horreum/serialization.rb', line 209 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)
184 185 186 187 188 189 190 191 |
# File 'lib/familia/horreum/serialization.rb', line 184 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.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/familia/horreum/serialization.rb', line 140 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.
Every persistent field appears, including those whose value is nil. This differs from #to_h_for_storage, which omits nil fields so that absence -- not a stored "null" -- represents "no value" in Valkey/Redis.
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.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/familia/horreum/serialization.rb', line 29 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-valued fields are omitted so that field absence -- not a stored "null" -- represents "no value" in the Valkey/Redis hash. This is what keeps HSETNX/HEXISTS-based claim patterns usable and avoids materializing every declared field. Fields cleared to nil are actively removed from storage on save; see Persistence#remove_stale_nil_fields.
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).
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/familia/horreum/serialization.rb', line 66 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) # Omit nil fields entirely. A Valkey/Redis hash has no NULL: absence is # the native "no value". Persisting nil as the JSON string "null" would # make declared fields perpetually present, defeating HSETNX/HEXISTS and # wasting memory. Round-trips are unaffected -- deserialize_value already # returns nil for an absent field. next if val.nil? 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 |