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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1b0e688509760fb4e5a6c8c9426015e5168ebcaf58dee51c1457580d62d219f
4
- data.tar.gz: 8447962706952260936adc9d386edaeeb4d85750f33266e5702fa42c87e5bfc2
3
+ metadata.gz: 3103ae36a9bc66cf15aa05d6f2731cbaa783bfb0139105a9636b865c762e427f
4
+ data.tar.gz: 51b7561b137fe05e919bf9e13cf517ad9208d5c9237573b0acb198a7dffcd169
5
5
  SHA512:
6
- metadata.gz: 7b5b370ec393ebd9a9a99dc68c4c824b8e9e4af42a120a1c686e6db3c0558a9525164c64ac93dee49c323fbebfdc5cc5c9a158d15e4fb1e58576d9c15db22c8f
7
- data.tar.gz: 447931fcff3033dfeb0b308c7012e23d85bfce06c26c1f5b429248518d9e41a339cd8e6f8e2dfbfea692c94a2e1e809ecec66ceba2bdc9e99d5ca28bc993b99f
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 { ensure_object_instantiated(_1) }
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 ensure_object_instantiated(node)
30
+ def ensure_object_created(node)
36
31
  @objects.fetch(node.name) do
37
- unless @visited.add?(node.name)
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
- resolve_auto_references(node.type.auto_references, node.ancestors, kwargs)
44
- @objects[node.name] = node.type.factory.call(node.type, @context, *args, **kwargs)
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
- ensure_object_instantiated(node)
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 "set"
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 [Array<Node>] list of ancestor nodes, from root to terminal nodes
16
- attr_reader :ancestors
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, ancestors: [], args: [], kwargs: {})
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
- unless ancestors.is_a?(Array) && ancestors.all? { _1.is_a?(Node) }
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
- @ancestors = ancestors
34
+ @parent = parent
34
35
  @args = args
35
36
  @kwargs = kwargs
36
37
  end
37
38
 
38
- # @return [Ref]
39
- def to_ref = Ref.new(name)
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 root? = ancestors.empty?
47
+ def anonymous? = name.start_with?(ANONYMOUS_NAME_PREFIX)
43
48
 
44
49
  # @return [Boolean]
45
- def anonymous? = name.start_with?(ANONYMOUS_NAME_PREFIX)
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
@@ -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
- result.add_node(
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
- ancestors: node.ancestors.map { result.nodes[_1.name] },
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
- # Define the result. This method is used by {DSL} and usually does not need to be called directly.
50
- # @param result [Object]
51
- # @param overwrite [Boolean] whether to overwrite the existing result
52
- def define_result(result, overwrite: false)
53
- return if defined?(@result) && !overwrite
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
- @result = result
56
+ ref = node&.alias_ref
57
+ ref ? resolve_node(ref.name) : node
56
58
  end
57
59
 
58
- # Create a set of objects and compute the result based on this blueprint.
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 [(Object, {Symbol => Object})] the result and the created objects
62
+ # @return [Hash{Symbol => Object}] the created objects
61
63
  def instantiate(context = nil)
62
- instantiator = Instantiator.new(context, self)
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 let let_default_name node on type args
15
- __send__ __id__ nil? object_id class instance_exec initialize block_given? raise
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
- # By preceding <code>let(name).</code> to the declaration, give a name to the node.
36
- # @param name [Symbol, nil] defaults to {Blueprint::Type#name} if omitted
37
- # @return [Let]
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 # no meaningful name is given (See Blueprint::Node#anonymous?)
42
- # let.article # named as article
43
- # let(:article2).article # named as article2
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(name = nil, &)
47
- raise TypeError, "name must be a Symbol" if name && !name.is_a?(Symbol)
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
- let = Let.new(self, name)
51
- return let unless block_given?
67
+ if setter_name.end_with? "="
68
+ raise ArgumentError, "Wrong setter use" if args.size != 1 || !kwargs.empty? || block
52
69
 
53
- @let_scope = let
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
- return self unless block_given?
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
- # @yieldparam ref [Ref]
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(:article1).article
109
- # let(:article2).article
83
+ # let.article1 = article
84
+ # let.article2 = article
110
85
  # end
111
86
  #
112
87
  # # Add article to `blog`
113
- # on.blog { let(:article3).article }
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(name = nil, *args, **kwargs)
118
- return On.new(self) if name.nil? && !block_given?
92
+ def on(node_name = nil, ...)
93
+ return Proxy.new(self, __method__) unless node_name
119
94
 
120
- node = @blueprint.nodes[name]
121
- raise ArgumentError, "unknown node: #{name}" unless 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
- args(*args, **kwargs)
126
- yield node.to_ref if block_given?
127
- @ancestors = stashed_ancestors
128
- node.to_ref
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, "Cannot use args at toplevel" if @ancestors.empty?
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
- # A helper method with the same name as the type name is also defined in the DSL. For example,
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| node(type, *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
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Factrey
4
4
  class Ref
5
- # A thin wrapper around {Proc} to represent the procedure using the results of the reference resolution.
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 {Proc}.
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
@@ -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 {Array} and {Hash}. For other structures, consider using {Defer}.
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Factrey
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
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
- blueprint.define_result dsl.new(blueprint:, ext:).instance_exec(&) if block_given?
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.3.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-07-30 00:00:00.000000000 Z
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/dsl/let.rb
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.1.0
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.11
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
@@ -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
@@ -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