Class: CMDx::Context

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/cmdx/context.rb

Overview

Shared data object passed through task execution. Wraps a symbol-keyed hash; supports ‘ctx.foo`/`ctx.foo = 1`/`ctx.foo?` dynamic accessors via #method_missing. Runtime freezes the root context during teardown so nested subtasks can’t mutate the outer task’s state after completion.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context = EMPTY_HASH) ⇒ Context

Returns a new instance of Context.

Parameters:

  • context (Hash, #to_h, #to_hash) (defaults to: EMPTY_HASH)

    source hash, keys are symbolized

Raises:

  • (ArgumentError)

    when ‘context` doesn’t respond to ‘#to_h`/`#to_hash`



43
44
45
46
47
48
49
50
51
52
# File 'lib/cmdx/context.rb', line 43

def initialize(context = EMPTY_HASH)
  @table =
    if context.respond_to?(:to_hash)
      context.to_hash
    elsif context.respond_to?(:to_h)
      context.to_h
    else
      raise ArgumentError, "must respond to `to_h` or `to_hash`"
    end.transform_keys(&:to_sym)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, **_kwargs) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Provides dynamic read/write/predicate access to context keys.

  • ‘ctx.name` — reads `@table`, `nil` when absent (raises `NoMethodError` when #strict? is true and the key is absent).

  • ‘ctx.name = val` — stores `val` under `:name`.

  • ‘ctx.name?` — truthy check for `@table`.

Parameters:

  • method_name (Symbol)

    dynamic reader/writer/predicate name

  • args (Array<Object>)

    stores RHS for writers (‘name=` → `[value]`)

  • _kwargs (Hash{Symbol => Object})

    ignored (accepted for Ruby keyword forwarding)

Options Hash (**_kwargs):

  • ignored (Object)

Raises:

  • (NoMethodError)

    when #strict? is true and the key is missing



283
284
285
286
287
288
289
290
291
292
293
# File 'lib/cmdx/context.rb', line 283

def method_missing(method_name, *args, **_kwargs, &)
  if method_name.end_with?("=")
    @table[method_name[..-2].to_sym] = args.first
  elsif method_name.end_with?("?")
    !!@table[method_name[..-2].to_sym]
  elsif strict? && !@table.key?(method_name)
    raise NoMethodError, "unknown context key #{method_name.inspect} (strict mode)"
  else
    @table[method_name]
  end
end

Instance Attribute Details

#strictBoolean

Enables strict mode — when true, dynamic readers via #method_missing raise ‘NoMethodError` for unknown keys instead of returning `nil`. Set by `Task#initialize` from `Task.settings.strict_context`.

Returns:

  • (Boolean)


39
40
41
# File 'lib/cmdx/context.rb', line 39

def strict
  @strict
end

Class Method Details

.build(context = EMPTY_HASH) ⇒ Context

Normalizes ‘context` into a Context instance. Passes through an unfrozen Context unchanged (so nested tasks share state); unwraps anything with `#context` (e.g. a Task); wraps hashes/hash-likes into a new Context with symbolized keys.

Parameters:

  • context (Context, #context, Hash, #to_h, #to_hash) (defaults to: EMPTY_HASH)

Returns:

Raises:

  • (ArgumentError)

    when ‘context` doesn’t respond to ‘#to_h`/`#to_hash`



22
23
24
25
26
27
28
29
30
# File 'lib/cmdx/context.rb', line 22

def build(context = EMPTY_HASH)
  if context.is_a?(self) && !context.frozen?
    context
  elsif context.respond_to?(:context)
    build(context.context)
  else
    new(context)
  end
end

Instance Method Details

#[](key) ⇒ Object?

Parameters:

  • key (Symbol, String)

Returns:

  • (Object, nil)


96
97
98
# File 'lib/cmdx/context.rb', line 96

def [](key)
  @table[key.to_sym]
end

#as_jsonHash{Symbol => Object}

JSON-friendly hash view. Aliases #to_h for conventional ‘as_json` callers (e.g. Rails); values pass through unchanged — non-primitive entries rely on their own `as_json` / `to_json`.

Returns:

  • (Hash{Symbol => Object})


215
216
217
# File 'lib/cmdx/context.rb', line 215

def as_json(*)
  to_h
end

#clearContext

Removes every entry.

Returns:



186
187
188
189
# File 'lib/cmdx/context.rb', line 186

def clear
  @table.clear
  self
end

#deconstructArray<Array(Symbol, Object)>

Pattern-matching support for ‘case context in […]`.

Returns:

  • (Array<Array(Symbol, Object)>)


244
245
246
# File 'lib/cmdx/context.rb', line 244

def deconstruct
  @table.to_a
end

#deconstruct_keys(keys) ⇒ Hash{Symbol => Object}

Pattern-matching support for ‘case context in …`.

Parameters:

  • keys (Array<Symbol>, nil)

    restrict the returned hash to these keys

Returns:

  • (Hash{Symbol => Object})


237
238
239
# File 'lib/cmdx/context.rb', line 237

def deconstruct_keys(keys)
  keys.nil? ? @table : @table.slice(*keys)
end

#deep_dupContext

Returns a deep copy. Non-mutable scalars are shared; Hashes/Arrays are recursively duplicated; other objects fall back to ‘#dup` (and then to the original on `StandardError`).

Returns:



253
254
255
256
257
# File 'lib/cmdx/context.rb', line 253

def deep_dup
  ctx = self.class.allocate
  ctx.instance_variable_set(:@table, compute_deep_dup(@table))
  ctx
end

#deep_merge(context = EMPTY_HASH) ⇒ Context

Like #merge but recursive into Hash values: a nested Hash key collision merges the two Hashes instead of replacing the left with the right. Non-Hash values follow last-write-wins (‘context` wins).

Parameters:

  • context (Context, Hash, #to_h, #to_hash) (defaults to: EMPTY_HASH)

Returns:



88
89
90
91
92
# File 'lib/cmdx/context.rb', line 88

def deep_merge(context = EMPTY_HASH)
  other = self.class.build(context)
  @table = compute_deep_merge(@table, other.to_h)
  self
end

#delete(key) {|Symbol| ... } ⇒ Object?

Returns removed value.

Parameters:

  • key (Symbol, String)

Yields:

  • (Symbol)

    optional default block, receives the symbolized key

Returns:

  • (Object, nil)

    removed value



179
180
181
# File 'lib/cmdx/context.rb', line 179

def delete(key, &)
  @table.delete(key.to_sym, &)
end

#dig(key, *keys) ⇒ Object?

Parameters:

  • key (Symbol, String)

    top-level key (symbolized)

  • keys (Array<Object>)

    nested keys passed through untouched

Returns:

  • (Object, nil)


112
113
114
# File 'lib/cmdx/context.rb', line 112

def dig(key, *keys)
  @table.dig(key.to_sym, *keys)
end

#each {|key, value| ... } ⇒ Context, Enumerator

Yields:

  • (key, value)

Returns:



160
161
162
# File 'lib/cmdx/context.rb', line 160

def each(&)
  @table.each(&)
end

#each_key {|Symbol| ... } ⇒ Context, Enumerator

Yields:

  • (Symbol)

Returns:



166
167
168
# File 'lib/cmdx/context.rb', line 166

def each_key(&)
  @table.each_key(&)
end

#each_value {|Object| ... } ⇒ Context, Enumerator

Yields:

  • (Object)

Returns:



172
173
174
# File 'lib/cmdx/context.rb', line 172

def each_value(&)
  @table.each_value(&)
end

#empty?Boolean

Returns:

  • (Boolean)


149
150
151
# File 'lib/cmdx/context.rb', line 149

def empty?
  @table.empty?
end

#eql?(other) ⇒ Boolean Also known as: ==

Equal when ‘other` is a Context with the same underlying hash.

Parameters:

  • other (Object)

Returns:

  • (Boolean)


195
196
197
# File 'lib/cmdx/context.rb', line 195

def eql?(other)
  other.is_a?(self.class) && (to_h == other.to_h)
end

#fetch(key) ⇒ Object

Hash-like fetch. Supports a default value, default block, or raises ‘KeyError` just like `Hash#fetch`.

Parameters:

  • key (Symbol, String)

Returns:

  • (Object)


105
106
107
# File 'lib/cmdx/context.rb', line 105

def fetch(key, ...)
  @table.fetch(key.to_sym, ...)
end

#freezeContext

Freezes the context and its backing hash. Runtime calls this on the root task’s context during teardown.

Returns:



263
264
265
266
# File 'lib/cmdx/context.rb', line 263

def freeze
  @table.freeze
  super
end

#hashInteger

Returns:

  • (Integer)


201
202
203
# File 'lib/cmdx/context.rb', line 201

def hash
  @table.hash
end

#key?(key) ⇒ Boolean

Parameters:

  • key (Symbol, String)

Returns:

  • (Boolean)


134
135
136
# File 'lib/cmdx/context.rb', line 134

def key?(key)
  @table.key?(key.to_sym)
end

#keysArray<Symbol>

Returns:

  • (Array<Symbol>)


139
140
141
# File 'lib/cmdx/context.rb', line 139

def keys
  @table.keys
end

#merge(context = EMPTY_HASH) ⇒ Context

Merges another context/hash-like into this one in place. Keys from ‘context` win on conflict.

Parameters:

  • context (Context, Hash, #to_h, #to_hash) (defaults to: EMPTY_HASH)

Returns:



76
77
78
79
80
# File 'lib/cmdx/context.rb', line 76

def merge(context = EMPTY_HASH)
  other = self.class.build(context)
  @table.merge!(other.to_h)
  self
end

#retrieve(key, value = nil) { ... } ⇒ Object

Fetch-or-store. Returns the existing value, or stores and returns the default (from block if given, else ‘value`).

Parameters:

  • key (Symbol, String)
  • value (Object) (defaults to: nil)

    fallback when no block is given

Yields:

  • invoked only when ‘key` is absent

Yield Returns:

  • (Object)

    value to store

Returns:

  • (Object)


124
125
126
127
128
129
130
# File 'lib/cmdx/context.rb', line 124

def retrieve(key, value = nil)
  nk = key.to_sym

  @table.fetch(nk) do
    @table[nk] = block_given? ? yield : value
  end
end

#sizeInteger

Returns:

  • (Integer)


154
155
156
# File 'lib/cmdx/context.rb', line 154

def size
  @table.size
end

#store(key, value) ⇒ Object Also known as: []=

Stores ‘value` under `key`, symbolizing the key. Overwrites any existing entry.

Parameters:

  • key (Symbol, String)
  • value (Object)

Returns:

  • (Object)

    the stored value



66
67
68
# File 'lib/cmdx/context.rb', line 66

def store(key, value)
  @table[key.to_sym] = value
end

#strict?Boolean

Returns whether dynamic reads for unknown keys raise instead of returning ‘nil`.

Returns:

  • (Boolean)

    whether dynamic reads for unknown keys raise instead of returning ‘nil`



56
57
58
# File 'lib/cmdx/context.rb', line 56

def strict?
  !!@strict
end

#to_hHash{Symbol => Object}

Returns the underlying table (not a copy).

Returns:

  • (Hash{Symbol => Object})

    the underlying table (not a copy)



206
207
208
# File 'lib/cmdx/context.rb', line 206

def to_h
  @table
end

#to_json(*args) ⇒ String

Serializes the context to a JSON string. Symbol keys are emitted as strings by the ‘json` stdlib.

Parameters:

  • args (Array)

    forwarded to ‘Hash#to_json`

Returns:

  • (String)


224
225
226
# File 'lib/cmdx/context.rb', line 224

def to_json(*args)
  to_h.to_json(*args)
end

#to_sString

Returns space-separated ‘key=value.inspect` pairs.

Returns:

  • (String)

    space-separated ‘key=value.inspect` pairs



229
230
231
# File 'lib/cmdx/context.rb', line 229

def to_s
  @table.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
end

#valuesArray<Object>

Returns:

  • (Array<Object>)


144
145
146
# File 'lib/cmdx/context.rb', line 144

def values
  @table.values
end