factrey 0.4.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: 16dba45f9a86cdb78925559b08fe048a8cb1b8004a5206e2a103aa8df2889fe3
4
- data.tar.gz: e84a1b67bce17d637837ecc3e1882e1b7dce4b96267652cc0e23c8bc97c46441
3
+ metadata.gz: 3103ae36a9bc66cf15aa05d6f2731cbaa783bfb0139105a9636b865c762e427f
4
+ data.tar.gz: 51b7561b137fe05e919bf9e13cf517ad9208d5c9237573b0acb198a7dffcd169
5
5
  SHA512:
6
- metadata.gz: 001c21f917a7c52d76191005a127889f6ed067c0d440d4f42b6879e252224b0ffa0e585a7458ba999ec753feb7e03b4bc29c79d8f35fe0265450e56ffd48b285
7
- data.tar.gz: 45e4982f36da43ff79aa9dcb316b82c8551f30034ba223aee39a321b19f01f0deddbb7ac901e842fd60ded4bd9ac26a04e2b58097b0f94f713a5a035e16a6103
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,12 +12,14 @@ 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
 
@@ -27,16 +27,16 @@ module Factrey
27
27
 
28
28
  # @param node [Node]
29
29
  # @return [Object]
30
- def ensure_object_instantiated(node)
30
+ def ensure_object_created(node)
31
31
  @objects.fetch(node.name) do
32
- unless @visited.add?(node.name)
32
+ unless @creating_objects.add?(node.name)
33
33
  raise ArgumentError, "Circular references detected around #{node.type_annotated_name}"
34
34
  end
35
35
 
36
36
  args = resolver.resolve(node.args)
37
37
  kwargs = resolver.resolve(node.kwargs)
38
- resolve_auto_references(node.type.auto_references, node.ancestors, kwargs)
39
- @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)
40
40
  end
41
41
  end
42
42
 
@@ -44,31 +44,7 @@ module Factrey
44
44
  def resolver
45
45
  @resolver ||= Ref::Resolver.new(recursion_limit: 5) do |name|
46
46
  node = @blueprint.nodes.fetch(name) { raise ArgumentError, "Missing definition: #{name}" }
47
- ensure_object_instantiated(node)
48
- end
49
- end
50
-
51
- # @param auto_references [Hash{Symbol => Symbol}]
52
- # @param referenceable_nodes [Array<Node>]
53
- # @param dest [Hash{Symbol => Object}]
54
- def resolve_auto_references(auto_references, referenceable_nodes, dest)
55
- candidates = {}
56
- auto_references.each do |type_name, attribute|
57
- next if dest.member? attribute # this attribute is explicitly specified
58
-
59
- compatible_node, index = referenceable_nodes.reverse_each.with_index.find do |node, _|
60
- node.type.compatible_types.include?(type_name)
61
- end
62
- next unless compatible_node
63
-
64
- # the node closest to the end of the array has priority
65
- next if candidates.member?(attribute) && candidates[attribute][1] <= index
66
-
67
- candidates[attribute] = [compatible_node, index]
68
- end
69
-
70
- candidates.each do |attribute, (node, _)|
71
- dest[attribute] = ensure_object_instantiated(node)
47
+ ensure_object_created(node)
72
48
  end
73
49
  end
74
50
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
3
  require "securerandom"
5
4
 
6
5
  module Factrey
@@ -16,34 +15,32 @@ module Factrey
16
15
  attr_reader :name
17
16
  # @return [Type] type of the object
18
17
  attr_reader :type
19
- # @return [Array<Node>] list of ancestor nodes, from root to terminal nodes
20
- attr_reader :ancestors
18
+ # @return [Node, nil] parent node
19
+ attr_reader :parent
21
20
  # @return [Array<Object>] positional arguments to be passed to the factory
22
21
  attr_reader :args
23
22
  # @return [Hash{Object => Object}] keyword arguments to be passed to the factory
24
23
  attr_reader :kwargs
25
24
 
26
- def initialize(name, type, ancestors: [], args: [], kwargs: {})
25
+ def initialize(name, type, parent: nil, args: [], kwargs: {})
27
26
  raise TypeError, "name must be a Symbol" if name && !name.is_a?(Symbol)
28
27
  raise TypeError, "type must be a Blueprint::Type" unless type.is_a? Blueprint::Type
29
- unless ancestors.is_a?(Array) && ancestors.all? { _1.is_a?(Node) }
30
- raise TypeError, "ancestors must be an Array of Nodes"
31
- end
28
+ raise TypeError, "parent must be a Node" if parent && !parent.is_a?(Node)
32
29
  raise TypeError, "args must be an Array" unless args.is_a? Array
33
30
  raise TypeError, "kwargs must be a Hash" unless kwargs.is_a? Hash
34
31
 
35
32
  @name = name || :"#{ANONYMOUS_NAME_PREFIX}#{SecureRandom.hex(6)}"
36
33
  @type = type
37
- @ancestors = ancestors
34
+ @parent = parent
38
35
  @args = args
39
36
  @kwargs = kwargs
40
37
  end
41
38
 
42
39
  # @param name [Symbol, nil]
43
40
  # @param value [Object]
44
- # @param ancestors [Array<Node>]
45
- def self.computed(name, value, ancestors: [])
46
- new(name, Blueprint::Type::COMPUTED, ancestors:, args: [value])
41
+ # @param parent [Node, nil]
42
+ def self.computed(name, value, parent: nil)
43
+ new(name, Blueprint::Type::COMPUTED, parent:, args: [value])
47
44
  end
48
45
 
49
46
  # @return [Boolean]
@@ -57,7 +54,7 @@ module Factrey
57
54
 
58
55
  # @return [Ref, nil] if this node works as an alias to another node, return the reference to the node
59
56
  def alias_ref
60
- case [@type, args]
57
+ case [type, args]
61
58
  in Blueprint::Type::COMPUTED, [Ref => ref]
62
59
  ref
63
60
  else
@@ -65,6 +62,29 @@ module Factrey
65
62
  end
66
63
  end
67
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
87
+
68
88
  # Used for debugging and error reporting.
69
89
  # @return [String]
70
90
  def type_annotated_name = "#{name}(#{type.name})"
@@ -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
  # A type representation on Factrey blueprints.
@@ -14,13 +12,12 @@ module Factrey
14
12
  attr_reader :compatible_types
15
13
  # @return [Hash{Symbol => Symbol}] a name-to-attribute mapping for auto-referencing
16
14
  attr_reader :auto_references
17
- # @return [Proc] procedure that actually creates an object. See {Blueprint::Instantiator} implementation
18
- attr_reader :factory
19
15
 
20
16
  # @param name [Symbol]
21
17
  # @param compatible_types [Array<Symbol>, Symbol]
22
18
  # @param auto_references [Hash{Symbol => Symbol}, Array<Symbol>, Symbol]
23
19
  # @yield [type, context, *args, **kwargs]
20
+ # procedure that actually creates an object. See {Blueprint::Instantiator} implementation
24
21
  def initialize(name, compatible_types: [], auto_references: {}, &factory)
25
22
  compatible_types = [compatible_types] if compatible_types.is_a? Symbol
26
23
  auto_references = [auto_references] if auto_references.is_a? Symbol
@@ -43,8 +40,16 @@ module Factrey
43
40
  @factory = factory
44
41
  end
45
42
 
46
- # A special type that represents values computed from other objects.
47
- COMPUTED = Type.new(:_computed) { |_, _, arg| arg }
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 }
48
53
  end
49
54
  end
50
55
  end
@@ -25,7 +25,7 @@ module Factrey
25
25
  node.name,
26
26
  node.type,
27
27
  # This is OK since Hash insertion order in Ruby is retained
28
- ancestors: node.ancestors.map { result.nodes[_1.name] },
28
+ parent: node.parent&.then { result.nodes[_1.name] },
29
29
  args: node.args.dup,
30
30
  kwargs: node.kwargs.dup,
31
31
  )
data/lib/factrey/dsl.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module Factrey
6
4
  # {Blueprint} DSL implementation.
7
5
  class DSL
@@ -37,7 +35,7 @@ module Factrey
37
35
  # @yieldparam ref [Ref]
38
36
  # @return [Ref]
39
37
  def object_node(name, type, ...)
40
- node = @blueprint.add_node(Blueprint::Node.new(name, type, ancestors: @ancestors))
38
+ node = @blueprint.add_node(Blueprint::Node.new(name, type, parent: @ancestors.last))
41
39
  on(node.name, ...)
42
40
  end
43
41
 
@@ -47,7 +45,7 @@ module Factrey
47
45
  # @param name [Symbol, nil]
48
46
  # @param value [Object]
49
47
  def computed_node(name, value)
50
- node = @blueprint.add_node(Blueprint::Node.computed(name, value, ancestors: @ancestors))
48
+ node = @blueprint.add_node(Blueprint::Node.computed(name, value, parent: @ancestors.last))
51
49
  node.to_ref
52
50
  end
53
51
 
data/lib/factrey/proxy.rb CHANGED
@@ -15,16 +15,18 @@ module Factrey
15
15
  class Proxy < BasicObject
16
16
  # @param receiver [Object]
17
17
  # @param method [Symbol]
18
- def initialize(receiver, method)
18
+ # @param preargs [Array<Object>]
19
+ def initialize(receiver, method, *preargs)
19
20
  @receiver = receiver
20
21
  @method = method
22
+ @preargs = preargs
21
23
  end
22
24
 
23
25
  # @!visibility private
24
26
  def respond_to_missing?(_method_name, _) = true
25
27
 
26
28
  def method_missing(method_name, ...)
27
- @receiver.__send__(@method, method_name, ...)
29
+ @receiver.__send__(@method, *@preargs, method_name, ...)
28
30
  end
29
31
  end
30
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.4.0"
4
+ VERSION = "0.5.0"
5
5
  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.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-08-13 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.
@@ -50,14 +50,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 3.1.0
53
+ version: 3.2.0
54
54
  required_rubygems_version: !ruby/object:Gem::Requirement
55
55
  requirements:
56
56
  - - ">="
57
57
  - !ruby/object:Gem::Version
58
58
  version: '0'
59
59
  requirements: []
60
- rubygems_version: 3.5.11
60
+ rubygems_version: 3.5.22
61
61
  signing_key:
62
62
  specification_version: 4
63
63
  summary: Provides a declarative DSL to represent the creation plan of objects