Module: Ruact::Serializable::ClassMethods

Defined in:
lib/ruact/serializable.rb

Instance Method Summary collapse

Instance Method Details

#ruact_deferred_props_listArray<Symbol>

Names whose eager loud check was deferred to first serialize (lazy ActiveRecord attributes). Read from the same class that declared the props, so subclasses share the parent declaration.

Returns:

  • (Array<Symbol>)


94
95
96
97
98
99
100
101
102
103
104
# File 'lib/ruact/serializable.rb', line 94

def ruact_deferred_props_list
  klass = self
  while klass
    if klass.instance_variable_defined?(:@ruact_props)
      return klass.instance_variable_get(:@ruact_deferred_props) || []
    end

    klass = klass.superclass
  end
  []
end

#ruact_lazy_attribute_class?Boolean

True when this class defines its attribute reader methods lazily, i.e. an ActiveRecord model. Detected WITHOUT a hard ActiveRecord dependency (the gem stays single-dep nokogiri): the constant is only referenced when it is already defined in the host.

Returns:

  • (Boolean)


137
138
139
# File 'lib/ruact/serializable.rb', line 137

def ruact_lazy_attribute_class?
  !!(defined?(ActiveRecord::Base) && self < ActiveRecord::Base)
end

#ruact_props(*attrs) ⇒ Object

Declare which instance methods should be included in the serialized payload.

The loud-omission guarantee is preserved: a name with no corresponding method still raises a clean ArgumentError. Only the timing of that check depends on the class:

  • PORO — checked eagerly at class-load (macro-invocation) time, as before. A typo'd/absent prop raises immediately.
  • ActiveRecord model — ActiveRecord defines its attribute reader methods lazily (on first instance access), so at macro-invocation time method_defined?(:title) is false even for a real column. Checking eagerly would either reject a valid model at boot or require a live DB connection at class-load (a Rails anti-pattern). So for a lazy-attribute class the not-yet-defined names are recorded and their loud check is deferred to the first ruact_serialize (via respond_to? on the instance), where the DB is up. A genuine typo still raises the same clean ArgumentError — just at first render of that model, not at boot.

Parameters:

  • attrs (Array<Symbol>)

Raises:

  • (ArgumentError)

    immediately for an undefined prop on a PORO; at first ruact_serialize for an undefined prop on an ActiveRecord model.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ruact/serializable.rb', line 55

def ruact_props(*attrs)
  deferred = []
  attrs.each do |attr|
    next if method_defined?(attr)

    # Lazy-attribute (ActiveRecord) class: the reader may still appear on
    # first instance access. Record it and check loudly on first serialize
    # instead of failing a valid model at boot.
    if ruact_lazy_attribute_class?
      deferred << attr
      next
    end

    raise ArgumentError,
          "ruact_props: method `#{attr}` is not defined on #{self}"
  end
  @ruact_props = attrs
  @ruact_deferred_props = deferred
end

#ruact_props_listArray<Symbol>

Returns the list of declared prop names as symbols. Walks the ancestor chain so subclasses inherit parent declarations.

Returns:

  • (Array<Symbol>)


79
80
81
82
83
84
85
86
87
# File 'lib/ruact/serializable.rb', line 79

def ruact_props_list
  klass = self
  while klass
    return klass.instance_variable_get(:@ruact_props) if klass.instance_variable_defined?(:@ruact_props)

    klass = klass.superclass
  end
  []
end

#ruact_validate_deferred_props!(instance) ⇒ Object

Run the deferred loud check once, on first ruact_serialize. A recorded name that the instance does not respond_to? (a typo, or a genuinely absent column) raises the same clean ArgumentError the eager path would.

Memoization keys on the validated deferred list itself (not a bare boolean), so any change to the effective declaration — a re-declaration on this class OR on an ancestor whose props a subclass inherits — is detected and re-validated loudly on the next serialize. Once a given list has been validated it costs one array comparison per serialize.

Parameters:

  • instance (Object)

Raises:

  • (ArgumentError)


118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/ruact/serializable.rb', line 118

def ruact_validate_deferred_props!(instance)
  deferred = ruact_deferred_props_list
  return if @ruact_deferred_props_validated == deferred

  deferred.each do |attr|
    next if instance.respond_to?(attr)

    raise ArgumentError,
          "ruact_props: method `#{attr}` is not defined on #{self}"
  end
  @ruact_deferred_props_validated = deferred
end