Module: Minitwin::Initialization

Included in:
Minitwin
Defined in:
lib/minitwin/initialization.rb,
sig/generated/minitwin/initialization.rbs

Overview

Instance construction and low-level helpers used by the DSL-generated accessors. Filters unknown keys on initialize and seeds nested block properties so that validations on nested twins can run.

Constant Summary collapse

ALIASES_VAR =

Cache constant references for JIT optimization

Returns:

  • (Object)
Minitwin::DYNAMIC_ALIASES_VAR
ALIASES_REV_VAR =

Returns:

  • (Object)
Minitwin::DYNAMIC_ALIASES_REV_VAR
FORBIDDEN_ALIAS_NAMES =

Forbidden method names that should never be aliased for security reasons

Returns:

  • (Object)
%i[
  eval instance_eval class_eval module_eval
  send __send__ public_send
  method_missing respond_to_missing?
  define_method remove_method undef_method
  instance_variable_get instance_variable_set
  instance_variables instance_variable_defined?
  const_get const_set
  class_variable_get class_variable_set
  binding tap then yield_self to_proc
  freeze __id__ object_id
  == equal? eql? hash <=>
].freeze

Instance Method Summary collapse

Instance Method Details

#__apply_dynamic_alias__(target_method, alias_name) ⇒ Object

Parameters:

  • target_method (Object)
  • alias_name (Object)

Returns:

  • (Object)


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/minitwin/initialization.rb', line 139

def __apply_dynamic_alias__(target_method, alias_name)
  unless alias_name.is_a?(String) || alias_name.is_a?(Symbol)
    raise ArgumentError, "Invalid alias name #{alias_name.inspect}: must be a String or Symbol"
  end

  alias_key = alias_name.to_sym
  aliases = instance_variable_get(ALIASES_VAR)
  aliases_rev = instance_variable_get(ALIASES_REV_VAR)

  # Security check: prevent aliasing to forbidden method names
  if FORBIDDEN_ALIAS_NAMES.include?(alias_key)
    raise ArgumentError, "Cannot define dynamic alias '#{alias_key}': forbidden method name for security reasons"
  end

  prev = aliases[target_method]
  if prev && prev != alias_key
    begin
      singleton_class.send(:remove_method, prev)
    rescue NameError
      # Expected: method may not exist if previously failed to define
    end
    aliases_rev.delete(prev)
  end

  # Collision checks: alias already used by another target or an existing method
  if aliases_rev.key?(alias_key) && aliases_rev[alias_key] != target_method
    raise ArgumentError, "Dynamic alias '#{alias_key}' already defined for '#{aliases_rev[alias_key]}'"
  end

  if respond_to?(alias_key, true) && aliases_rev[alias_key] != target_method
    raise ArgumentError, "Cannot define dynamic alias '#{alias_key}': method already exists"
  end

  # Define forwarding method on the singleton class
  singleton_class.send(:define_method, alias_key) do
    send(target_method)
  end

  aliases[target_method] = alias_key
  aliases_rev[alias_key] = target_method
end

#__compute_alias_name__(as_proc) ⇒ Object

Parameters:

  • as_proc (Object)

Returns:

  • (Object)


121
122
123
124
125
126
127
# File 'lib/minitwin/initialization.rb', line 121

def __compute_alias_name__(as_proc)
  instance_exec(&as_proc)
rescue StandardError
  # Expected: Dynamic alias proc may fail or return invalid names.
  # Return nil to skip this alias definition.
  nil
end

#__compute_nested_alias_name__(entry) ⇒ Object

Parameters:

  • entry (Object)

Returns:

  • (Object)


129
130
131
132
133
134
135
136
137
# File 'lib/minitwin/initialization.rb', line 129

def __compute_nested_alias_name__(entry)
  obj = public_send(entry[:group])
  obj = Minitwin::Utils.traverse_path(obj, entry[:path][0..-2])
  obj.instance_exec(&entry[:as])
rescue StandardError
  # Expected: Nested path traversal or dynamic alias proc may fail.
  # Return nil to skip this alias definition.
  nil
end

#__recompute_aliases_for_collection__(collection_method) ⇒ Object

Parameters:

  • collection_method (Object)

Returns:

  • (Object)


88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/minitwin/initialization.rb', line 88

def __recompute_aliases_for_collection__(collection_method)
  return unless self.class.respond_to?(collection_method)

  self.class.public_send(collection_method).each do |key, meta|
    as_meta = meta[:as]
    next unless as_meta.is_a?(Proc)

    alias_name = __compute_alias_name__(as_meta)
    next if alias_name.nil?

    __apply_dynamic_alias__(key, alias_name)
  end
end

#__recompute_dynamic_aliases__Object

Define or update per-instance alias methods for properties/collections where as: was provided as a Proc. The Proc is executed in the context of the instance to compute the alias name.

Returns:

  • (Object)


76
77
78
79
80
81
82
83
84
85
86
# File 'lib/minitwin/initialization.rb', line 76

def __recompute_dynamic_aliases__
  instance_variable_set(ALIASES_VAR, {}) unless instance_variable_defined?(ALIASES_VAR)
  instance_variable_set(ALIASES_REV_VAR, {}) unless instance_variable_defined?(ALIASES_REV_VAR)

  # Handle scalar properties and collections
  __recompute_aliases_for_collection__(:properties)
  __recompute_aliases_for_collection__(:collections)

  # Handle nested dynamic aliases (registered by DSL#nested)
  __recompute_nested_aliases__
end

#__recompute_nested_aliases__Object

Returns:

  • (Object)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/minitwin/initialization.rb', line 102

def __recompute_nested_aliases__
  return unless self.class.respond_to?(:dynamic_nested_aliases)

  self.class.dynamic_nested_aliases.each do |entry|
    as_meta = entry[:as]
    target = entry[:target]

    if as_meta.is_a?(Proc)
      alias_name = __compute_nested_alias_name__(entry)
      next if alias_name.nil?

      __apply_dynamic_alias__(target, alias_name)
    else
      # Static alias recorded by nested to support protected inner readers
      __apply_dynamic_alias__(target, as_meta)
    end
  end
end

#assign_attribute(method:, value:) ⇒ Object

Parameters:

  • method: (Object)
  • value: (Object)

Returns:

  • (Object)


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

def assign_attribute(method:, value:)
  if respond_to?("#{method}=")
    send("#{method}=", value)
  else
    instance_variable_set("@#{method}", value)
  end
end

#attribute_methodsObject

Returns:

  • (Object)


65
66
67
# File 'lib/minitwin/initialization.rb', line 65

def attribute_methods
  self.class.send(:allowed_attribute_keys_array)
end

#define_instance_variable(name:, value:) ⇒ Object

Parameters:

  • name: (Object)
  • value: (Object)

Returns:

  • (Object)


69
70
71
# File 'lib/minitwin/initialization.rb', line 69

def define_instance_variable(name:, value:)
  instance_variable_set(Minitwin::Utils.ivar_name(name), value)
end

#dynamic_aliasesObject

Public: expose current dynamic aliases as a Hash of alias_name => target_method

Returns:

  • (Object)


182
183
184
185
186
# File 'lib/minitwin/initialization.rb', line 182

def dynamic_aliases
  return {} unless instance_variable_defined?(ALIASES_REV_VAR) && instance_variable_get(ALIASES_REV_VAR)

  instance_variable_get(ALIASES_REV_VAR).dup
end

#initialize(**args) ⇒ instance

: (**untyped) -> instance

Parameters:

  • (Object)

Returns:

  • (instance)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/minitwin/initialization.rb', line 29

def initialize(**args)
  allowed_keys = self.class.send(:allowed_attribute_keys)

  args.select! { |arg, _| allowed_keys.include?(arg.to_sym) }

  getter_defaults = {}
  self.class.block_properties.each do |method|
    getter_defaults[method] = {} if allowed_keys.include?(method)
  end

  attrs = getter_defaults.merge(args)

  # Skip per-setter alias recomputation during bulk init; recompute once after
  @__skip_alias_recompute__ = true
  if Minitwin.send(:active_model_initialized?, self.class)
    super(attrs)
  else
    # :nocov: (exercised only without ActiveModel; covered by subprocess test)
    attrs.each { |k, v| assign_attribute(method: k, value: v) }
    # :nocov:
  end
  @__skip_alias_recompute__ = false

  __recompute_dynamic_aliases__ if self.class.dynamic_aliases?
end