factrey 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/factrey/blueprint/instantiator.rb +9 -38
- data/lib/factrey/blueprint/node.rb +53 -12
- data/lib/factrey/blueprint/type.rb +12 -2
- data/lib/factrey/blueprint.rb +17 -18
- data/lib/factrey/dsl.rb +60 -81
- data/lib/factrey/proxy.rb +32 -0
- data/lib/factrey/ref/defer.rb +2 -2
- data/lib/factrey/ref/resolver.rb +2 -1
- data/lib/factrey/version.rb +1 -1
- data/lib/factrey.rb +6 -1
- metadata +5 -6
- data/lib/factrey/dsl/let.rb +0 -22
- data/lib/factrey/dsl/on.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3103ae36a9bc66cf15aa05d6f2731cbaa783bfb0139105a9636b865c762e427f
|
4
|
+
data.tar.gz: 51b7561b137fe05e919bf9e13cf517ad9208d5c9237573b0acb198a7dffcd169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44b1d62bc324437ab8a587b6906044711d7b65659b103bed1d6f789a3a5dd9cd2ca7caeb6ff69fd74e48ae0872627945276c3c58c03b28fd450b6af96d7763a0
|
7
|
+
data.tar.gz: d49aaed8a5f83620638b44ebe3963b1c68c6ef7d4effd9bfe9254e4e39de50c4859c7e33eb8be5e3a82d6b6fdbb9f04ecfa22f3fda7daeab042ed3a6e0ed5210
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "set"
|
4
|
-
|
5
3
|
module Factrey
|
6
4
|
class Blueprint
|
7
5
|
# An internal class used by {Blueprint#instantiate}.
|
@@ -14,34 +12,31 @@ module Factrey
|
|
14
12
|
def initialize(context, blueprint)
|
15
13
|
@context = context
|
16
14
|
@objects = {}
|
17
|
-
@visited = Set.new
|
18
15
|
@blueprint = blueprint
|
16
|
+
|
17
|
+
# Intermediate state
|
18
|
+
@creating_objects = Set.new
|
19
19
|
end
|
20
20
|
|
21
21
|
def instantiate_objects
|
22
|
-
@blueprint.nodes.each_value {
|
22
|
+
@blueprint.nodes.each_value { ensure_object_created(_1) }
|
23
23
|
@objects
|
24
24
|
end
|
25
25
|
|
26
|
-
def instantiate_result
|
27
|
-
instantiate_objects # To keep consistency in the order of instantiation
|
28
|
-
resolver.resolve(@blueprint.result)
|
29
|
-
end
|
30
|
-
|
31
26
|
private
|
32
27
|
|
33
28
|
# @param node [Node]
|
34
29
|
# @return [Object]
|
35
|
-
def
|
30
|
+
def ensure_object_created(node)
|
36
31
|
@objects.fetch(node.name) do
|
37
|
-
unless @
|
32
|
+
unless @creating_objects.add?(node.name)
|
38
33
|
raise ArgumentError, "Circular references detected around #{node.type_annotated_name}"
|
39
34
|
end
|
40
35
|
|
41
36
|
args = resolver.resolve(node.args)
|
42
37
|
kwargs = resolver.resolve(node.kwargs)
|
43
|
-
|
44
|
-
@objects[node.name] = node.type.
|
38
|
+
node.auto_referenced_ancestors.each { kwargs[_1] = ensure_object_created(_2) }
|
39
|
+
@objects[node.name] = node.type.create_object(@context, *args, **kwargs)
|
45
40
|
end
|
46
41
|
end
|
47
42
|
|
@@ -49,31 +44,7 @@ module Factrey
|
|
49
44
|
def resolver
|
50
45
|
@resolver ||= Ref::Resolver.new(recursion_limit: 5) do |name|
|
51
46
|
node = @blueprint.nodes.fetch(name) { raise ArgumentError, "Missing definition: #{name}" }
|
52
|
-
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# @param auto_references [Hash{Symbol => Symbol}]
|
57
|
-
# @param referenceable_nodes [Array<Node>]
|
58
|
-
# @param dest [Hash{Symbol => Object}]
|
59
|
-
def resolve_auto_references(auto_references, referenceable_nodes, dest)
|
60
|
-
candidates = {}
|
61
|
-
auto_references.each do |type_name, attribute|
|
62
|
-
next if dest.member? attribute # this attribute is explicitly specified
|
63
|
-
|
64
|
-
compatible_node, index = referenceable_nodes.reverse_each.with_index.find do |node, _|
|
65
|
-
node.type.compatible_types.include?(type_name)
|
66
|
-
end
|
67
|
-
next unless compatible_node
|
68
|
-
|
69
|
-
# the node closest to the end of the array has priority
|
70
|
-
next if candidates.member?(attribute) && candidates[attribute][1] <= index
|
71
|
-
|
72
|
-
candidates[attribute] = [compatible_node, index]
|
73
|
-
end
|
74
|
-
|
75
|
-
candidates.each do |attribute, (node, _)|
|
76
|
-
dest[attribute] = ensure_object_instantiated(node)
|
47
|
+
ensure_object_created(node)
|
77
48
|
end
|
78
49
|
end
|
79
50
|
end
|
@@ -1,48 +1,89 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "securerandom"
|
4
4
|
|
5
5
|
module Factrey
|
6
6
|
class Blueprint
|
7
7
|
# A node corresponds to an object to be created. A {Blueprint} consists of a set of nodes.
|
8
8
|
class Node
|
9
|
+
# A name prefix given to anonymous nodes for convenience.
|
9
10
|
ANONYMOUS_NAME_PREFIX = "_anon_"
|
11
|
+
# Name used for the node that hold the results of the blueprint.
|
12
|
+
RESULT_NAME = :_result_
|
10
13
|
|
11
14
|
# @return [Symbol] name given to the object to be created
|
12
15
|
attr_reader :name
|
13
16
|
# @return [Type] type of the object
|
14
17
|
attr_reader :type
|
15
|
-
# @return [
|
16
|
-
attr_reader :
|
18
|
+
# @return [Node, nil] parent node
|
19
|
+
attr_reader :parent
|
17
20
|
# @return [Array<Object>] positional arguments to be passed to the factory
|
18
21
|
attr_reader :args
|
19
22
|
# @return [Hash{Object => Object}] keyword arguments to be passed to the factory
|
20
23
|
attr_reader :kwargs
|
21
24
|
|
22
|
-
def initialize(name, type,
|
25
|
+
def initialize(name, type, parent: nil, args: [], kwargs: {})
|
23
26
|
raise TypeError, "name must be a Symbol" if name && !name.is_a?(Symbol)
|
24
27
|
raise TypeError, "type must be a Blueprint::Type" unless type.is_a? Blueprint::Type
|
25
|
-
|
26
|
-
raise TypeError, "ancestors must be an Array of Nodes"
|
27
|
-
end
|
28
|
+
raise TypeError, "parent must be a Node" if parent && !parent.is_a?(Node)
|
28
29
|
raise TypeError, "args must be an Array" unless args.is_a? Array
|
29
30
|
raise TypeError, "kwargs must be a Hash" unless kwargs.is_a? Hash
|
30
31
|
|
31
32
|
@name = name || :"#{ANONYMOUS_NAME_PREFIX}#{SecureRandom.hex(6)}"
|
32
33
|
@type = type
|
33
|
-
@
|
34
|
+
@parent = parent
|
34
35
|
@args = args
|
35
36
|
@kwargs = kwargs
|
36
37
|
end
|
37
38
|
|
38
|
-
# @
|
39
|
-
|
39
|
+
# @param name [Symbol, nil]
|
40
|
+
# @param value [Object]
|
41
|
+
# @param parent [Node, nil]
|
42
|
+
def self.computed(name, value, parent: nil)
|
43
|
+
new(name, Blueprint::Type::COMPUTED, parent:, args: [value])
|
44
|
+
end
|
40
45
|
|
41
46
|
# @return [Boolean]
|
42
|
-
def
|
47
|
+
def anonymous? = name.start_with?(ANONYMOUS_NAME_PREFIX)
|
43
48
|
|
44
49
|
# @return [Boolean]
|
45
|
-
def
|
50
|
+
def result? = name == RESULT_NAME
|
51
|
+
|
52
|
+
# @return [Ref] the reference to this node
|
53
|
+
def to_ref = Ref.new(name)
|
54
|
+
|
55
|
+
# @return [Ref, nil] if this node works as an alias to another node, return the reference to the node
|
56
|
+
def alias_ref
|
57
|
+
case [type, args]
|
58
|
+
in Blueprint::Type::COMPUTED, [Ref => ref]
|
59
|
+
ref
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Array<Node>] a list of ancestor nodes
|
66
|
+
def ancestors = parent ? [parent].concat(parent.ancestors) : []
|
67
|
+
|
68
|
+
# @return [Hash{Symbol => Node}] a map from attributes to auto-referenced ancestor nodes
|
69
|
+
def auto_referenced_ancestors
|
70
|
+
ancestors = self.ancestors
|
71
|
+
candidates = {}
|
72
|
+
type.auto_references.each do |type_name, attribute|
|
73
|
+
next if kwargs.member? attribute # this attribute is explicitly specified
|
74
|
+
|
75
|
+
# closer ancestors have higher (lower integer) priority
|
76
|
+
compatible_node, priority = ancestors.each_with_index.find do |node, _|
|
77
|
+
node.type.compatible_types.include?(type_name)
|
78
|
+
end
|
79
|
+
next unless compatible_node
|
80
|
+
next if candidates.member?(attribute) && candidates[attribute][0] <= priority
|
81
|
+
|
82
|
+
candidates[attribute] = [priority, compatible_node]
|
83
|
+
end
|
84
|
+
|
85
|
+
candidates.transform_values { _2 }
|
86
|
+
end
|
46
87
|
|
47
88
|
# Used for debugging and error reporting.
|
48
89
|
# @return [String]
|
@@ -12,13 +12,12 @@ module Factrey
|
|
12
12
|
attr_reader :compatible_types
|
13
13
|
# @return [Hash{Symbol => Symbol}] a name-to-attribute mapping for auto-referencing
|
14
14
|
attr_reader :auto_references
|
15
|
-
# @return [Proc] procedure that actually creates an object. See {Blueprint::Instantiator} implementation
|
16
|
-
attr_reader :factory
|
17
15
|
|
18
16
|
# @param name [Symbol]
|
19
17
|
# @param compatible_types [Array<Symbol>, Symbol]
|
20
18
|
# @param auto_references [Hash{Symbol => Symbol}, Array<Symbol>, Symbol]
|
21
19
|
# @yield [type, context, *args, **kwargs]
|
20
|
+
# procedure that actually creates an object. See {Blueprint::Instantiator} implementation
|
22
21
|
def initialize(name, compatible_types: [], auto_references: {}, &factory)
|
23
22
|
compatible_types = [compatible_types] if compatible_types.is_a? Symbol
|
24
23
|
auto_references = [auto_references] if auto_references.is_a? Symbol
|
@@ -40,6 +39,17 @@ module Factrey
|
|
40
39
|
@auto_references = auto_references.freeze
|
41
40
|
@factory = factory
|
42
41
|
end
|
42
|
+
|
43
|
+
# Create an object of this type.
|
44
|
+
# @param context [Object] the context object that is passed to the factory
|
45
|
+
# @param args [Array<Object>] positional arguments for the factory
|
46
|
+
# @param kwargs [Hash{Symbol => Object}] keyword arguments for the factory
|
47
|
+
def create_object(context, *, **)
|
48
|
+
@factory.call(self, context, *, **)
|
49
|
+
end
|
50
|
+
|
51
|
+
# A special type that represents values computed from other objects. See {Node.computed}.
|
52
|
+
COMPUTED = new(:_computed) { |_, _, arg| arg }
|
43
53
|
end
|
44
54
|
end
|
45
55
|
end
|
data/lib/factrey/blueprint.rb
CHANGED
@@ -10,8 +10,6 @@ module Factrey
|
|
10
10
|
class Blueprint
|
11
11
|
# @return [Hash{Symbol => Node}] a set of nodes
|
12
12
|
attr_reader :nodes
|
13
|
-
# @return [Object] the result of the DSL code is defined here
|
14
|
-
attr_reader :result
|
15
13
|
|
16
14
|
# Creates an empty blueprint.
|
17
15
|
def initialize
|
@@ -23,46 +21,47 @@ module Factrey
|
|
23
21
|
result = self.class.new
|
24
22
|
|
25
23
|
nodes.each_value do |node|
|
26
|
-
|
24
|
+
new_node = Node.new(
|
27
25
|
node.name,
|
28
26
|
node.type,
|
29
27
|
# This is OK since Hash insertion order in Ruby is retained
|
30
|
-
|
28
|
+
parent: node.parent&.then { result.nodes[_1.name] },
|
31
29
|
args: node.args.dup,
|
32
30
|
kwargs: node.kwargs.dup,
|
33
31
|
)
|
32
|
+
result.add_node(new_node)
|
34
33
|
end
|
35
34
|
|
36
35
|
result
|
37
36
|
end
|
38
37
|
|
39
38
|
# Add a node. This method is used by {DSL} and usually does not need to be called directly.
|
39
|
+
# @param node [Node]
|
40
40
|
# @return [Node]
|
41
|
-
def add_node(
|
42
|
-
node = Node.new(...)
|
41
|
+
def add_node(node)
|
43
42
|
raise ArgumentError, "duplicate node: #{node.name}" if nodes.member?(node.name)
|
44
43
|
|
45
44
|
nodes[node.name] = node
|
46
45
|
node
|
47
46
|
end
|
48
47
|
|
49
|
-
#
|
50
|
-
# @param
|
51
|
-
# @param
|
52
|
-
|
53
|
-
|
48
|
+
# Resolve a node.
|
49
|
+
# @param name [Symbol]
|
50
|
+
# @param follow_alias [Boolean] whether to follow an alias node. See {Node#alias_ref}
|
51
|
+
# @return [Node, nil]
|
52
|
+
def resolve_node(name, follow_alias: true)
|
53
|
+
node = nodes[name]
|
54
|
+
return node unless follow_alias
|
54
55
|
|
55
|
-
|
56
|
+
ref = node&.alias_ref
|
57
|
+
ref ? resolve_node(ref.name) : node
|
56
58
|
end
|
57
59
|
|
58
|
-
# Create a set of objects
|
60
|
+
# Create a set of objects based on this blueprint.
|
59
61
|
# @param context [Object] context object to be passed to the factories
|
60
|
-
# @return [
|
62
|
+
# @return [Hash{Symbol => Object}] the created objects
|
61
63
|
def instantiate(context = nil)
|
62
|
-
|
63
|
-
objects = instantiator.instantiate_objects
|
64
|
-
result = instantiator.instantiate_result
|
65
|
-
[result, objects]
|
64
|
+
Instantiator.new(context, self).instantiate_objects
|
66
65
|
end
|
67
66
|
end
|
68
67
|
end
|
data/lib/factrey/dsl.rb
CHANGED
@@ -1,18 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "set"
|
4
|
-
require "securerandom"
|
5
|
-
|
6
|
-
require_relative "dsl/let"
|
7
|
-
require_relative "dsl/on"
|
8
|
-
|
9
3
|
module Factrey
|
10
4
|
# {Blueprint} DSL implementation.
|
11
5
|
class DSL
|
12
6
|
# Methods reserved for DSL.
|
13
7
|
RESERVED_METHODS = %i[
|
14
|
-
ref ext
|
15
|
-
__send__ __id__ nil? object_id class
|
8
|
+
ref ext object_node computed_node let on args
|
9
|
+
__send__ __method__ __id__ nil? is_a? to_s inspect object_id class instance_eval instance_variables
|
10
|
+
initialize block_given? enum_for raise
|
16
11
|
].to_set.freeze
|
17
12
|
|
18
13
|
(instance_methods + private_instance_methods).each do |method|
|
@@ -32,100 +27,81 @@ module Factrey
|
|
32
27
|
# @return [Object] the external object passed to {Factrey.blueprint}
|
33
28
|
attr_reader :ext
|
34
29
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
30
|
+
# Add an object node to the blueprint.
|
31
|
+
#
|
32
|
+
# This method is usually not called directly. Use the shorthand method defined by {.add_type} instead.
|
33
|
+
# @param name [Symbol, nil]
|
34
|
+
# @param type [Blueprint::Type]
|
35
|
+
# @yieldparam ref [Ref]
|
36
|
+
# @return [Ref]
|
37
|
+
def object_node(name, type, ...)
|
38
|
+
node = @blueprint.add_node(Blueprint::Node.new(name, type, parent: @ancestors.last))
|
39
|
+
on(node.name, ...)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add a computed node to the blueprint.
|
43
|
+
#
|
44
|
+
# This method is usually not called directly. Use {#let} instead.
|
45
|
+
# @param name [Symbol, nil]
|
46
|
+
# @param value [Object]
|
47
|
+
def computed_node(name, value)
|
48
|
+
node = @blueprint.add_node(Blueprint::Node.computed(name, value, parent: @ancestors.last))
|
49
|
+
node.to_ref
|
50
|
+
end
|
51
|
+
|
52
|
+
# Define a computed node with name.
|
53
|
+
# @param setter_name [Symbol, nil] the setter name for the computed node
|
54
|
+
# @return [Ref, Proxy] returns a {Proxy} for <code>let.node_name = ...</code> notation if no argument is given
|
38
55
|
# @example
|
39
56
|
# bp =
|
40
57
|
# Factrey.blueprint do
|
41
|
-
# article
|
42
|
-
# let.article
|
43
|
-
# let(:
|
58
|
+
# article(title: "Foo") # object itself has no meaningful name (See Blueprint::Node#anonymous?)
|
59
|
+
# let.article = article(title: "Bar") # an alias `article` to the article object is defined
|
60
|
+
# let.article(title: "Bar") # We can omit `.node_name =` if the name is the same as the method name
|
61
|
+
# let.article2 = article(title: "Baz") # an alias `article2` to the article object is defined
|
44
62
|
# end
|
45
63
|
# bp.instantiate #=> { article: ..., article2: ..., ... }
|
46
|
-
def let(
|
47
|
-
|
48
|
-
raise ArgumentError, "nested let" if @let_scope
|
64
|
+
def let(setter_name = nil, *args, **kwargs, &block)
|
65
|
+
return Proxy.new(self, __method__) unless setter_name
|
49
66
|
|
50
|
-
|
51
|
-
|
67
|
+
if setter_name.end_with? "="
|
68
|
+
raise ArgumentError, "Wrong setter use" if args.size != 1 || !kwargs.empty? || block
|
52
69
|
|
53
|
-
|
54
|
-
ret = yield
|
55
|
-
@let_scope = nil
|
56
|
-
ret
|
57
|
-
end
|
58
|
-
|
59
|
-
# Overrides the default name given by {#let}.
|
60
|
-
#
|
61
|
-
# This method does nothing if it is not preceded by {#let}.
|
62
|
-
# @param name [Symbol]
|
63
|
-
# @return [Let, Blueprint]
|
64
|
-
# @example
|
65
|
-
# class Factrey::DSL do
|
66
|
-
# # Define a shortcut method for user(:admin)
|
67
|
-
# def admin_user(...) = let_default_name(:admin_user).user(:admin, ...)
|
68
|
-
# end
|
69
|
-
# Factrey.blueprint do
|
70
|
-
# admin_user # no meaningful name is given (See Blueprint::Node#anonymous?)
|
71
|
-
# let.admin_user # named as admin_user
|
72
|
-
# let(:user2).admin_user # named as user2
|
73
|
-
# end
|
74
|
-
def let_default_name(name, &)
|
75
|
-
raise TypeError, "name must be a Symbol" unless name.is_a?(Symbol)
|
76
|
-
|
77
|
-
if @let_scope && @let_scope.name.nil?
|
78
|
-
@let_scope = nil # consumed
|
79
|
-
|
80
|
-
let(name, &)
|
70
|
+
computed_node(setter_name[0..-2].to_sym, args[0])
|
81
71
|
else
|
82
|
-
|
83
|
-
|
84
|
-
yield
|
72
|
+
# `let.node_name(...)` is a shorthand for `let.node_name = node_name(...)`
|
73
|
+
let(:"#{setter_name}=", __send__(setter_name, *args, **kwargs, &block))
|
85
74
|
end
|
86
75
|
end
|
87
76
|
|
88
|
-
# Add a node to the blueprint.
|
89
|
-
#
|
90
|
-
# This method is usually not called directly. Use the shorthand method defined by {.add_type} instead.
|
91
|
-
# @param type [Blueprint::Type]
|
92
|
-
# @yieldparam ref [Ref]
|
93
|
-
# @return [Ref]
|
94
|
-
def node(type, ...)
|
95
|
-
name = @let_scope ? (@let_scope.name || type.name) : nil
|
96
|
-
@let_scope = nil # consumed
|
97
|
-
|
98
|
-
node = @blueprint.add_node(name, type, ancestors: @ancestors)
|
99
|
-
on(node.name, ...)
|
100
|
-
end
|
101
|
-
|
102
77
|
# Enter the node to configure arguments and child nodes.
|
103
|
-
# @
|
104
|
-
# @return [Ref]
|
78
|
+
# @param node_name [Symbol, nil] the node name to enter
|
79
|
+
# @return [Ref, Proxy] returns a {Proxy} for <code>on.node_name(...)</code> notation if no argument is given
|
105
80
|
# @example
|
106
81
|
# Factrey.blueprint do
|
107
82
|
# let.blog do
|
108
|
-
# let
|
109
|
-
# let
|
83
|
+
# let.article1 = article
|
84
|
+
# let.article2 = article
|
110
85
|
# end
|
111
86
|
#
|
112
87
|
# # Add article to `blog`
|
113
|
-
# on.blog { let
|
88
|
+
# on.blog { let.article3 = article }
|
114
89
|
# # Add title to `article2`
|
115
90
|
# on.article2(title: "This is an article 2")
|
116
91
|
# end
|
117
|
-
def on(
|
118
|
-
return
|
92
|
+
def on(node_name = nil, ...)
|
93
|
+
return Proxy.new(self, __method__) unless node_name
|
119
94
|
|
120
|
-
node = @blueprint.
|
121
|
-
raise ArgumentError, "unknown node: #{
|
95
|
+
node = @blueprint.resolve_node(node_name)
|
96
|
+
raise ArgumentError, "unknown node: #{node_name}" unless node
|
122
97
|
|
123
98
|
stashed_ancestors = @ancestors
|
124
99
|
@ancestors = node.ancestors + [node]
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
100
|
+
begin
|
101
|
+
args(...)
|
102
|
+
ensure
|
103
|
+
@ancestors = stashed_ancestors
|
104
|
+
end
|
129
105
|
end
|
130
106
|
|
131
107
|
# Add arguments to the current node.
|
@@ -138,10 +114,13 @@ module Factrey
|
|
138
114
|
# on.blog(:premium, title: "Who-ha")
|
139
115
|
# end
|
140
116
|
def args(*args, **kwargs)
|
141
|
-
raise NameError, "
|
117
|
+
raise NameError, "cannot use args at toplevel" if @ancestors.empty?
|
118
|
+
raise NameError, "cannot use args to computed nodes" if @ancestors.last.type == Blueprint::Type::COMPUTED
|
142
119
|
|
143
120
|
@ancestors.last.args.concat(args)
|
144
121
|
@ancestors.last.kwargs.update(kwargs)
|
122
|
+
yield @ancestors.last.to_ref if block_given?
|
123
|
+
@ancestors.last.to_ref
|
145
124
|
end
|
146
125
|
|
147
126
|
class << self
|
@@ -151,8 +130,8 @@ module Factrey
|
|
151
130
|
end
|
152
131
|
|
153
132
|
# Add a new type that will be available in this DSL.
|
154
|
-
#
|
155
|
-
# if you have added the <code>foo</code> type, you can declare node with <code>#foo</code>.
|
133
|
+
# This method defines a helper method with the same name as the type name. For example,
|
134
|
+
# if you have added the <code>foo</code> type, you can declare an object node with <code>#foo</code>.
|
156
135
|
#
|
157
136
|
# {.add_type} is called automatically when you use <code>factory_bot-blueprint</code> gem.
|
158
137
|
# @param type [Blueprint::Type] blueprint type
|
@@ -179,7 +158,7 @@ module Factrey
|
|
179
158
|
end
|
180
159
|
|
181
160
|
types[type.name] = type
|
182
|
-
define_method(type.name) { |*args, **kwargs, &block|
|
161
|
+
define_method(type.name) { |*args, **kwargs, &block| object_node(nil, type, *args, **kwargs, &block) }
|
183
162
|
end
|
184
163
|
end
|
185
164
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Factrey
|
4
|
+
# An intermediate object to provide some notation combined with <code>method_missing</code>.
|
5
|
+
# @example
|
6
|
+
# class Foo
|
7
|
+
# def foo(name = nil)
|
8
|
+
# return Factrey::Proxy.new(self, __method__) unless name
|
9
|
+
#
|
10
|
+
# name
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Foo.new.foo.bar #=> :bar
|
15
|
+
class Proxy < BasicObject
|
16
|
+
# @param receiver [Object]
|
17
|
+
# @param method [Symbol]
|
18
|
+
# @param preargs [Array<Object>]
|
19
|
+
def initialize(receiver, method, *preargs)
|
20
|
+
@receiver = receiver
|
21
|
+
@method = method
|
22
|
+
@preargs = preargs
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def respond_to_missing?(_method_name, _) = true
|
27
|
+
|
28
|
+
def method_missing(method_name, ...)
|
29
|
+
@receiver.__send__(@method, *@preargs, method_name, ...)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/factrey/ref/defer.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
module Factrey
|
4
4
|
class Ref
|
5
|
-
# A thin wrapper around
|
5
|
+
# A thin wrapper around <code>Proc</coode> to represent the procedure using the results of the reference resolution.
|
6
6
|
# Each argument name is considered as a reference.
|
7
|
-
# These references are resolved and the results are passed to the
|
7
|
+
# These references are resolved and the results are passed to the <code>Proc</code>.
|
8
8
|
#
|
9
9
|
# {Ref}s and {Defer}s are usually created through {ShorthandMethods#ref}.
|
10
10
|
class Defer
|
data/lib/factrey/ref/resolver.rb
CHANGED
@@ -13,7 +13,8 @@ module Factrey
|
|
13
13
|
|
14
14
|
# Traverse data recursively and resolve all {Ref}s and {Defer}s.
|
15
15
|
#
|
16
|
-
# This method supports recursive traversal for
|
16
|
+
# This method supports recursive traversal for <code>Array</code> and <code>Hash</code>. For other structures,
|
17
|
+
# consider using {Defer}.
|
17
18
|
# @param object [Object]
|
18
19
|
# @param recursion_count [Integer]
|
19
20
|
def resolve(object, recursion_count: 0)
|
data/lib/factrey/version.rb
CHANGED
data/lib/factrey.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "factrey/version"
|
4
4
|
require_relative "factrey/ref"
|
5
|
+
require_relative "factrey/proxy"
|
5
6
|
require_relative "factrey/blueprint"
|
6
7
|
require_relative "factrey/dsl"
|
7
8
|
|
@@ -36,8 +37,12 @@ module Factrey
|
|
36
37
|
raise TypeError, "blueprint must be a Blueprint" if blueprint && !blueprint.is_a?(Blueprint)
|
37
38
|
raise TypeError, "dsl must be a subclass of DSL" unless dsl <= DSL
|
38
39
|
|
40
|
+
is_extending = !blueprint.nil?
|
39
41
|
blueprint ||= Blueprint.new
|
40
|
-
|
42
|
+
|
43
|
+
result = block_given? ? dsl.new(blueprint:, ext:).instance_eval(&) : nil
|
44
|
+
blueprint.add_node(Blueprint::Node.computed(Blueprint::Node::RESULT_NAME, result)) unless is_extending
|
45
|
+
|
41
46
|
blueprint
|
42
47
|
end
|
43
48
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: factrey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yubrot
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
Factrey provides a declarative DSL to represent the creation plan of objects, for FactoryBot::Blueprint.
|
@@ -27,8 +27,7 @@ files:
|
|
27
27
|
- lib/factrey/blueprint/node.rb
|
28
28
|
- lib/factrey/blueprint/type.rb
|
29
29
|
- lib/factrey/dsl.rb
|
30
|
-
- lib/factrey/
|
31
|
-
- lib/factrey/dsl/on.rb
|
30
|
+
- lib/factrey/proxy.rb
|
32
31
|
- lib/factrey/ref.rb
|
33
32
|
- lib/factrey/ref/builder.rb
|
34
33
|
- lib/factrey/ref/defer.rb
|
@@ -51,14 +50,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
52
51
|
- - ">="
|
53
52
|
- !ruby/object:Gem::Version
|
54
|
-
version: 3.
|
53
|
+
version: 3.2.0
|
55
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
55
|
requirements:
|
57
56
|
- - ">="
|
58
57
|
- !ruby/object:Gem::Version
|
59
58
|
version: '0'
|
60
59
|
requirements: []
|
61
|
-
rubygems_version: 3.5.
|
60
|
+
rubygems_version: 3.5.22
|
62
61
|
signing_key:
|
63
62
|
specification_version: 4
|
64
63
|
summary: Provides a declarative DSL to represent the creation plan of objects
|
data/lib/factrey/dsl/let.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Factrey
|
4
|
-
class DSL
|
5
|
-
# An intermediate object for <code>let(:name).node(...)</code> notation. See {DSL#let}.
|
6
|
-
class Let < BasicObject
|
7
|
-
attr_reader :name
|
8
|
-
|
9
|
-
# @param dsl [DSL]
|
10
|
-
# @param name [Symbol, nil]
|
11
|
-
def initialize(dsl, name)
|
12
|
-
@dsl = dsl
|
13
|
-
@name = name
|
14
|
-
end
|
15
|
-
|
16
|
-
# @!visibility private
|
17
|
-
def respond_to_missing?(_method_name, _) = true
|
18
|
-
|
19
|
-
def method_missing(method_name, ...) = @dsl.let(@name) { @dsl.__send__(method_name, ...) }
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/factrey/dsl/on.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Factrey
|
4
|
-
class DSL
|
5
|
-
# An intermediate object for <code>on.name(...)</code> notation. See {DSL#on}.
|
6
|
-
class On < BasicObject
|
7
|
-
# @param dsl [DSL]
|
8
|
-
def initialize(dsl) = @dsl = dsl
|
9
|
-
|
10
|
-
# @!visibility private
|
11
|
-
def respond_to_missing?(_name, _) = true
|
12
|
-
|
13
|
-
def method_missing(name, ...) = @dsl.on(name, ...)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|