Class: ObjectForge::ForgeDSL

Inherits:
UnBasicObject show all
Defined in:
lib/object_forge/forge_dsl.rb

Overview

Note:

This class is not intended to be used directly, but it’s not a private API.

DSL for defining a forge.

Since:

  • 0.1.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from UnBasicObject

#class, #eql?, #frozen?, #hash, #is_a?, #pretty_print, #pretty_print_cycle, #respond_to?, #to_s

Constructor Details

#initialize {|f| ... } ⇒ ForgeDSL

Define forge’s parameters through DSL.

If the block has a parameter, an object will be yielded, and self context will be preserved. Otherwise, DSL will change self context inside the block, without ability to call methods available outside.

Examples:

with block parameter

ForgeDSL.new do |f|
  f.attribute(:name) { "Name" }
  f[:description] { name.upcase }
  f.duration { rand(1000) }
end

without block parameter

ForgeDSL.new do
  attribute(:name) { "Name" }
  self[:description] { name.upcase }
  duration { rand(1000) }
end

Yield Parameters:

Yield Returns:

  • (void)

Since:

  • 0.1.0



52
53
54
55
56
57
58
59
60
61
# File 'lib/object_forge/forge_dsl.rb', line 52

def initialize(&dsl)
  super
  @attributes = {}
  @sequences = {}
  @traits = {}

  dsl.arity.zero? ? instance_exec(&dsl) : yield(self)

  freeze
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name) ⇒ Symbol (private)

Define an attribute using a shorthand.

Can not be used to define attributes with reserved names. Trying to use a conflicting name will lead to usual issues with calling random methods. When in doubt, use #attribute or #[] instead.

Reserved names are:

  • all names ending in ?, ! or =

  • all names starting with a non-word ASCII character (operators, , [], []=)

  • rand

Parameters:

  • name (Symbol)

    attribute name

Yield Returns:

  • (Any)

    attribute value

Returns:

  • (Symbol)

    attribute name

Raises:

  • (DSLError)

    if a reserved name is used

Since:

  • 0.1.0



247
248
249
250
251
252
# File 'lib/object_forge/forge_dsl.rb', line 247

def method_missing(name, **nil, &)
  return super if frozen?
  return attribute(name, &) if respond_to_missing?(name, false)

  raise DSLError, "#{name.inspect} is a reserved name (in #{name.inspect})"
end

Instance Attribute Details

#attributesHash{Symbol => Proc} (readonly)

Returns attribute definitions.

Returns:

  • (Hash{Symbol => Proc})

    attribute definitions

Since:

  • 0.1.0



21
22
23
# File 'lib/object_forge/forge_dsl.rb', line 21

def attributes
  @attributes
end

#sequencesHash{Symbol => Sequence} (readonly)

Returns used sequences.

Returns:

  • (Hash{Symbol => Sequence})

    used sequences

Since:

  • 0.1.0



24
25
26
# File 'lib/object_forge/forge_dsl.rb', line 24

def sequences
  @sequences
end

#traitsHash{Symbol => Hash{Symbol => Proc}} (readonly)

Returns trait definitions.

Returns:

  • (Hash{Symbol => Hash{Symbol => Proc}})

    trait definitions

Since:

  • 0.1.0



27
28
29
# File 'lib/object_forge/forge_dsl.rb', line 27

def traits
  @traits
end

Instance Method Details

#attribute(name, &definition) ⇒ Symbol Also known as: []

Define an attribute, possibly transient.

DSL does not know or care what attributes the forged class has, so the only difference between “real” and “transient” attributes is how the class itself treats them.

It is also possible to define attributes using method_missing shortcut, except for conflicting or reserved names.

You can refer to any other attribute inside the attribute definition block. self[:name] can be used to refer to an attribute with a conflicting or reserved name.

Examples:

f.attribute(:name) { "Name" }
f[:description] { name.downcase }
f.duration { rand(1000) }

using conflicting and reserved names

f.attribute(:[]) { "Brackets" }
f.attribute(:[]=) { "#{self[:[]]} are brackets" }
f.attribute(:!) { "#{self[:[]=]}!" }

Parameters:

  • name (Symbol)

    attribute name

Yield Returns:

  • (Any)

    attribute value

Returns:

  • (Symbol)

    attribute name

Raises:

  • (ArgumentError)

    if name is not a Symbol

  • (DSLError)

    if no block is given

Since:

  • 0.1.0



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/object_forge/forge_dsl.rb', line 104

def attribute(name, &definition)
  unless ::Symbol === name
    raise ::ArgumentError,
          "attribute name must be a Symbol, #{name.class} given (in #{name.inspect})"
  end
  unless block_given?
    raise DSLError, "attribute definition requires a block (in #{name.inspect})"
  end

  if @current_trait
    @traits[@current_trait][name] = definition
  else
    @attributes[name] = definition
  end

  name
end

#freezeself

Note:

Called automatically in #initialize.

Freezes the instance, including attributes, sequences and traits. Prevents further responses through #method_missing.

Returns:

  • (self)

Since:

  • 0.1.0



69
70
71
72
73
74
75
# File 'lib/object_forge/forge_dsl.rb', line 69

def freeze
  ::Object.instance_method(:freeze).bind_call(self)
  @attributes.freeze
  @sequences.freeze
  @traits.freeze
  self
end

#inspectString

Return a string containing a human-readable representation of the definition.

Returns:

  • (String)

Since:

  • 0.1.0



220
221
222
223
224
225
# File 'lib/object_forge/forge_dsl.rb', line 220

def inspect
  "#<#{self.class.name}:#{__id__} " \
    "attributes=#{@attributes.keys.inspect} " \
    "sequences=#{@sequences.keys.inspect} " \
    "traits={#{@traits.map { |k, v| "#{k.inspect}=#{v.keys.inspect}" }.join(", ")}}>"
end

#sequence(name, initial = 1) {|value| ... } ⇒ Symbol

Define an attribute, using a sequence.

name is used for both attribute and sequence, for the whole forge. If the name was used for a sequence previously, the sequence will not be redefined on subsequent calls.

Examples:

f.sequence(:date, Date.today)
f.sequence(:id) { _1.to_s }
f.sequence(:dated_id, 10) { |n| "#{Date.today}/#{n}-#{id}" }

using external sequence

seq = Sequence.new(1)
f.sequence(:global_id, seq)

sequence reuse

f.sequence(:id, "a") # => "a", "b", ...
f.trait :new_id do
  f.sequence(:id) { |n| n * 2 } # => "aa", "bb", ...
end

Parameters:

  • name (Symbol)

    attribute name

  • initial (Sequence, #succ) (defaults to: 1)

    existing sequence, or initial value for a new sequence

Yield Parameters:

  • value (#succ)

    current value of the sequence to calculate attribute value

Yield Returns:

  • (Any)

    attribute value

Returns:

  • (Symbol)

    attribute name

Raises:

  • (ArgumentError)

    if name is not a Symbol

  • (DSLError)

    if initial does not respond to #succ and is not a Sequence

Since:

  • 0.1.0



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/object_forge/forge_dsl.rb', line 151

def sequence(name, initial = 1, **nil, &)
  unless ::Symbol === name
    raise ::ArgumentError,
          "sequence name must be a Symbol, #{name.class} given (in #{name.inspect})"
  end

  seq = @sequences[name] ||= Sequence.new(initial)

  if block_given?
    attribute(name) { instance_exec(seq.next, &) }
  else
    attribute(name) { seq.next }
  end

  name
end

#trait(name) {|f| ... } ⇒ Symbol

Note:

Traits can not be defined inside of traits.

Define a trait — a group of attributes with non-default values.

DSL yields itself to the block, in case you need to refer to it. This can be used to define traits using a block coming from outside of DSL.

Examples:

f.trait :special do
  f.name { "***xXxSPECIALxXx***" }
  f.sequence(:special_id) { "~~~ SpEcIaL #{_1} ~~~" }
end

externally defined trait

# Variable defined outside of DSL:
success_trait = ->(ft) do
  ft.status { :success }
  ft.error_code { 0 }
end
# Inside the DSL:
f.trait(:success, &success_trait)

Parameters:

  • name (Symbol)

    trait name

Yields:

  • block for trait definition

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (Symbol)

    trait name

Raises:

  • (ArgumentError)

    if name is not a Symbol

  • (DSLError)

    if no block is given

  • (DSLError)

    if called inside of another trait definition

Since:

  • 0.1.0



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/object_forge/forge_dsl.rb', line 198

def trait(name, **nil)
  unless ::Symbol === name
    raise ::ArgumentError,
          "trait name must be a Symbol, #{name.class} given (in #{name.inspect})"
  end
  if @current_trait
    raise DSLError, "can not define trait inside of another trait (in #{name.inspect})"
  end
  raise DSLError, "trait definition requires a block (in #{name.inspect})" unless block_given?

  @current_trait = name
  @traits[name] = {}
  yield self
  @traits[name].freeze
  @current_trait = nil

  name
end