Module: Minitwin::Serialization

Included in:
Minitwin
Defined in:
lib/minitwin/serialization.rb

Overview

Serialization to Hash/JSON and ActiveModel validation aggregation. Converts nested twins recursively and preserves array items (dropping only nil). When ActiveModel validations are available, nested errors are surfaced on the parent using dot/bracket notation.

Constant Summary collapse

ALIASES_VAR =

Cache constant references for JIT optimization

Minitwin::DYNAMIC_ALIASES_VAR
NESTED_PREFIX =
Minitwin::NESTED_READER_PREFIX

Instance Method Summary collapse

Instance Method Details

#attributesObject

: () -> Hash[Symbol, untyped]



58
59
60
61
62
63
64
# File 'lib/minitwin/serialization.rb', line 58

def attributes
  # Use setter-based attribute names and read via `send` to allow
  # accessing protected original readers when aliases (`as:`) are used.
  attribute_methods.each_with_object({}) do |m, h|
    h[m] = send(m) if respond_to?(m, true)
  end
end

#inspectObject

: () -> String



102
103
104
105
# File 'lib/minitwin/serialization.rb', line 102

def inspect
  attrs = to_hash.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
  "#<#{self.class.name} #{attrs}>"
end

#pretty_print(pretty_printer) ⇒ Object

Internal helper for PrettyPrint. Do not call this on your own. : (PP) -> void



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/minitwin/serialization.rb', line 109

def pretty_print(pretty_printer)
  pretty_printer.object_group(self) do
    pretty_printer.breakable
    pretty_printer.seplist(
      ordered_attributes_for_pp,
      -> {
        pretty_printer.text(",")
        pretty_printer.breakable
      }
    ) do |(name, value)|
      pretty_printer.group do
        pretty_printer.text name.to_s
        pretty_printer.text ": "
        pretty_printer.pp value
      end
    end
  end
end

#to_hash(render_nil: false) ⇒ Object Also known as: to_h

: (render_nil: bool) -> Hash[Symbol, untyped]



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/minitwin/serialization.rb', line 15

def to_hash(render_nil: false)
  hash = Minitwin.hash_klass.new

  methods_to_serialize = self.class.send(:serializable_getters)

  methods_to_serialize.each do |method|
    value = send(method)
    next if value.nil? && !render_nil

    hash[method] = transform_value_for_serialization(value)
  end

  # Include dynamic alias keys (defined per instance via `as: -> { ... }`).
  if instance_variable_defined?(ALIASES_VAR)
    aliases = instance_variable_get(ALIASES_VAR)
    if aliases && !aliases.empty?
      aliases.each do |target_method, alias_method|
        # Skip nested proxy aliases at the top level; nested groups
        # serialize under their container key only.
        next if target_method.is_a?(Symbol) && target_method.to_s.start_with?(NESTED_PREFIX)

        # Read the value from the original target method to avoid issues if
        # the alias method is overridden. Apply the same transformation rules
        # as above for nested twins and arrays.
        value = send(target_method)
        next if value.nil? && !render_nil

        hash[alias_method] = transform_value_for_serialization(value)
      end
    end
  end

  hash
end

#to_jsonObject

: (**untyped) -> String



53
54
55
# File 'lib/minitwin/serialization.rb', line 53

def to_json(**)
  to_hash(**).to_json
end

#valid?Boolean

: () -> bool

Returns:

  • (Boolean)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/minitwin/serialization.rb', line 67

def valid?
  # If ActiveModel validations are available and included, run them and
  # aggregate nested errors. Otherwise, consider the twin valid.
  if defined?(ActiveModel::Validations) && self.class.ancestors.include?(ActiveModel::Validations)
    super

    self.class.block_properties.each do |property|
      child = send(property)
      next unless child.respond_to?(:valid?)

      child.valid?
      child.errors.each do |attribute|
        errors.add("#{property}.#{attribute.attribute}", attribute.message)
      end
    end

    self.class.collection_properties.each do |property|
      send(property).each_with_index do |value, index|
        next unless value.respond_to?(:valid?)

        value.valid?
        value.errors.each do |attribute|
          errors.add("#{property}[#{index}].#{attribute.attribute}", attribute.message)
        end
      end
    end

    errors.empty?
  else
    # When ActiveModel is not available, consider the twin valid by default.
    true
  end
end