Class: Karafka::Core::Configurable::Node
- Inherits:
-
Object
- Object
- Karafka::Core::Configurable::Node
- Defined in:
- lib/karafka/core/configurable/node.rb
Overview
Single non-leaf node This is a core component for the configurable settings
The idea here is simple: we collect settings (leafs) and children (nodes) information and
we only compile/initialize the values prior to user running the #configure API. This API
needs to run prior to using the result stuff even if there is nothing to configure
Instance Attribute Summary collapse
-
#children ⇒ Object
We need to be able to redefine children for deep copy.
-
#nestings ⇒ Object
readonly
Returns the value of attribute nestings.
-
#node_name ⇒ Object
readonly
Returns the value of attribute node_name.
Class Method Summary collapse
-
.new ⇒ Object
Builds each node through its own anonymous subclass.
Instance Method Summary collapse
-
#compile ⇒ Object
Converts the settings definitions into end children.
-
#configure {|_self| ... } ⇒ Node
Allows for the configuration and setup of the settings.
-
#deep_dup ⇒ Node
Deep copies all the children nodes to allow us for templates building on a class level and non-side-effect usage on an instance/inherited.
-
#initialize(node_name, nestings = ->(_) {}, evaluate: true) ⇒ Node
constructor
A new instance of Node.
-
#register(name, value) ⇒ Object
Registers a key-value pair as a setting on an already-compiled node without going through the static
settingDSL. -
#setting(node_name, default: nil, constructor: nil, lazy: false, &block) ⇒ Object
Allows for a single leaf or nested node definition.
-
#to_h ⇒ Hash
Frozen config hash representation.
Constructor Details
#initialize(node_name, nestings = ->(_) {}, evaluate: true) ⇒ Node
Returns a new instance of Node.
65 66 67 68 69 70 71 72 73 |
# File 'lib/karafka/core/configurable/node.rb', line 65 def initialize(node_name, nestings = ->(_) {}, evaluate: true) @node_name = node_name @children = [] @nestings = nestings @compiled = false @configs_refs = {} @local_defs = {} instance_eval(&nestings) if evaluate end |
Instance Attribute Details
#children ⇒ Object
We need to be able to redefine children for deep copy
16 17 18 |
# File 'lib/karafka/core/configurable/node.rb', line 16 def children @children end |
#nestings ⇒ Object (readonly)
Returns the value of attribute nestings.
13 14 15 |
# File 'lib/karafka/core/configurable/node.rb', line 13 def nestings @nestings end |
#node_name ⇒ Object (readonly)
Returns the value of attribute node_name.
13 14 15 |
# File 'lib/karafka/core/configurable/node.rb', line 13 def node_name @node_name end |
Class Method Details
.new ⇒ Object
Builds each node through its own anonymous subclass. Since setting values are
mirrored into instance variables for fast access and each node layout carries a
different set of them, instantiating nodes directly from this class would grow its
object shape variations past the Ruby limit, degrading ivar access for all nodes.
A subclass per layout keeps shape variations per class minimal (late setting
calls after inheritance or runtime register calls may add a few more, staying
well under the limit). #deep_dup reuses the subclass of its template, so
duplicated configs share shapes as well.
56 57 58 |
# File 'lib/karafka/core/configurable/node.rb', line 56 def new(...) equal?(Node) ? Class.new(self).new(...) : super end |
Instance Method Details
#compile ⇒ Object
It runs once, after things are compiled, they will not be recompiled again
Converts the settings definitions into end children
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/karafka/core/configurable/node.rb', line 207 def compile @children.each do |value| # Do not redefine something that was already set during compilation # This will allow us to reconfigure things and skip override with defaults skippable = @configs_refs.key?(value.node_name) || (value.is_a?(Leaf) && value.compiled?) # A leaf is only treated as lazy (a dynamically (re)evaluated accessor) when it # actually has a constructor to evaluate. `lazy: true` without a constructor has # nothing to evaluate, so it behaves like a regular setting backed by its default. # Otherwise the lazy path builds a dynamic accessor that calls `constructor.arity` # on a `nil` constructor and crashes on first read. lazy_leaf = value.is_a?(Leaf) && value.lazy? && !value.constructor.nil? # Do not create accessor for leafs that are lazy as they will get a custom method # created instead build_accessors(value) unless lazy_leaf next if skippable initialized = if value.is_a?(Leaf) value.compiled = true if value.constructor && value.lazy? false elsif value.constructor call_constructor(value) else value.default end else value.compile value end if lazy_leaf && !initialized build_dynamic_accessor(value) else config_write(value.node_name, initialized) end end @compiled = true end |
#configure {|_self| ... } ⇒ Node
Allows for the configuration and setup of the settings
Compile settings, allow for overrides via yielding
104 105 106 107 108 |
# File 'lib/karafka/core/configurable/node.rb', line 104 def configure compile if !@compiled || node_name == :root yield(self) if block_given? self end |
#deep_dup ⇒ Node
Deep copies all the children nodes to allow us for templates building on a class level and non-side-effect usage on an instance/inherited.
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 |
# File 'lib/karafka/core/configurable/node.rb', line 141 def deep_dup # Same-layout nodes reuse the class of their template so they share object shapes dupped = self.class.new(node_name, nestings, evaluate: false) children.each do |value| dupped.children << if value.is_a?(Leaf) # After inheritance we need to reload the state so the leafs are recompiled again value = value.dup value.compiled = false # `Struct#dup` is intentionally shallow here: the leaf's `default` value is shared by # reference across the class template and every config instance produced by # `deep_dup`. This is the contract -- one uniform rule for all default types -- and it # is what lets a shared service object passed as a default (e.g. a logger) keep its # identity across all configs instead of being cloned per instance. The flip side is # that an in-place mutation of a mutable container default (e.g. `config.list << :x`) # is visible on every other instance and on the template. A caller that needs a # per-instance mutable default should not rely on a mutable `default:` (e.g. # `default: []`): assign the value inside a `configure` block, or dup it themselves, # so each instance owns its own copy. value else value.deep_dup end end dupped end |
#register(name, value) ⇒ Object
Registers a key-value pair as a setting on an already-compiled node without going
through the static setting DSL. Useful for dynamic registries (e.g. named clusters)
where the keys are not known at class-load time.
Unlike setting, which is designed to be called at class-definition time, register
is safe to call at runtime because it:
- appends a pre-compiled Leaf so `deep_dup` and `to_h` include it
- sets `@configs_refs` directly so the reader accessor returns the value immediately
- builds reader/writer accessors via the same `build_accessors` path
Raises ArgumentError if the name is already registered to prevent silent overwrites.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/karafka/core/configurable/node.rb', line 187 def register(name, value) name = name.to_sym prevent_reserved_names!(name) # Check the defined children, not just @configs_refs: a lazy setting with a constructor # is not written to @configs_refs until first read, so a `@configs_refs.key?` guard # alone would silently overwrite it instead of raising the documented error. if @children.any? { |child| child.node_name == name } raise ArgumentError, "#{name} is already registered" end leaf = Leaf.new(name, value, nil, true, false) @children << leaf build_accessors(leaf) config_write(name, value) end |
#setting(node_name, default: nil, constructor: nil, lazy: false, &block) ⇒ Object
Allows for a single leaf or nested node definition
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/karafka/core/configurable/node.rb', line 83 def setting(node_name, default: nil, constructor: nil, lazy: false, &block) # Symbolize at definition time (same as `#register`) so the config store, accessors, # `#to_h` and the compile state checks all agree on the key type also when a String # name is provided node_name = node_name.to_sym prevent_reserved_names!(node_name) @children << if block Node.new(node_name, block) else Leaf.new(node_name, default, constructor, false, lazy) end compile end |
#to_h ⇒ Hash
Returns frozen config hash representation.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/karafka/core/configurable/node.rb', line 111 def to_h config = {} @children.each do |value| config[value.node_name] = if value.is_a?(Leaf) result = if @configs_refs.key?(value.node_name) @configs_refs[value.node_name] elsif value.constructor # Use the arity-aware helper (same as `#compile`) so a `->(default) { ... }` # constructor receives its default instead of being called with no arguments, # which would raise `ArgumentError: wrong number of arguments`. call_constructor(value) elsif value.default value.default end # We need to check if value is not a result node for cases # where we merge additional config result.is_a?(Node) ? result.to_h : result else value.to_h end end config.freeze end |