factrey 0.2.0 → 0.4.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 +0 -5
- data/lib/factrey/blueprint/node.rb +25 -4
- data/lib/factrey/blueprint/type.rb +5 -0
- data/lib/factrey/blueprint.rb +16 -17
- data/lib/factrey/dsl.rb +60 -79
- data/lib/factrey/proxy.rb +30 -0
- data/lib/factrey/version.rb +1 -1
- data/lib/factrey.rb +6 -1
- metadata +3 -4
- 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: 16dba45f9a86cdb78925559b08fe048a8cb1b8004a5206e2a103aa8df2889fe3
|
4
|
+
data.tar.gz: e84a1b67bce17d637837ecc3e1882e1b7dce4b96267652cc0e23c8bc97c46441
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 001c21f917a7c52d76191005a127889f6ed067c0d440d4f42b6879e252224b0ffa0e585a7458ba999ec753feb7e03b4bc29c79d8f35fe0265450e56ffd48b285
|
7
|
+
data.tar.gz: 45e4982f36da43ff79aa9dcb316b82c8551f30034ba223aee39a321b19f01f0deddbb7ac901e842fd60ded4bd9ac26a04e2b58097b0f94f713a5a035e16a6103
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "set"
|
4
|
+
require "securerandom"
|
4
5
|
|
5
6
|
module Factrey
|
6
7
|
class Blueprint
|
7
8
|
# A node corresponds to an object to be created. A {Blueprint} consists of a set of nodes.
|
8
9
|
class Node
|
10
|
+
# A name prefix given to anonymous nodes for convenience.
|
9
11
|
ANONYMOUS_NAME_PREFIX = "_anon_"
|
12
|
+
# Name used for the node that hold the results of the blueprint.
|
13
|
+
RESULT_NAME = :_result_
|
10
14
|
|
11
15
|
# @return [Symbol] name given to the object to be created
|
12
16
|
attr_reader :name
|
@@ -35,14 +39,31 @@ module Factrey
|
|
35
39
|
@kwargs = kwargs
|
36
40
|
end
|
37
41
|
|
38
|
-
# @
|
39
|
-
|
42
|
+
# @param name [Symbol, nil]
|
43
|
+
# @param value [Object]
|
44
|
+
# @param ancestors [Array<Node>]
|
45
|
+
def self.computed(name, value, ancestors: [])
|
46
|
+
new(name, Blueprint::Type::COMPUTED, ancestors:, args: [value])
|
47
|
+
end
|
40
48
|
|
41
49
|
# @return [Boolean]
|
42
|
-
def
|
50
|
+
def anonymous? = name.start_with?(ANONYMOUS_NAME_PREFIX)
|
43
51
|
|
44
52
|
# @return [Boolean]
|
45
|
-
def
|
53
|
+
def result? = name == RESULT_NAME
|
54
|
+
|
55
|
+
# @return [Ref] the reference to this node
|
56
|
+
def to_ref = Ref.new(name)
|
57
|
+
|
58
|
+
# @return [Ref, nil] if this node works as an alias to another node, return the reference to the node
|
59
|
+
def alias_ref
|
60
|
+
case [@type, args]
|
61
|
+
in Blueprint::Type::COMPUTED, [Ref => ref]
|
62
|
+
ref
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
46
67
|
|
47
68
|
# Used for debugging and error reporting.
|
48
69
|
# @return [String]
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "set"
|
4
|
+
|
3
5
|
module Factrey
|
4
6
|
class Blueprint
|
5
7
|
# A type representation on Factrey blueprints.
|
@@ -40,6 +42,9 @@ module Factrey
|
|
40
42
|
@auto_references = auto_references.freeze
|
41
43
|
@factory = factory
|
42
44
|
end
|
45
|
+
|
46
|
+
# A special type that represents values computed from other objects.
|
47
|
+
COMPUTED = Type.new(:_computed) { |_, _, arg| arg }
|
43
48
|
end
|
44
49
|
end
|
45
50
|
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,7 +21,7 @@ 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
|
@@ -31,38 +29,39 @@ module Factrey
|
|
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,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "set"
|
4
|
-
require "securerandom"
|
5
|
-
|
6
|
-
require_relative "dsl/let"
|
7
|
-
require_relative "dsl/on"
|
8
4
|
|
9
5
|
module Factrey
|
10
6
|
# {Blueprint} DSL implementation.
|
11
7
|
class DSL
|
12
8
|
# Methods reserved for DSL.
|
13
9
|
RESERVED_METHODS = %i[
|
14
|
-
ref ext
|
15
|
-
__send__ __id__ nil? object_id class
|
10
|
+
ref ext object_node computed_node let on args
|
11
|
+
__send__ __method__ __id__ nil? is_a? to_s inspect object_id class instance_eval instance_variables
|
12
|
+
initialize block_given? enum_for raise
|
16
13
|
].to_set.freeze
|
17
14
|
|
18
15
|
(instance_methods + private_instance_methods).each do |method|
|
@@ -32,100 +29,81 @@ module Factrey
|
|
32
29
|
# @return [Object] the external object passed to {Factrey.blueprint}
|
33
30
|
attr_reader :ext
|
34
31
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
32
|
+
# Add an object node to the blueprint.
|
33
|
+
#
|
34
|
+
# This method is usually not called directly. Use the shorthand method defined by {.add_type} instead.
|
35
|
+
# @param name [Symbol, nil]
|
36
|
+
# @param type [Blueprint::Type]
|
37
|
+
# @yieldparam ref [Ref]
|
38
|
+
# @return [Ref]
|
39
|
+
def object_node(name, type, ...)
|
40
|
+
node = @blueprint.add_node(Blueprint::Node.new(name, type, ancestors: @ancestors))
|
41
|
+
on(node.name, ...)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add a computed node to the blueprint.
|
45
|
+
#
|
46
|
+
# This method is usually not called directly. Use {#let} instead.
|
47
|
+
# @param name [Symbol, nil]
|
48
|
+
# @param value [Object]
|
49
|
+
def computed_node(name, value)
|
50
|
+
node = @blueprint.add_node(Blueprint::Node.computed(name, value, ancestors: @ancestors))
|
51
|
+
node.to_ref
|
52
|
+
end
|
53
|
+
|
54
|
+
# Define a computed node with name.
|
55
|
+
# @param setter_name [Symbol, nil] the setter name for the computed node
|
56
|
+
# @return [Ref, Proxy] returns a {Proxy} for <code>let.node_name = ...</code> notation if no argument is given
|
38
57
|
# @example
|
39
58
|
# bp =
|
40
59
|
# Factrey.blueprint do
|
41
|
-
# article
|
42
|
-
# let.article
|
43
|
-
# let(:
|
60
|
+
# article(title: "Foo") # object itself has no meaningful name (See Blueprint::Node#anonymous?)
|
61
|
+
# let.article = article(title: "Bar") # an alias `article` to the article object is defined
|
62
|
+
# let.article(title: "Bar") # We can omit `.node_name =` if the name is the same as the method name
|
63
|
+
# let.article2 = article(title: "Baz") # an alias `article2` to the article object is defined
|
44
64
|
# end
|
45
65
|
# bp.instantiate #=> { article: ..., article2: ..., ... }
|
46
|
-
def let(
|
47
|
-
|
48
|
-
raise ArgumentError, "nested let" if @let_scope
|
49
|
-
|
50
|
-
let = Let.new(self, name)
|
51
|
-
return let unless block_given?
|
52
|
-
|
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)
|
66
|
+
def let(setter_name = nil, *args, **kwargs, &block)
|
67
|
+
return Proxy.new(self, __method__) unless setter_name
|
76
68
|
|
77
|
-
if
|
78
|
-
|
69
|
+
if setter_name.end_with? "="
|
70
|
+
raise ArgumentError, "Wrong setter use" if args.size != 1 || !kwargs.empty? || block
|
79
71
|
|
80
|
-
|
72
|
+
computed_node(setter_name[0..-2].to_sym, args[0])
|
81
73
|
else
|
82
|
-
|
83
|
-
|
84
|
-
yield
|
74
|
+
# `let.node_name(...)` is a shorthand for `let.node_name = node_name(...)`
|
75
|
+
let(:"#{setter_name}=", __send__(setter_name, *args, **kwargs, &block))
|
85
76
|
end
|
86
77
|
end
|
87
78
|
|
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
79
|
# Enter the node to configure arguments and child nodes.
|
103
|
-
# @
|
104
|
-
# @return [Ref]
|
80
|
+
# @param node_name [Symbol, nil] the node name to enter
|
81
|
+
# @return [Ref, Proxy] returns a {Proxy} for <code>on.node_name(...)</code> notation if no argument is given
|
105
82
|
# @example
|
106
83
|
# Factrey.blueprint do
|
107
84
|
# let.blog do
|
108
|
-
# let
|
109
|
-
# let
|
85
|
+
# let.article1 = article
|
86
|
+
# let.article2 = article
|
110
87
|
# end
|
111
88
|
#
|
112
89
|
# # Add article to `blog`
|
113
|
-
# on.blog { let
|
90
|
+
# on.blog { let.article3 = article }
|
114
91
|
# # Add title to `article2`
|
115
92
|
# on.article2(title: "This is an article 2")
|
116
93
|
# end
|
117
|
-
def on(
|
118
|
-
return
|
94
|
+
def on(node_name = nil, ...)
|
95
|
+
return Proxy.new(self, __method__) unless node_name
|
119
96
|
|
120
|
-
node = @blueprint.
|
121
|
-
raise ArgumentError, "unknown node: #{
|
97
|
+
node = @blueprint.resolve_node(node_name)
|
98
|
+
raise ArgumentError, "unknown node: #{node_name}" unless node
|
122
99
|
|
123
100
|
stashed_ancestors = @ancestors
|
124
101
|
@ancestors = node.ancestors + [node]
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
102
|
+
begin
|
103
|
+
args(...)
|
104
|
+
ensure
|
105
|
+
@ancestors = stashed_ancestors
|
106
|
+
end
|
129
107
|
end
|
130
108
|
|
131
109
|
# Add arguments to the current node.
|
@@ -138,10 +116,13 @@ module Factrey
|
|
138
116
|
# on.blog(:premium, title: "Who-ha")
|
139
117
|
# end
|
140
118
|
def args(*args, **kwargs)
|
141
|
-
raise NameError, "
|
119
|
+
raise NameError, "cannot use args at toplevel" if @ancestors.empty?
|
120
|
+
raise NameError, "cannot use args to computed nodes" if @ancestors.last.type == Blueprint::Type::COMPUTED
|
142
121
|
|
143
122
|
@ancestors.last.args.concat(args)
|
144
123
|
@ancestors.last.kwargs.update(kwargs)
|
124
|
+
yield @ancestors.last.to_ref if block_given?
|
125
|
+
@ancestors.last.to_ref
|
145
126
|
end
|
146
127
|
|
147
128
|
class << self
|
@@ -151,8 +132,8 @@ module Factrey
|
|
151
132
|
end
|
152
133
|
|
153
134
|
# 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>.
|
135
|
+
# This method defines a helper method with the same name as the type name. For example,
|
136
|
+
# if you have added the <code>foo</code> type, you can declare an object node with <code>#foo</code>.
|
156
137
|
#
|
157
138
|
# {.add_type} is called automatically when you use <code>factory_bot-blueprint</code> gem.
|
158
139
|
# @param type [Blueprint::Type] blueprint type
|
@@ -179,7 +160,7 @@ module Factrey
|
|
179
160
|
end
|
180
161
|
|
181
162
|
types[type.name] = type
|
182
|
-
define_method(type.name) { |*args, **kwargs, &block|
|
163
|
+
define_method(type.name) { |*args, **kwargs, &block| object_node(nil, type, *args, **kwargs, &block) }
|
183
164
|
end
|
184
165
|
end
|
185
166
|
end
|
@@ -0,0 +1,30 @@
|
|
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
|
+
def initialize(receiver, method)
|
19
|
+
@receiver = receiver
|
20
|
+
@method = method
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!visibility private
|
24
|
+
def respond_to_missing?(_method_name, _) = true
|
25
|
+
|
26
|
+
def method_missing(method_name, ...)
|
27
|
+
@receiver.__send__(@method, method_name, ...)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
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.4.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-08-13 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
|
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
|