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
53
54
55
# 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, <<~MSG.chomp
        Context.build expected a Hash or an object responding to #to_h/#to_hash (got #{context.class}).
        See https://drexed.github.io/cmdx/basics/context/#assigning-data
      MSG
    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)



295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/cmdx/context.rb', line 295

def method_missing(method_name, *args, **_kwargs, &)
  if @table.key?(method_name)
    @table[method_name]
  elsif 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?
    raise UnknownAccessorError, <<~MSG.chomp
      unknown context key #{method_name.inspect} (strict mode); declared keys: #{@table.keys.inspect}.
      See https://drexed.github.io/cmdx/basics/context/#strict-mode
    MSG
  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)


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

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})


232
233
234
# File 'lib/cmdx/context.rb', line 232

def as_json(*)
  to_h
end

#clearContext

Removes every entry.

Returns:



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

def clear
  @table.clear
  self
end

#deconstructArray<Array(Symbol, Object)>

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

Returns:

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


268
269
270
# File 'lib/cmdx/context.rb', line 268

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})


261
262
263
# File 'lib/cmdx/context.rb', line 261

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`). `@strict` is preserved on the copy.

Returns:



277
278
279
280
281
282
# File 'lib/cmdx/context.rb', line 277

def deep_dup
  ctx = self.class.allocate
  ctx.instance_variable_set(:@table, Util.deep_dup(@table))
  ctx.instance_variable_set(:@strict, @strict)
  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:



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

def deep_merge(context = EMPTY_HASH)
  other = self.class.build(context)
  @table = Util.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



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

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)


127
128
129
# File 'lib/cmdx/context.rb', line 127

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

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

Yields:

  • (key, value)

Returns:



175
176
177
# File 'lib/cmdx/context.rb', line 175

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

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

Yields:

  • (Symbol)

Returns:



181
182
183
# File 'lib/cmdx/context.rb', line 181

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

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

Yields:

  • (Object)

Returns:



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

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

#empty?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/cmdx/context.rb', line 164

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)


210
211
212
# File 'lib/cmdx/context.rb', line 210

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)


120
121
122
# File 'lib/cmdx/context.rb', line 120

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:



288
289
290
291
# File 'lib/cmdx/context.rb', line 288

def freeze
  @table.freeze
  super
end

#hashInteger

Returns:

  • (Integer)


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

def hash
  @table.hash
end

#initialize_copy(source) ⇒ void

This method returns an undefined value.

Ensures ‘#dup` / `#clone` produce a context with an independent backing hash so mutations on the copy do not leak into the original. `@strict` is preserved on the copy.

Parameters:



63
64
65
66
67
# File 'lib/cmdx/context.rb', line 63

def initialize_copy(source)
  super
  @table  = source.instance_variable_get(:@table).dup
  @strict = source.strict?
end

#key?(key) ⇒ Boolean

Parameters:

  • key (Symbol, String)

Returns:

  • (Boolean)


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

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

#keysArray<Symbol>

Returns:

  • (Array<Symbol>)


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

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:



91
92
93
94
95
# File 'lib/cmdx/context.rb', line 91

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)


139
140
141
142
143
144
145
# File 'lib/cmdx/context.rb', line 139

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

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

#sizeInteger

Returns:

  • (Integer)


169
170
171
# File 'lib/cmdx/context.rb', line 169

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



81
82
83
# File 'lib/cmdx/context.rb', line 81

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`



71
72
73
# File 'lib/cmdx/context.rb', line 71

def strict?
  !!@strict
end

#to_hHash{Symbol => Object}

Returns a shallow copy of the underlying table. Frozen contexts return the frozen table directly to preserve ‘Hash#frozen?` semantics for serialization callers.

Returns:

  • (Hash{Symbol => Object})

    a shallow copy of the underlying table. Frozen contexts return the frozen table directly to preserve ‘Hash#frozen?` semantics for serialization callers.



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

def to_h
  @table.frozen? ? @table : @table.dup
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)


241
242
243
# File 'lib/cmdx/context.rb', line 241

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



246
247
248
249
250
251
252
253
254
255
# File 'lib/cmdx/context.rb', line 246

def to_s
  buf = String.new(capacity: 256)

  @table.each do |k, v|
    buf << " " unless buf.empty?
    buf << k.to_s << "=" << v.inspect
  end

  buf
end

#valuesArray<Object>

Returns:

  • (Array<Object>)


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

def values
  @table.values
end