Class: CMDx::Attribute

Inherits:
Object
  • Object
show all
Defined in:
lib/cmdx/attribute.rb

Overview

Represents a configurable attribute within a CMDx task. Attributes define the data structure and validation rules for task parameters. They can be nested to create complex hierarchical data structures.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, options = {}) {|self| ... } ⇒ Attribute

Creates a new attribute with the specified name and configuration.

Examples:

Attribute.new(:user_id, required: true, types: [Integer, String]) do
  required :name, types: String
  optional :email, types: String
end

Parameters:

  • name (Symbol, String)

    The name of the attribute

  • options (Hash) (defaults to: {})

    Configuration options for the attribute

Options Hash (options):

  • :parent (Attribute)

    The parent attribute for nested structures

  • :required (Boolean)

    Whether the attribute is required (default: false)

  • :types (Array<Class>, Class)

    The expected type(s) for the attribute value

  • :description (String)

    The description of the attribute

  • :source (Symbol, String, Proc)

    The source of the attribute value

  • :as (Symbol, String)

    The method name to use for this attribute

  • :prefix (Symbol, String, Boolean)

    The prefix to add to the method name

  • :suffix (Symbol, String, Boolean)

    The suffix to add to the method name

  • :default (Object)

    The default value for the attribute

Yields:

  • (self)

    Block to configure nested attributes

Rbs:

  • ((Symbol | String) name, ?Hash[Symbol, untyped] options) ?{ () -> void } -> void



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/cmdx/attribute.rb', line 108

def initialize(name, options = {}, &)
  @parent = options.delete(:parent)
  @required = options.delete(:required) || false
  @types = Utils::Wrap.array(options.delete(:types) || options.delete(:type))
  @description = options.delete(:description) || options.delete(:desc)

  @name = name.to_sym
  @options = options
  @children = []

  instance_eval(&) if block_given?
end

Instance Attribute Details

#childrenArray<Attribute> (readonly)

Returns the child attributes for nested structures.

Examples:

attribute.children # => [#<Attribute @name=:street>, #<Attribute @name=:city>]

Returns:

  • (Array<Attribute>)

    Array of child attributes

Rbs:



53
54
55
# File 'lib/cmdx/attribute.rb', line 53

def children
  @children
end

#descriptionString (readonly)

Returns the description of the attribute.

Examples:

attribute.description # => "The user's name"

Returns:

  • (String)

    The description of the attribute

Rbs:

  • @description: String



83
84
85
# File 'lib/cmdx/attribute.rb', line 83

def description
  @description
end

#nameSymbol (readonly)

Returns the name of this attribute.

Examples:

attribute.name # => :user_id

Returns:

  • (Symbol)

    The attribute name

Rbs:

  • @name: Symbol



33
34
35
# File 'lib/cmdx/attribute.rb', line 33

def name
  @name
end

#optionsHash{Symbol => Object} (readonly)

Returns the configuration options for this attribute.

Examples:

attribute.options # => { required: true, default: 0 }

Returns:

  • (Hash{Symbol => Object})

    Configuration options hash

Rbs:

  • @options: Hash[Symbol, untyped]



43
44
45
# File 'lib/cmdx/attribute.rb', line 43

def options
  @options
end

#parentAttribute? (readonly)

Returns the parent attribute if this is a nested attribute.

Examples:

attribute.parent # => #<Attribute @name=:address>

Returns:

  • (Attribute, nil)

    The parent attribute, or nil if root-level

Rbs:

  • @parent: (Attribute | nil)



63
64
65
# File 'lib/cmdx/attribute.rb', line 63

def parent
  @parent
end

#taskCMDx::Task

Returns the task instance associated with this attribute.

Examples:

attribute.task.context[:user_id] # => 42

Returns:

Rbs:

  • @task: Task



23
24
25
# File 'lib/cmdx/attribute.rb', line 23

def task
  @task
end

#typesArray<Class> (readonly)

Returns the expected type(s) for this attribute’s value.

Examples:

attribute.types # => [Integer, String]

Returns:

  • (Array<Class>)

    Array of expected type classes

Rbs:



73
74
75
# File 'lib/cmdx/attribute.rb', line 73

def types
  @types
end

Class Method Details

.build(*names, **options) {|self| ... } ⇒ Array<Attribute>

Builds multiple attributes with the same configuration.

Examples:

Attribute.build(:first_name, :last_name, required: true, types: String)

Parameters:

  • names (Array<Symbol, String>)

    The names of the attributes to create

  • options (Hash)

    Configuration options for the attributes

Options Hash (**options):

  • :* (Object)

    Any attribute configuration option

Yields:

  • (self)

    Block to configure nested attributes

Returns:

  • (Array<Attribute>)

    Array of created attributes

Raises:

  • (ArgumentError)

    When no names are provided or :as is used with multiple attributes

Rbs:

  • (*untyped names, **untyped options) ?{ () -> void } -> Array



154
155
156
157
158
159
160
161
162
# File 'lib/cmdx/attribute.rb', line 154

def build(*names, **options, &)
  if names.none?
    raise ArgumentError, "no attributes given"
  elsif (names.size > 1) && options.key?(:as)
    raise ArgumentError, "the :as option only supports one attribute per definition"
  end

  names.filter_map { |name| new(name, **options, &) }
end

.optional(*names, **options) {|self| ... } ⇒ Array<Attribute>

Creates optional attributes (not required).

Examples:

Attribute.optional(:description, :tags, types: String)

Parameters:

  • names (Array<Symbol, String>)

    The names of the attributes to create

  • options (Hash)

    Configuration options for the attributes

Options Hash (**options):

  • :* (Object)

    Any attribute configuration option

Yields:

  • (self)

    Block to configure nested attributes

Returns:

  • (Array<Attribute>)

    Array of created optional attributes

Rbs:

  • (*untyped names, **untyped options) ?{ () -> void } -> Array



178
179
180
# File 'lib/cmdx/attribute.rb', line 178

def optional(*names, **options, &)
  build(*names, **options.merge(required: false), &)
end

.required(*names, **options) {|self| ... } ⇒ Array<Attribute>

Creates required attributes.

Examples:

Attribute.required(:id, :name, types: [Integer, String])

Parameters:

  • names (Array<Symbol, String>)

    The names of the attributes to create

  • options (Hash)

    Configuration options for the attributes

Options Hash (**options):

  • :* (Object)

    Any attribute configuration option

Yields:

  • (self)

    Block to configure nested attributes

Returns:

  • (Array<Attribute>)

    Array of created required attributes

Rbs:

  • (*untyped names, **untyped options) ?{ () -> void } -> Array



196
197
198
# File 'lib/cmdx/attribute.rb', line 196

def required(*names, **options, &)
  build(*names, **options.merge(required: true), &)
end

Instance Method Details

#allocation_nameSymbol?

Returns the method name for this attribute when it can be resolved statically (without a task instance). Returns nil for Proc/callable sources whose method name depends on runtime evaluation.

Examples:

attribute.allocation_name # => :user_name

Returns:

  • (Symbol, nil)

    The static method name, or nil if dynamic

Rbs:

  • () -> Symbol?



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/cmdx/attribute.rb', line 261

def allocation_name
  return @allocation_name if defined?(@allocation_name)

  @allocation_name = options[:as] || begin
    src = options[:source]
    source_name =
      if parent
        parent.allocation_name
      elsif !src.is_a?(Proc) && !src.respond_to?(:call)
        src || :context
      end

    if source_name.is_a?(Symbol)
      prefix = AFFIX.call(options[:prefix]) { "#{source_name}_" }
      suffix = AFFIX.call(options[:suffix]) { "_#{source_name}" }
      :"#{prefix}#{name}#{suffix}"
    end
  end
end

#clear_task_tree!Object

Recursively clears the task reference from this attribute and all children. Prevents the class-level attribute from retaining the last-executed task instance.

Rbs:

  • () -> void



321
322
323
324
# File 'lib/cmdx/attribute.rb', line 321

def clear_task_tree!
  @task = nil
  children.each(&:clear_task_tree!)
end

#define_and_verify_treeObject

Defines and verifies the entire attribute tree including nested children.

Rbs:

  • () -> void



308
309
310
311
312
313
314
315
# File 'lib/cmdx/attribute.rb', line 308

def define_and_verify_tree
  define_and_verify

  children.each do |child|
    child.task = task
    child.define_and_verify_tree
  end
end

#initialize_dup(source) ⇒ Object

Deep-copies children and clears task-dependent memoization so that duped attributes can be safely bound to a task without mutating the class-level originals. This makes concurrent execution thread-safe.

Parameters:

  • source (Attribute)

    The attribute being duplicated

Rbs:

  • (Attribute source) -> void



128
129
130
131
132
133
134
# File 'lib/cmdx/attribute.rb', line 128

def initialize_dup(source)
  super
  @children = source.children.map(&:dup)
  remove_instance_variable(:@source) if defined?(@source)
  remove_instance_variable(:@method_name) if defined?(@method_name)
  @task = nil
end

#method_nameSymbol

Generates the method name for accessing this attribute.

Examples:

attribute.method_name # => :user_name

Returns:

  • (Symbol)

    The method name for the attribute

Rbs:

  • () -> Symbol



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/cmdx/attribute.rb', line 289

def method_name
  return @method_name if defined?(@method_name)

  result = options[:as] || begin
    prefix = AFFIX.call(options[:prefix]) { "#{source}_" }
    suffix = AFFIX.call(options[:suffix]) { "_#{source}" }
    :"#{prefix}#{name}#{suffix}"
  end

  # Only memoize if @source is defined to avoid memoizing method
  # name when no task is present.
  return result unless defined?(@source)

  @method_name = result
end

#optional?Boolean

Checks if the attribute is optional.

Examples:

attribute.optional? # => true

Returns:

  • (Boolean)

    true if the attribute is optional, false otherwise

Rbs:

  • () -> bool



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

def optional?
  !required? || !!options[:optional]
end

#required?Boolean

Checks if the attribute is required.

Examples:

attribute.required? # => true

Returns:

  • (Boolean)

    true if the attribute is required, false otherwise

Rbs:

  • () -> bool



222
223
224
# File 'lib/cmdx/attribute.rb', line 222

def required?
  !!@required
end

#sourceSymbol

Determines the source of the attribute value. Returns :context as a safe fallback when task is not yet set (e.g., schema introspection).

Examples:

attribute.source # => :context

Returns:

  • (Symbol)

    The source identifier for the attribute value

Rbs:

  • () -> untyped



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/cmdx/attribute.rb', line 235

def source
  return @source if defined?(@source)

  parent&.method_name || begin
    value = options[:source]

    if value.is_a?(Proc)
      task ? @source = task.instance_eval(&value) : :context
    elsif value.respond_to?(:call)
      task ? @source = value.call(task) : :context
    else
      @source = value || :context
    end
  end
end

#to_hHash

}

Examples:

attribute.to_h # => {
name: :user_id,
method_name: :current_user_id,
description: "The user's name",
required: true,
types: [:integer],
options: {},
children: []

Returns:

  • (Hash)

    A hash representation of the attribute

Rbs:

  • () -> Hash[Symbol, untyped]



340
341
342
343
344
345
346
347
348
349
350
# File 'lib/cmdx/attribute.rb', line 340

def to_h
  {
    name: name,
    method_name: method_name,
    description: description,
    required: required?,
    types: types,
    options: options.except(:if, :unless),
    children: children.map(&:to_h)
  }
end