Class: Builderator::Config::Attributes

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Enumerable
Defined in:
lib/builderator/config/attributes.rb

Overview

Shared Attribute Mixin

Direct Known Subclasses

Collection, Namespace, File

Defined Under Namespace

Classes: Collection, Namespace

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}, options = {}, &block) ⇒ Attributes

Returns a new instance of Attributes.



178
179
180
181
182
183
184
185
186
187
# File 'lib/builderator/config/attributes.rb', line 178

def initialize(attributes = {}, options = {}, &block)
  @attributes = Rash.coerce(attributes)
  @nodes = {}
  @block = block

  ## Track change status for consumers
  @parent = options.fetch(:parent, self)
  @extends = options[:extends]
  @dirty = false
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



173
174
175
# File 'lib/builderator/config/attributes.rb', line 173

def attributes
  @attributes
end

#extendsObject (readonly)

Returns the value of attribute extends.



176
177
178
# File 'lib/builderator/config/attributes.rb', line 176

def extends
  @extends
end

#nodesObject (readonly)

Returns the value of attribute nodes.



174
175
176
# File 'lib/builderator/config/attributes.rb', line 174

def nodes
  @nodes
end

#parentObject (readonly)

Returns the value of attribute parent.



175
176
177
# File 'lib/builderator/config/attributes.rb', line 175

def parent
  @parent
end

Class Method Details

.attribute(attribute_name, default = nil, **options) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/builderator/config/attributes.rb', line 17

def attribute(attribute_name, default = nil, **options)
  ##
  # Helpers for Array-type attributes
  ##
  if options[:type] == :list
    define_method(attribute_name) do |*arg, **run_options|
      ## Instantiate List if it doesn't exist yet. `||=` will always return a new Rash.
      @attributes[attribute_name] = Config::List.new(run_options) unless @attributes.has?(attribute_name, Config::List)

      @attributes[attribute_name].set(*arg.flatten) unless arg.empty?
      @attributes[attribute_name]
    end

    define_method(options[:singular]) do |*arg, **run_options|
      send(attribute_name, run_options).push(*arg.flatten)
    end if options.include?(:singular)

    return
  end

  ##
  # Helpers for Hash-type attributes
  ##
  if options[:type] == :hash
    define_method(attribute_name) do |arg = nil|
      ## Instantiate List if it doesn't exist yet. `||=` will always return a new Rash.
      @attributes[attribute_name] = Config::Rash.new unless @attributes.has?(attribute_name, Config::Rash)

      dirty(@attributes[attribute_name].merge!(Config::Rash.coerce(arg)).any?) unless arg.nil?
      @attributes[attribute_name]
    end

    return
  end

  ## Getter/Setter
  define_method(attribute_name) do |*arg|
    set_or_return(attribute_name, arg.first, default, options)
  end

  ## Setter
  define_method("#{attribute_name}=") do |arg|
    set_if_valid(attribute_name, arg, options)
  end
end

.collection(collection_name, &definition) ⇒ Object

A Collection is a named-set of items in a sub-node of the attribute-set.

Like Namespaces, Collections map to a top-level key, but they also have multiple second-order keys:

e.g. ‘collection :vagrant …` adds a DSL method `vagrant(name = :default, &block)` which maps to `attributes[<name>]`

Multiple entities can be added to the collection by calling the DSL method with unique ‘name` arguments. Multiple calls to the DSL method with the same name argument will update the existing entity in place

An entry can be defined as an extension of another node by passing a hash as the instance name: ‘name => Config.node(:name)`. This will use the values defined in `Config.node(:name)` as defaults for the new entry



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/builderator/config/attributes.rb', line 108

def collection(collection_name, &definition)
  collection_class = Collection.create(collection_name, &definition)

  define_method(collection_name) do |instance_name = nil, &block|
    extension_base = nil

    ## Allow extension to be defined as a key-value
    if instance_name.is_a?(Hash)
      extension_base = instance_name.first.last
      instance_name = instance_name.first.first
    end

    nodes[collection_name] ||= collection_class.new(
      @attributes[collection_name],
      :parent => self)

    return nodes[collection_name] if instance_name.nil?
    nodes[collection_name].fetch(instance_name, :extends => extension_base, &block)
  end
end

.namespace(namespace_name, &definition) ⇒ Object

A Namespace is a singleton sub-node of the attribute-set

e.g. ‘namespace :chef …` maps to `attributes` and adds a method `chef(&block)` to the DSL which is used as follows:

“‘ chef do

run_list 'foo', 'bar'
...

end “‘

Multiple calls to the DSL method are safe and will update the same sub-node.



79
80
81
82
83
84
85
86
87
88
# File 'lib/builderator/config/attributes.rb', line 79

def namespace(namespace_name, &definition)
  namespace_class = Namespace.create(namespace_name, &definition)

  define_method(namespace_name) do |&block|
    nodes[namespace_name] ||= namespace_class.new(
      @attributes[namespace_name],
      :name => namespace_name,
      :parent => self, &block)
  end
end

Instance Method Details

#==(other) ⇒ Object



169
170
171
# File 'lib/builderator/config/attributes.rb', line 169

def ==(other)
  attributes == other.attributes
end

#cleanObject

Clear dirty state flag



190
191
192
# File 'lib/builderator/config/attributes.rb', line 190

def clean
  @dirty = false
end

#compile(evaluate = true) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/builderator/config/attributes.rb', line 200

def compile(evaluate = true)
  ## Underlay base values if present
  if extends.is_a?(Attributes)
    previous_state = attributes
    dirty_state = dirty

    attributes.merge!(extends.attributes)

    @block.call(self) if @block && evaluate
    nodes.each { |_, node| node.compile }

    root.dirty!(dirty_state || previous_state.diff(attributes).any?)

    return self
  end

  ## Compile this node and its children
  @block.call(self) if @block && evaluate
  nodes.each { |_, node| node.compile }

  self
end

#dirty(update = false) ⇒ Object

All dirty state should aggregate at the root node



160
161
162
163
# File 'lib/builderator/config/attributes.rb', line 160

def dirty(update = false)
  return @dirty ||= update if root?
  root.dirty(update)
end

#dirty!(set) ⇒ Object



165
166
167
# File 'lib/builderator/config/attributes.rb', line 165

def dirty!(set)
  @dirty = set
end

#merge(other) ⇒ Object



223
224
225
226
# File 'lib/builderator/config/attributes.rb', line 223

def merge(other)
  dirty(attributes.merge!(other.attributes).any?)
  self
end

#reset!Object



194
195
196
197
198
# File 'lib/builderator/config/attributes.rb', line 194

def reset!
  @attributes = Config::Rash.new
  @nodes = {}
  @dirty = false
end

#rootObject

Get the root Attributes object



149
150
151
152
153
# File 'lib/builderator/config/attributes.rb', line 149

def root
  return self if root?

  parent.root
end

#root?Boolean

Returns:

  • (Boolean)


155
156
157
# File 'lib/builderator/config/attributes.rb', line 155

def root?
  parent == self
end

#sealObject



138
139
140
141
# File 'lib/builderator/config/attributes.rb', line 138

def seal
  attributes.seal
  self
end

#to_json(*_) ⇒ Object



228
229
230
# File 'lib/builderator/config/attributes.rb', line 228

def to_json(*_)
  JSON.pretty_generate(to_hash)
end

#unsealObject



143
144
145
146
# File 'lib/builderator/config/attributes.rb', line 143

def unseal
  attributes.unseal
  self
end