Class: SpecForge::Attribute

Inherits:
Object
  • Object
show all
Extended by:
Resolvable
Includes:
Resolvable
Defined in:
lib/spec_forge/attribute.rb,
lib/spec_forge/attribute/faker.rb,
lib/spec_forge/attribute/regex.rb,
lib/spec_forge/attribute/factory.rb,
lib/spec_forge/attribute/literal.rb,
lib/spec_forge/attribute/matcher.rb,
lib/spec_forge/attribute/generate.rb,
lib/spec_forge/attribute/template.rb,
lib/spec_forge/attribute/variable.rb,
lib/spec_forge/attribute/chainable.rb,
lib/spec_forge/attribute/transform.rb,
lib/spec_forge/attribute/resolvable.rb,
lib/spec_forge/attribute/environment.rb,
lib/spec_forge/attribute/parameterized.rb,
lib/spec_forge/attribute/resolvable_hash.rb,
lib/spec_forge/attribute/resolvable_array.rb,
lib/spec_forge/attribute/resolvable_struct.rb

Overview

Base class for all attribute types in SpecForge. Attributes represent values that can be transformed, resolved, or have special meaning in the context of specs and expectations.

The Attribute system handles dynamic data generation, variable references, matchers, transformations and other special values in YAML specs.

Examples:

Basic usage in YAML

username: faker.internet.username    # A dynamic faker attribute
email: /\w+@\w+\.\w+/                # A regex attribute
status: kind_of.integer              # A matcher attribute
user_id: variables.user.id           # A variable reference

Defined Under Namespace

Modules: Chainable, Resolvable Classes: Environment, Factory, Faker, Generate, Literal, Matcher, Parameterized, Regex, ResolvableArray, ResolvableHash, ResolvableStruct, Template, Transform, Variable

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Resolvable

resolve_as_matcher_proc, resolve_proc, resolved_proc

Constructor Details

#initialize(input, **options) ⇒ Attribute

Creates a new attribute

Parameters:

  • input (Object)

    The original input value

  • options (Hash)

    Additional options for attribute behavior

Options Hash (**options):

  • :context (Hash)

    Custom variable context for resolution, bypassing Forge.context lookup



132
133
134
135
# File 'lib/spec_forge/attribute.rb', line 132

def initialize(input, **options)
  @input = input
  @options = options
end

Instance Attribute Details

#inputObject (readonly)

The original input value

Returns:

  • (Object)


122
123
124
# File 'lib/spec_forge/attribute.rb', line 122

def input
  @input
end

Class Method Details

.from(value, **options) ⇒ Attribute

Creates an Attribute instance based on the input value's type and content. Recursively converts Array and Hash

Parameters:

  • value (Object)

    The input value to convert into an Attribute

  • options (Hash)

    Additional options passed to attribute constructors

Options Hash (**options):

  • :context (Hash)

    Custom variable context for resolution

Returns:

  • (Attribute)

    A new Attribute instance of the appropriate subclass



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/spec_forge/attribute.rb', line 35

def self.from(value, **options)
  case value
  when String
    from_string(value, **options)
  when Hash
    from_hash(value, **options)
  when Attribute, ResolvableArray, ResolvableHash, ResolvableStruct
    value
  when Array
    array = value.map { |v| Attribute.from(v, **options) }
    ResolvableArray.new(array)
  when Struct, Data, OpenStruct
    ResolvableStruct.new(value)
  else
    Literal.new(value, **options)
  end
end

.from_hash(hash, **options) ⇒ Attribute

Creates an Attribute instance from a hash

Parameters:

  • hash (Hash)

    The input hash

  • options (Hash)

    Additional options passed to attribute constructors

Options Hash (**options):

  • :context (Hash)

    Custom variable context for resolution

Returns:



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/spec_forge/attribute.rb', line 97

def self.from_hash(hash, **options)
  # Determine if the hash is an expanded macro call
  has_macro = ->(h, regex) { h.any? { |k, _| k.match?(regex) } }

  if has_macro.call(hash, Transform::KEYWORD_REGEX)
    Transform.from_hash(hash, **options)
  elsif has_macro.call(hash, Generate::KEYWORD_REGEX)
    Generate.from_hash(hash, **options)
  elsif has_macro.call(hash, Faker::KEYWORD_REGEX)
    Faker.from_hash(hash, **options)
  elsif has_macro.call(hash, Matcher::KEYWORD_REGEX)
    Matcher.from_hash(hash, **options)
  elsif has_macro.call(hash, Factory::KEYWORD_REGEX)
    Factory.from_hash(hash, **options)
  else
    hash = hash.transform_values { |v| Attribute.from(v, **options) }
    ResolvableHash.new(hash)
  end
end

.from_string(string, **options) ⇒ Attribute

Creates an Attribute instance from a string

Parameters:

  • string (String)

    The input string

  • options (Hash)

    Additional options passed to attribute constructors

Options Hash (**options):

  • :context (Hash)

    Custom variable context for resolution

Returns:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/spec_forge/attribute.rb', line 64

def self.from_string(string, **options)
  klass =
    case string
    when Template::REGEX
      Template
    when Environment::KEYWORD_REGEX
      Environment
    when Factory::KEYWORD_REGEX
      Factory
    when Faker::KEYWORD_REGEX
      Faker
    when Matcher::KEYWORD_REGEX
      Matcher
    when Regex::KEYWORD_REGEX
      Regex
    else
      Literal
    end

  klass.new(string, **options)
end

Instance Method Details

#==(other) ⇒ Boolean

Compares this attributes input to other

Parameters:

  • other (Object, Attribute)

    If another Attribute, the input will be compared

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
152
153
# File 'lib/spec_forge/attribute.rb', line 144

def ==(other)
  other =
    if other.is_a?(Attribute)
      other.input
    else
      other
    end

  input == other
end

#resolveObject

Performs recursive resolution of the attribute's value. Handles nested arrays and hashes by recursively resolving their elements.

Unlike #resolved, this method doesn't cache results and can be used when fresh resolution is needed each time.

Examples:

hash_attr = Attribute::ResolvableHash.new({name: Attribute::Faker.new("faker.name.name")})
hash_attr.resolve # => {name: "John Smith"}
hash_attr.resolve # => {name: "Jane Doe"} (different value on each call)

Returns:

  • (Object)

    The recursively resolved value without caching



207
208
209
210
211
212
213
214
215
216
# File 'lib/spec_forge/attribute.rb', line 207

def resolve
  case value
  when Array
    value.map(&resolve_proc)
  when Hash
    value.transform_values(&resolve_proc)
  else
    value
  end
end

#resolve_as_matcherRSpec::Matchers::BuiltIn::BaseMatcher

Converts the resolved value into an RSpec matcher

Transforms the attribute's resolved value into an appropriate RSpec matcher for use in expectations. Handles arrays, hashes, matchers, regexes, and literal values differently to produce the correct matcher type.

Returns:

  • (RSpec::Matchers::BuiltIn::BaseMatcher)

    An RSpec matcher for the value



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/spec_forge/attribute.rb', line 227

def resolve_as_matcher
  methods = Attribute::Matcher::MATCHER_METHODS

  case resolved
  when Array
    resolved_array = resolved.map(&resolve_as_matcher_proc)

    if resolved_array.size > 0
      resolved_array
    else
      methods.eq([])
    end
  when Hash
    resolved_hash = resolved.transform_values(&resolve_as_matcher_proc).stringify_keys

    if resolved_hash.size > 0
      resolved_hash
    else
      methods.eq({})
    end
  when Attribute::Matcher, Regexp
    methods.match(resolved)
  when RSpec::Matchers::BuiltIn::BaseMatcher,
      RSpec::Matchers::DSL::Matcher,
      Class
    resolved # Pass through
  else
    methods.eq(resolved)
  end
end

#resolvedObject

Returns the fully evaluated result with complete recursive resolution. Calls #value internally and then resolves all nested attributes, caching the result.

Use this when you need the final, fully-resolved value with all nested attributes fully evaluated to their primitive values.

Examples:

faker_attr = Attribute::Faker.new("faker.name.first_name")
faker_attr.resolved # => "Jane" (result is cached in @resolved)
faker_attr.resolved # => "Jane" (returns same cached value)

Returns:

  • (Object)

    The completely resolved value with cached results



189
190
191
# File 'lib/spec_forge/attribute.rb', line 189

def resolved
  @resolved ||= resolve
end

#valueObject

Returns the processed value of this attribute. Recursively calls #value on underlying attributes, but does NOT resolve all nested structures completely.

This returns an intermediate representation - for fully resolved values, use #resolve instead.

Examples:

variable_attr = Attribute::Variable.new("variables.user")
variable_attr.value # => User instance, but any attributes of User remain
as Attribute objects

Returns:

  • (Object)

    The processed value of this attribute

Raises:

  • (RuntimeError)

    if not implemented by subclass



171
172
173
# File 'lib/spec_forge/attribute.rb', line 171

def value
  raise "not implemented"
end