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

Instance Method Details

#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

Parameters:

  • val (String)

    The string value from Redis to deserialize

  • symbolize (Boolean) (defaults to: false)

    Whether to symbolize hash keys (default: false)

  • field_name (Symbol, nil) (defaults to: nil)

    Optional field name for better error context

Returns:

  • (Object)

    The deserialized value with original Ruby type, or the original string if not JSON



161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/familia/horreum/serialization.rb', line 161

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

Note:

Strings are JSON-encoded to prevent type coercion bugs where string "123" would be indistinguishable from integer 123 in storage

Note:

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:

  1. ConcealedStrings (encrypted values) → extract encrypted_value
  2. ALL other types → JSON serialization (String, Integer, Boolean, Float, nil, Hash, Array)

Examples:

Type preservation through JSON encoding

serialize_value("007")           # => "\"007\"" (JSON string)
serialize_value(123)             # => "123" (JSON number)
serialize_value(true)            # => "true" (JSON boolean)
serialize_value({a: 1})          # => "{\"a\":1}" (JSON object)

Parameters:

  • val (Object)

    The Ruby object to serialize for Valkey storage

Returns:

  • (String)

    JSON-encoded string representation

See Also:

  • For extracting identifiers from Familia objects


136
137
138
139
140
141
142
143
# File 'lib/familia/horreum/serialization.rb', line 136

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_aArray

Note:

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.

Examples:

Converting an object to array format

user = User.new(name: "John", email: "john@example.com", age: 30)
user.to_a
# => ["John", "john@example.com", "30"]

Returns:

  • (Array)

    Array of serialized field values in field order



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/familia/horreum/serialization.rb', line 92

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_hHash

Note:

Only loggable fields are included. Encrypted fields are excluded.

Note:

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.

Examples:

Converting an object to hash format for API response

user = User.new(name: "John", email: "john@example.com", age: 30)
user.to_h
# => {"name"=>"John", "email"=>"john@example.com", "age"=>30}
# Note: Returns actual Ruby types, not JSON strings

Returns:

  • (Hash)

    Hash with field names as string keys and Ruby values



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_storageHash

Note:

This is an internal method used by commit_fields and hmset

Note:

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).

Examples:

Internal storage preparation

user = User.new(name: "John", age: 30)
user.to_h_for_storage
# => {"name"=>"\"John\"", "age"=>"30"}
# Note: Strings are JSON-encoded with quotes

Returns:

  • (Hash)

    Hash with field names as string keys and JSON-encoded values



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