Class: Factrey::DSL

Inherits:
Object
  • Object
show all
Includes:
Ref::ShorthandMethods
Defined in:
lib/factrey/dsl.rb

Overview

Blueprint DSL implementation.

Constant Summary collapse

RESERVED_METHODS =

Methods reserved for DSL.

%i[
  ref ext object_node computed_node let on args
  __send__ __method__ __id__ nil? is_a? to_s inspect object_id class instance_eval instance_variables
  initialize block_given? enum_for raise
].to_set.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Ref::ShorthandMethods

#ref

Constructor Details

#initialize(blueprint:, ext:) ⇒ DSL

Returns a new instance of DSL.

Parameters:



21
22
23
24
25
# File 'lib/factrey/dsl.rb', line 21

def initialize(blueprint:, ext:)
  @blueprint = blueprint
  @ext = ext
  @ancestors = []
end

Instance Attribute Details

#extObject (readonly)

Returns the external object passed to Factrey.blueprint.

Returns:



30
31
32
# File 'lib/factrey/dsl.rb', line 30

def ext
  @ext
end

Class Method Details

.add_type(type) ⇒ Object

Add a new type that will be available in this DSL. This method defines a helper method with the same name as the type name. For example, if you have added the foo type, you can declare an object node with #foo.

add_type is called automatically when you use factory_bot-blueprint gem.

Examples:

factory = ->(type, _ctx, *args, **kwargs) { FactoryBot.create(type.name, *args, **kwargs) }
Factrey::DSL.add_type(Factrey::Blueprint::Type.new(:blog, &factory))
Factrey::DSL.add_type(Factrey::Blueprint::Type.new(:article, auto_references: :blog, &factory))

Factrey.blueprint do
  blog do
    article(title: "Article 1")
    article(title: "Article 2")
  end
end

Parameters:



151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/factrey/dsl.rb', line 151

def add_type(type)
  if RESERVED_METHODS.member? type.name
    raise ArgumentError, "Cannot use reserved method name '#{type.name}' for type name"
  end

  if types.member? type.name
    raise ArgumentError, "duplicate type definition: #{type.name}" if types[type.name] != type

    return
  end

  types[type.name] = type
  define_method(type.name) { |*args, **kwargs, &block| object_node(nil, type, *args, **kwargs, &block) }
end

.typesHash{Symbol => Type}

Returns the types defined in this DSL.

Returns:

  • (Hash{Symbol => Type})

    the types defined in this DSL



130
131
132
# File 'lib/factrey/dsl.rb', line 130

def types
  @types ||= {}
end

Instance Method Details

#args(*args, **kwargs) {|@ancestors.last.to_ref| ... } ⇒ Object

Add arguments to the current node.

Examples:

Factrey.blueprint do
  let.blog

  # The following two lines are equivalent:
  on.blog { args :premium, title: "Who-ha" }
  on.blog(:premium, title: "Who-ha")
end

Yields:

  • (@ancestors.last.to_ref)

Raises:

  • (NameError)


118
119
120
121
122
123
124
125
126
# File 'lib/factrey/dsl.rb', line 118

def args(*args, **kwargs)
  raise NameError, "cannot use args at toplevel" if @ancestors.empty?
  raise NameError, "cannot use args to computed nodes" if @ancestors.last.type == Blueprint::Type::COMPUTED

  @ancestors.last.args.concat(args)
  @ancestors.last.kwargs.update(kwargs)
  yield @ancestors.last.to_ref if block_given?
  @ancestors.last.to_ref
end

#computed_node(name, value) ⇒ Object

Add a computed node to the blueprint.

This method is usually not called directly. Use #let instead.

Parameters:

  • name (Symbol, nil)
  • value (Object)


49
50
51
52
# File 'lib/factrey/dsl.rb', line 49

def computed_node(name, value)
  node = @blueprint.add_node(Blueprint::Node.computed(name, value, ancestors: @ancestors))
  node.to_ref
end

#let(setter_name = nil, *args, **kwargs, &block) ⇒ Ref, Proxy

Define a computed node with name.

Examples:

bp =
  Factrey.blueprint do
    article(title: "Foo")                # object itself has no meaningful name (See Blueprint::Node#anonymous?)
    let.article = article(title: "Bar")  # an alias `article` to the article object is defined
    let.article(title: "Bar")            # We can omit `.node_name =` if the name is the same as the method name
    let.article2 = article(title: "Baz") # an alias `article2` to the article object is defined
  end
bp.instantiate              #=> { article: ..., article2: ..., ... }

Parameters:

  • setter_name (Symbol, nil) (defaults to: nil)

    the setter name for the computed node

Returns:

  • (Ref, Proxy)

    returns a Proxy for let.node_name = ... notation if no argument is given



66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/factrey/dsl.rb', line 66

def let(setter_name = nil, *args, **kwargs, &block)
  return Proxy.new(self, __method__) unless setter_name

  if setter_name.end_with? "="
    raise ArgumentError, "Wrong setter use" if args.size != 1 || !kwargs.empty? || block

    computed_node(setter_name[0..-2].to_sym, args[0])
  else
    # `let.node_name(...)` is a shorthand for `let.node_name = node_name(...)`
    let(:"#{setter_name}=", __send__(setter_name, *args, **kwargs, &block))
  end
end

#object_node(name, type) {|ref| ... } ⇒ Ref

Add an object node to the blueprint.

This method is usually not called directly. Use the shorthand method defined by add_type instead.

Parameters:

Yield Parameters:

Returns:



39
40
41
42
# File 'lib/factrey/dsl.rb', line 39

def object_node(name, type, ...)
  node = @blueprint.add_node(Blueprint::Node.new(name, type, ancestors: @ancestors))
  on(node.name, ...)
end

#on(node_name = nil) ⇒ Ref, Proxy

Enter the node to configure arguments and child nodes.

Examples:

Factrey.blueprint do
  let.blog do
    let.article1 = article
    let.article2 = article
  end

  # Add article to `blog`
  on.blog { let.article3 = article }
  # Add title to `article2`
  on.article2(title: "This is an article 2")
end

Parameters:

  • node_name (Symbol, nil) (defaults to: nil)

    the node name to enter

Returns:

  • (Ref, Proxy)

    returns a Proxy for on.node_name(...) notation if no argument is given

Raises:

  • (ArgumentError)


94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/factrey/dsl.rb', line 94

def on(node_name = nil, ...)
  return Proxy.new(self, __method__) unless node_name

  node = @blueprint.resolve_node(node_name)
  raise ArgumentError, "unknown node: #{node_name}" unless node

  stashed_ancestors = @ancestors
  @ancestors = node.ancestors + [node]
  begin
    args(...)
  ensure
    @ancestors = stashed_ancestors
  end
end