Module: Fixtury::SchemaNode

Extended by:
ActiveSupport::Concern
Included in:
Definition, Schema
Defined in:
lib/fixtury/schema_node.rb

Overview

This module is used to provide a common interface for all nodes in the schema tree. Namespaces and fixture definitions adhere to this interface and are provided with common behaviors for registration, traversal, inspection, etc

Constant Summary collapse

VALID_NODE_NAME =
/^[a-zA-Z0-9_]*$/

Instance Method Summary collapse

Instance Method Details

#acts_like_fixtury_schema_node?Boolean

Adherance to the acts_like? interface

Returns:

  • (Boolean)


52
53
54
# File 'lib/fixtury/schema_node.rb', line 52

def acts_like_fixtury_schema_node?
  true
end

#add_child(child) ⇒ Fixtury::Errors::AlreadyDefinedError

Adds child to the node’s children hash as long as another is not already defined.

Parameters:

Returns:

Raises:



61
62
63
64
65
66
67
# File 'lib/fixtury/schema_node.rb', line 61

def add_child(child)
  if children.key?(child.name) && children[child.name] != child
    raise Errors::AlreadyDefinedError, child.pathname
  end

  children[child.name] = child
end

#apply_options!(opts = {}) ⇒ void

This method returns an undefined value.

Applies options to the node and raises if a collision occurs. This is useful for reopening a node and ensuring options are not altered.

Parameters:

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

    The options to apply to the node.

Raises:



164
165
166
167
168
169
170
171
172
# File 'lib/fixtury/schema_node.rb', line 164

def apply_options!(opts = {})
  opts.each do |key, value|
    if options.key?(key) && options[key] != value
      raise Errors::OptionCollisionError.new(name, key, options[key], value)
    end

    options[key] = value
  end
end

#first_ancestor?TrueClass, FalseClass

Is the current node the first ancestor?

Returns:

  • (TrueClass, FalseClass)

    ‘true` if the node is the first ancestor, `false` otherwise.



72
73
74
# File 'lib/fixtury/schema_node.rb', line 72

def first_ancestor?
  parent.nil?
end

#get(search) ⇒ Fixtury::SchemaNode, NilClass Also known as: []

Retrieves a node in the tree relative to self. Absolute and relative searches are accepted. The potential absolute paths are determined by a Fixtury::PathResolver instance relative to this node’s pathname.

Parameters:

  • search (String)

    The search to be used for finding the node.

Returns:

Raises:

  • (ArgumentError)

    if the search is blank.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/fixtury/schema_node.rb', line 113

def get(search)
  raise ArgumentError, "`search` must be provided" if search.blank?

  resolver = Fixtury::PathResolver.new(namespace: self.pathname, search: search)
  resolver.possible_absolute_paths.each do |path|
    target = first_ancestor
    segments = path.split("/")
    segments.reject!(&:blank?)
    segments.shift if segments.first == target.name
    segments.each do |segment|
      target = target.children[segment]
      break unless target
    end

    return target if target
  end

  nil
end

#get!(search) ⇒ Object

Performs get() but raises if the result is nil. (see #get)

Raises:



98
99
100
101
102
103
# File 'lib/fixtury/schema_node.rb', line 98

def get!(search)
  thing = get(search)
  raise Errors::SchemaNodeNotDefinedError.new(pathname, search) unless thing

  thing
end

#initialize(name:, parent: nil, **options) ⇒ Fixtury::SchemaNode

Constructs a new SchemaNode object.

Parameters:

  • name (String)

    The relative name of the node.

  • parent (Object) (defaults to: nil)

    The parent node of the node.

  • options (Hash)

    Additional options for the node.

Returns:

Raises:

  • (ArgumentError)

    if the name does not match the VALID_NODE_NAME regex.



22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/fixtury/schema_node.rb', line 22

def initialize(name:, parent: nil, **options)
  name = name.to_s
  raise ArgumentError, "#{name.inspect} is an invalid node name" unless name.match?(VALID_NODE_NAME)

  @name = name
  @parent = parent
  @pathname = File.join(*[parent&.pathname, "/", @name].compact).to_s
  @children = {}
  @options = {}
  apply_options!(options)
  @first_ancestor = @parent&.first_ancestor || self
  @parent&.add_child(self)
end

#inspectString

Inspect the SchemaNode object without representing the parent or children to avoid large prints.

Returns:

  • (String)

    The inspection string.



40
41
42
# File 'lib/fixtury/schema_node.rb', line 40

def inspect
  "#{self.class}(pathname: #{pathname.inspect}, children: #{children.size})"
end

#isolation_key(default: true) ⇒ String, NilClass

Determines the isolation key in a top-down manner. It first accepts an isolation key set by the parent, then it checks for an isolation key set by the node itself. If no isolation key is found, it defaults to the node’s name unless default is set to falsy.

Parameters:

  • default (TrueClass, FalseClass, String) (defaults to: true)

    if no isolation key is present, what should the default value be? @option default [true] The default value is the node’s name. @option default [String] The default value is a custom string. @option default [false, nil, “”] No isolation key should be represented

Returns:

  • (String, NilClass)

    The isolation key.



85
86
87
88
89
90
91
92
93
# File 'lib/fixtury/schema_node.rb', line 85

def isolation_key(default: true)
  from_parent = parent&.isolation_key(default: nil)
  return from_parent if from_parent

  value = options[:isolate] || default
  value = (value == true ? pathname : value&.to_s).presence
  value = (value == "/" ? nil : value) # special case to accommodate root nodes
  value.presence
end

#schema_node_typeString

An identifier used during the printing of the tree structure.

Returns:

  • (String)

    The demodularized class name.



47
48
49
# File 'lib/fixtury/schema_node.rb', line 47

def schema_node_type
  self.class.name.demodulize.underscore
end

#structure(prefix = "") ⇒ String

Generates a string representing the structure of the schema tree. The string will be in the form of “type:name(options)”. The children will be on the next line and indented by two spaces.

Parameters:

  • prefix (String) (defaults to: "")

    The prefix to be used for any lines produced.

Returns:

  • (String)

    The structure string.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/fixtury/schema_node.rb', line 140

def structure(prefix = "")
  out = []

  opts = options.except(:isolate)
  opts.compact!

  my_structure = +"#{prefix}#{schema_node_type}:#{name}"
  iso = isolation_key(default: nil)
  my_structure << "[#{iso}]" if iso
  my_structure << "(#{opts.to_a.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")})" if opts.present?
  out << my_structure

  children.each_value do |child|
    out << child.structure("#{prefix}  ")
  end
  out.join("\n")
end