object_forge 0.1.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 +7 -0
- data/lib/object_forge/crucible.rb +88 -0
- data/lib/object_forge/forge.rb +102 -0
- data/lib/object_forge/forge_dsl.rb +260 -0
- data/lib/object_forge/forgeyard.rb +63 -0
- data/lib/object_forge/sequence.rb +59 -0
- data/lib/object_forge/un_basic_object.rb +66 -0
- data/lib/object_forge/version.rb +6 -0
- data/lib/object_forge.rb +108 -0
- data/sig/object_forge.rbs +186 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 13df90e0eb14f3fb52c1b9be5706a3b2da6c844369005f0500fe31f67ced6091
|
|
4
|
+
data.tar.gz: 6ce301c70ae87b0e46662482cd46af63d8e282aca65f92e220dc02e4a6b3e2b6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b75f0d0ddacd4f102a02bbb74bd0189e8b2759eb4e9a4f3f62de84a028356800908d3313ec5361299078e49a2101abcedda91358601cfc03cf76ac2bb3fb05b7
|
|
7
|
+
data.tar.gz: 76231dd798153526c679874b3528d120a7f3562c8dfc540001be54465b46f53a50afa46f04d17f8bc2ddfcb152708d9003e417fd4d8c6d220890c62597930104
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
require_relative "un_basic_object"
|
|
6
|
+
|
|
7
|
+
module ObjectForge
|
|
8
|
+
# Melting pot for the forged object's attributes.
|
|
9
|
+
#
|
|
10
|
+
# @note This class is not intended to be used directly,
|
|
11
|
+
# but it's not a private API.
|
|
12
|
+
#
|
|
13
|
+
# @thread_safety Attribute resolution is idempotent,
|
|
14
|
+
# but modifies instance variables, making it unsafe to share the Crucible
|
|
15
|
+
#
|
|
16
|
+
# @since 0.1.0
|
|
17
|
+
class Crucible < UnBasicObject
|
|
18
|
+
%i[rand].each { |m| private define_method(m, ::Object.instance_method(m)) }
|
|
19
|
+
|
|
20
|
+
# @param attributes [Hash{Symbol => Proc, Any}] initial attributes
|
|
21
|
+
def initialize(attributes)
|
|
22
|
+
super()
|
|
23
|
+
@attributes = attributes
|
|
24
|
+
@resolved_attributes = ::Set.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Resolve all attributes by calling their +Proc+s,
|
|
28
|
+
# using +self+ as the evaluation context.
|
|
29
|
+
#
|
|
30
|
+
# Attributes can freely refer to each other inside +Proc+s
|
|
31
|
+
# through bareword names or +#[]+.
|
|
32
|
+
# However, make sure to avoid cyclic dependencies:
|
|
33
|
+
# they aren't specially detected or handled, and will cause +SystemStackError+.
|
|
34
|
+
#
|
|
35
|
+
# @note This method destructively modifies initial attributes.
|
|
36
|
+
#
|
|
37
|
+
# @return [Hash{Symbol => Any}] resolved attributes
|
|
38
|
+
def resolve!
|
|
39
|
+
@attributes.each_key { |name| method_missing(name) }
|
|
40
|
+
@attributes
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Get the value of the attribute +name+.
|
|
46
|
+
#
|
|
47
|
+
# To prevent problems with calling methods which may be defined,
|
|
48
|
+
# +#[]+ can be used instead.
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# attrs = {
|
|
52
|
+
# name: -> { "Name" },
|
|
53
|
+
# description: -> { name.downcase },
|
|
54
|
+
# duration: -> { rand(1000) }
|
|
55
|
+
# }
|
|
56
|
+
# Crucible.new(attrs).resolve!
|
|
57
|
+
# # => { name: "Name", description: "name", duration: 123 }
|
|
58
|
+
# @example using conflicting and reserved names
|
|
59
|
+
# attrs = {
|
|
60
|
+
# "[]": -> { "Brackets" },
|
|
61
|
+
# "[]=": -> { "#{self[:[]]} are brackets" },
|
|
62
|
+
# "!": -> { "#{self[:[]=]}!" }
|
|
63
|
+
# }
|
|
64
|
+
# Crucible.new(attrs).resolve!
|
|
65
|
+
# # => { "[]": "Brackets", "[]=": "Brackets are brackets", "!": "Brackets are brackets!" }
|
|
66
|
+
#
|
|
67
|
+
# @param name [Symbol]
|
|
68
|
+
# @return [Any]
|
|
69
|
+
def method_missing(name)
|
|
70
|
+
if @attributes.key?(name)
|
|
71
|
+
if @resolved_attributes.include?(name) || !(::Proc === @attributes[name])
|
|
72
|
+
@attributes[name]
|
|
73
|
+
else
|
|
74
|
+
@resolved_attributes << name
|
|
75
|
+
@attributes[name] = instance_exec(&@attributes[name])
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
alias [] method_missing
|
|
83
|
+
|
|
84
|
+
def respond_to_missing?(name, _include_all)
|
|
85
|
+
@attributes.key?(name)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "crucible"
|
|
4
|
+
require_relative "forge_dsl"
|
|
5
|
+
|
|
6
|
+
module ObjectForge
|
|
7
|
+
# Object instantitation forge.
|
|
8
|
+
#
|
|
9
|
+
# @since 0.1.0
|
|
10
|
+
class Forge
|
|
11
|
+
# Interface for forge parameters.
|
|
12
|
+
# It is not used internally, but can be useful for defining forges
|
|
13
|
+
# through means other than {ForgeDSL}.
|
|
14
|
+
#
|
|
15
|
+
# @!attribute [r] attributes
|
|
16
|
+
# Non-trait values of the attributes.
|
|
17
|
+
# @return [Hash{Symbol => Any}]
|
|
18
|
+
#
|
|
19
|
+
# @!attribute [r] traits
|
|
20
|
+
# Attributes belonging to traits.
|
|
21
|
+
# @return [Hash{Symbol => Hash{Symbol => Any}}]
|
|
22
|
+
Parameters = Struct.new(:attributes, :traits, keyword_init: true)
|
|
23
|
+
|
|
24
|
+
# Define (and create) a forge using DSL.
|
|
25
|
+
#
|
|
26
|
+
# @see ForgeDSL
|
|
27
|
+
# @thread_safety Thread-safe if DSL definition is thread-safe.
|
|
28
|
+
#
|
|
29
|
+
# @param forged [Class] class to forge
|
|
30
|
+
# @param name [Symbol, nil] forge name
|
|
31
|
+
# @yieldparam f [ForgeDSL]
|
|
32
|
+
# @yieldreturn [void]
|
|
33
|
+
# @return [Forge] forge
|
|
34
|
+
def self.define(forged, name: nil, &)
|
|
35
|
+
new(forged, ForgeDSL.new(&), name:)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Symbol, nil] forge name
|
|
39
|
+
attr_reader :name
|
|
40
|
+
|
|
41
|
+
# @return [Class] class to forge
|
|
42
|
+
attr_reader :forged
|
|
43
|
+
|
|
44
|
+
# @return [Parameters, ForgeDSL] forge parameters
|
|
45
|
+
attr_reader :parameters
|
|
46
|
+
|
|
47
|
+
# @param forged [Class] class to forge
|
|
48
|
+
# @param parameters [Parameters, ForgeDSL] forge parameters
|
|
49
|
+
# @param name [Symbol, nil] forge name
|
|
50
|
+
def initialize(forged, parameters, name: nil)
|
|
51
|
+
@name = name
|
|
52
|
+
@forged = forged
|
|
53
|
+
@parameters = parameters
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Forge a new instance.
|
|
57
|
+
#
|
|
58
|
+
# @overload forge(*traits, **overrides)
|
|
59
|
+
# @overload forge(traits, overrides)
|
|
60
|
+
#
|
|
61
|
+
# Positional arguments are taken as trait names, keyword arguments as attribute overrides,
|
|
62
|
+
# unless there are exactly two positional arguments: an array and a hash.
|
|
63
|
+
#
|
|
64
|
+
# All traits and overrides are applied in argument order,
|
|
65
|
+
# with overrides always applied after traits.
|
|
66
|
+
#
|
|
67
|
+
# @thread_safety Forging is thread-safe if {#parameters},
|
|
68
|
+
# +traits+ and +overrides+ are thread-safe.
|
|
69
|
+
#
|
|
70
|
+
# @param traits [Array<Symbol>] traits to apply
|
|
71
|
+
# @param overrides [Hash{Symbol => Any}] attribute overrides
|
|
72
|
+
# @return [Any] built instance
|
|
73
|
+
def forge(*traits, **overrides)
|
|
74
|
+
# @type var traits: Array[(Array[Symbol] | Hash[Symbol, untyped])]
|
|
75
|
+
traits, overrides = check_traits_and_overrides(traits, overrides)
|
|
76
|
+
attributes = @parameters.attributes.merge(*@parameters.traits.values_at(*traits), overrides)
|
|
77
|
+
attributes = Crucible.new(attributes).resolve!
|
|
78
|
+
|
|
79
|
+
forged.new(attributes)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
alias build forge
|
|
83
|
+
alias [] forge
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def check_traits_and_overrides(traits, overrides)
|
|
88
|
+
unless traits.size == 2 && overrides.empty?
|
|
89
|
+
# @type var traits: Array[Symbol]
|
|
90
|
+
# @type var overrides: Hash[Symbol, untyped]
|
|
91
|
+
return [traits, overrides]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
case traits
|
|
95
|
+
in [Array => real_traits, Hash => real_overrides]
|
|
96
|
+
[real_traits, real_overrides]
|
|
97
|
+
else
|
|
98
|
+
[traits, overrides]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "sequence"
|
|
4
|
+
require_relative "un_basic_object"
|
|
5
|
+
|
|
6
|
+
module ObjectForge
|
|
7
|
+
# DSL for defining a forge.
|
|
8
|
+
#
|
|
9
|
+
# @note This class is not intended to be used directly,
|
|
10
|
+
# but it's not a private API.
|
|
11
|
+
#
|
|
12
|
+
# @thread_safety DSL is not thread-safe.
|
|
13
|
+
# Take care not to introduce side effects,
|
|
14
|
+
# especially in attribute definitions.
|
|
15
|
+
# The instance itself is frozen after initialization,
|
|
16
|
+
# so it should be safe to share.
|
|
17
|
+
#
|
|
18
|
+
# @since 0.1.0
|
|
19
|
+
class ForgeDSL < UnBasicObject
|
|
20
|
+
# @return [Hash{Symbol => Proc}] attribute definitions
|
|
21
|
+
attr_reader :attributes
|
|
22
|
+
|
|
23
|
+
# @return [Hash{Symbol => Sequence}] used sequences
|
|
24
|
+
attr_reader :sequences
|
|
25
|
+
|
|
26
|
+
# @return [Hash{Symbol => Hash{Symbol => Proc}}] trait definitions
|
|
27
|
+
attr_reader :traits
|
|
28
|
+
|
|
29
|
+
# Define forge's parameters through DSL.
|
|
30
|
+
#
|
|
31
|
+
# If the block has a parameter, an object will be yielded,
|
|
32
|
+
# and +self+ context will be preserved.
|
|
33
|
+
# Otherwise, DSL will change +self+ context inside the block,
|
|
34
|
+
# without ability to call methods available outside.
|
|
35
|
+
#
|
|
36
|
+
# @example with block parameter
|
|
37
|
+
# ForgeDSL.new do |f|
|
|
38
|
+
# f.attribute(:name) { "Name" }
|
|
39
|
+
# f[:description] { name.upcase }
|
|
40
|
+
# f.duration { rand(1000) }
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# @example without block parameter
|
|
44
|
+
# ForgeDSL.new do
|
|
45
|
+
# attribute(:name) { "Name" }
|
|
46
|
+
# self[:description] { name.upcase }
|
|
47
|
+
# duration { rand(1000) }
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# @yieldparam f [ForgeDSL] self
|
|
51
|
+
# @yieldreturn [void]
|
|
52
|
+
def initialize(&dsl)
|
|
53
|
+
super
|
|
54
|
+
@attributes = {}
|
|
55
|
+
@sequences = {}
|
|
56
|
+
@traits = {}
|
|
57
|
+
|
|
58
|
+
dsl.arity.zero? ? instance_exec(&dsl) : yield(self)
|
|
59
|
+
|
|
60
|
+
freeze
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Freezes the instance, including +attributes+, +sequences+ and +traits+.
|
|
64
|
+
# Prevents further responses through +#method_missing+.
|
|
65
|
+
#
|
|
66
|
+
# @note Called automatically in {#initialize}.
|
|
67
|
+
#
|
|
68
|
+
# @return [self]
|
|
69
|
+
def freeze
|
|
70
|
+
::Object.instance_method(:freeze).bind_call(self)
|
|
71
|
+
@attributes.freeze
|
|
72
|
+
@sequences.freeze
|
|
73
|
+
@traits.freeze
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Define an attribute, possibly transient.
|
|
78
|
+
#
|
|
79
|
+
# DSL does not know or care what attributes the forged class has,
|
|
80
|
+
# so the only difference between "real" and "transient" attributes
|
|
81
|
+
# is how the class itself treats them.
|
|
82
|
+
#
|
|
83
|
+
# It is also possible to define attributes using +method_missing+ shortcut,
|
|
84
|
+
# except for conflicting or reserved names.
|
|
85
|
+
#
|
|
86
|
+
# You can refer to any other attribute inside the attribute definition block.
|
|
87
|
+
# +self[:name]+ can be used to refer to an attribute with a conflicting or reserved name.
|
|
88
|
+
#
|
|
89
|
+
# @example
|
|
90
|
+
# f.attribute(:name) { "Name" }
|
|
91
|
+
# f[:description] { name.downcase }
|
|
92
|
+
# f.duration { rand(1000) }
|
|
93
|
+
# @example using conflicting and reserved names
|
|
94
|
+
# f.attribute(:[]) { "Brackets" }
|
|
95
|
+
# f.attribute(:[]=) { "#{self[:[]]} are brackets" }
|
|
96
|
+
# f.attribute(:!) { "#{self[:[]=]}!" }
|
|
97
|
+
#
|
|
98
|
+
# @param name [Symbol] attribute name
|
|
99
|
+
# @yieldreturn [Any] attribute value
|
|
100
|
+
# @return [Symbol] attribute name
|
|
101
|
+
#
|
|
102
|
+
# @raise [ArgumentError] if +name+ is not a Symbol
|
|
103
|
+
# @raise [DSLError] if no block is given
|
|
104
|
+
def attribute(name, &definition)
|
|
105
|
+
unless ::Symbol === name
|
|
106
|
+
raise ::ArgumentError,
|
|
107
|
+
"attribute name must be a Symbol, #{name.class} given (in #{name.inspect})"
|
|
108
|
+
end
|
|
109
|
+
unless block_given?
|
|
110
|
+
raise DSLError, "attribute definition requires a block (in #{name.inspect})"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if @current_trait
|
|
114
|
+
@traits[@current_trait][name] = definition
|
|
115
|
+
else
|
|
116
|
+
@attributes[name] = definition
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
name
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
alias [] attribute
|
|
123
|
+
|
|
124
|
+
# Define an attribute, using a sequence.
|
|
125
|
+
#
|
|
126
|
+
# +name+ is used for both attribute and sequence, for the whole forge.
|
|
127
|
+
# If the name was used for a sequence previously,
|
|
128
|
+
# the sequence will not be redefined on subsequent calls.
|
|
129
|
+
#
|
|
130
|
+
# @example
|
|
131
|
+
# f.sequence(:date, Date.today)
|
|
132
|
+
# f.sequence(:id) { _1.to_s }
|
|
133
|
+
# f.sequence(:dated_id, 10) { |n| "#{Date.today}/#{n}-#{id}" }
|
|
134
|
+
# @example using external sequence
|
|
135
|
+
# seq = Sequence.new(1)
|
|
136
|
+
# f.sequence(:global_id, seq)
|
|
137
|
+
# @example sequence reuse
|
|
138
|
+
# f.sequence(:id, "a") # => "a", "b", ...
|
|
139
|
+
# f.trait :new_id do
|
|
140
|
+
# f.sequence(:id) { |n| n * 2 } # => "aa", "bb", ...
|
|
141
|
+
# end
|
|
142
|
+
#
|
|
143
|
+
# @param name [Symbol] attribute name
|
|
144
|
+
# @param initial [Sequence, #succ] existing sequence, or initial value for a new sequence
|
|
145
|
+
# @yieldparam value [#succ] current value of the sequence to calculate attribute value
|
|
146
|
+
# @yieldreturn [Any] attribute value
|
|
147
|
+
# @return [Symbol] attribute name
|
|
148
|
+
#
|
|
149
|
+
# @raise [ArgumentError] if +name+ is not a Symbol
|
|
150
|
+
# @raise [DSLError] if +initial+ does not respond to #succ and is not a {Sequence}
|
|
151
|
+
def sequence(name, initial = 1, **nil, &)
|
|
152
|
+
unless ::Symbol === name
|
|
153
|
+
raise ::ArgumentError,
|
|
154
|
+
"sequence name must be a Symbol, #{name.class} given (in #{name.inspect})"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
seq = @sequences[name] ||= Sequence.new(initial)
|
|
158
|
+
|
|
159
|
+
if block_given?
|
|
160
|
+
attribute(name) { instance_exec(seq.next, &) }
|
|
161
|
+
else
|
|
162
|
+
attribute(name) { seq.next }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
name
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Define a trait — a group of attributes with non-default values.
|
|
169
|
+
#
|
|
170
|
+
# DSL yields itself to the block, in case you need to refer to it.
|
|
171
|
+
# This can be used to define traits using a block coming from outside of DSL.
|
|
172
|
+
#
|
|
173
|
+
# @example
|
|
174
|
+
# f.trait :special do
|
|
175
|
+
# f.name { "***xXxSPECIALxXx***" }
|
|
176
|
+
# f.sequence(:special_id) { "~~~ SpEcIaL #{_1} ~~~" }
|
|
177
|
+
# end
|
|
178
|
+
# @example externally defined trait
|
|
179
|
+
# # Variable defined outside of DSL:
|
|
180
|
+
# success_trait = ->(ft) do
|
|
181
|
+
# ft.status { :success }
|
|
182
|
+
# ft.error_code { 0 }
|
|
183
|
+
# end
|
|
184
|
+
# # Inside the DSL:
|
|
185
|
+
# f.trait(:success, &success_trait)
|
|
186
|
+
#
|
|
187
|
+
# @note Traits can not be defined inside of traits.
|
|
188
|
+
#
|
|
189
|
+
# @param name [Symbol] trait name
|
|
190
|
+
# @yield block for trait definition
|
|
191
|
+
# @yieldparam f [ForgeDSL] self
|
|
192
|
+
# @yieldreturn [void]
|
|
193
|
+
# @return [Symbol] trait name
|
|
194
|
+
#
|
|
195
|
+
# @raise [ArgumentError] if +name+ is not a Symbol
|
|
196
|
+
# @raise [DSLError] if no block is given
|
|
197
|
+
# @raise [DSLError] if called inside of another trait definition
|
|
198
|
+
def trait(name, **nil)
|
|
199
|
+
unless ::Symbol === name
|
|
200
|
+
raise ::ArgumentError,
|
|
201
|
+
"trait name must be a Symbol, #{name.class} given (in #{name.inspect})"
|
|
202
|
+
end
|
|
203
|
+
if @current_trait
|
|
204
|
+
raise DSLError, "can not define trait inside of another trait (in #{name.inspect})"
|
|
205
|
+
end
|
|
206
|
+
raise DSLError, "trait definition requires a block (in #{name.inspect})" unless block_given?
|
|
207
|
+
|
|
208
|
+
@current_trait = name
|
|
209
|
+
@traits[name] = {}
|
|
210
|
+
yield self
|
|
211
|
+
@traits[name].freeze
|
|
212
|
+
@current_trait = nil
|
|
213
|
+
|
|
214
|
+
name
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Return a string containing a human-readable representation of the definition.
|
|
218
|
+
#
|
|
219
|
+
# @return [String]
|
|
220
|
+
def inspect
|
|
221
|
+
"#<#{self.class.name}:#{__id__} " \
|
|
222
|
+
"attributes=#{@attributes.keys.inspect} " \
|
|
223
|
+
"sequences=#{@sequences.keys.inspect} " \
|
|
224
|
+
"traits={#{@traits.map { |k, v| "#{k.inspect}=#{v.keys.inspect}" }.join(", ")}}>"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
private
|
|
228
|
+
|
|
229
|
+
# Define an attribute using a shorthand.
|
|
230
|
+
#
|
|
231
|
+
# Can not be used to define attributes with reserved names.
|
|
232
|
+
# Trying to use a conflicting name will lead to usual issues
|
|
233
|
+
# with calling random methods.
|
|
234
|
+
# When in doubt, use {#attribute} or {#[]} instead.
|
|
235
|
+
#
|
|
236
|
+
# Reserved names are:
|
|
237
|
+
# - all names ending in +?+, +!+ or +=+
|
|
238
|
+
# - all names starting with a non-word ASCII character
|
|
239
|
+
# (operators, +`+, +[]+, +[]=+)
|
|
240
|
+
# - +rand+
|
|
241
|
+
#
|
|
242
|
+
# @param name [Symbol] attribute name
|
|
243
|
+
# @yieldreturn [Any] attribute value
|
|
244
|
+
# @return [Symbol] attribute name
|
|
245
|
+
#
|
|
246
|
+
# @raise [DSLError] if a reserved +name+ is used
|
|
247
|
+
def method_missing(name, **nil, &)
|
|
248
|
+
return super if frozen?
|
|
249
|
+
return attribute(name, &) if respond_to_missing?(name, false)
|
|
250
|
+
|
|
251
|
+
raise DSLError, "#{name.inspect} is a reserved name (in #{name.inspect})"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def respond_to_missing?(name, _include_all)
|
|
255
|
+
return false if frozen?
|
|
256
|
+
|
|
257
|
+
!name.end_with?("?", "!", "=") && !name.match?(/\A(?=\p{ASCII})\P{Word}/) && name != :rand
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
|
|
5
|
+
module ObjectForge
|
|
6
|
+
# A registry for forges, making them accessible by name.
|
|
7
|
+
#
|
|
8
|
+
# @since 0.1.0
|
|
9
|
+
class Forgeyard
|
|
10
|
+
# @return [Concurrent::Map{Symbol => Forge}] registered forges
|
|
11
|
+
attr_reader :forges
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@forges = Concurrent::Map.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Define and register a forge in one go.
|
|
18
|
+
#
|
|
19
|
+
# @see #register
|
|
20
|
+
# @see Forge.define
|
|
21
|
+
#
|
|
22
|
+
# @param name [Symbol] name to register forge under
|
|
23
|
+
# @param forged [Class] class to forge
|
|
24
|
+
# @yieldparam f [ForgeDSL]
|
|
25
|
+
# @yieldreturn [void]
|
|
26
|
+
# @return [Forge] forge
|
|
27
|
+
def define(name, forged, &)
|
|
28
|
+
register(name, Forge.define(forged, name: name, &))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Add a forge under a specified name.
|
|
32
|
+
#
|
|
33
|
+
# If +name+ was already taken, new +forge+ will be ignored
|
|
34
|
+
# and existing forge will be returned.
|
|
35
|
+
#
|
|
36
|
+
# @thread_safety Registration is thread-safe, i.e. first one always wins.
|
|
37
|
+
#
|
|
38
|
+
# @param name [Symbol] name to register forge under
|
|
39
|
+
# @param forge [Forge] forge to register
|
|
40
|
+
# @return [Forge] actually registered forge
|
|
41
|
+
def register(name, forge)
|
|
42
|
+
# `put_if_absent` returns `nil` if there was no previous value, hence the `||`.
|
|
43
|
+
@forges.put_if_absent(name, forge) || forge
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Build an instance using a forge.
|
|
47
|
+
#
|
|
48
|
+
# @see Forge#forge
|
|
49
|
+
#
|
|
50
|
+
# @param name [Symbol] name of the forge
|
|
51
|
+
# @param traits [Array<Symbol>] traits to apply
|
|
52
|
+
# @param overrides [Hash{Symbol => Any}] attribute overrides
|
|
53
|
+
# @return [Any] built instance
|
|
54
|
+
#
|
|
55
|
+
# @raise [KeyError] if forge with the specified name is not registered
|
|
56
|
+
def forge(name, *traits, **overrides)
|
|
57
|
+
@forges.fetch(name)[traits, overrides]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
alias build forge
|
|
61
|
+
alias [] forge
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/mvar"
|
|
4
|
+
|
|
5
|
+
module ObjectForge
|
|
6
|
+
# A thread-safe representation of a sequence of values.
|
|
7
|
+
#
|
|
8
|
+
# @since 0.1.0
|
|
9
|
+
class Sequence
|
|
10
|
+
# Return a new sequence, or +initial+ if it's already a sequence.
|
|
11
|
+
#
|
|
12
|
+
# @param initial [#succ, Sequence]
|
|
13
|
+
# @return [Sequence]
|
|
14
|
+
def self.new(initial, ...)
|
|
15
|
+
return initial if initial.is_a?(Sequence)
|
|
16
|
+
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [#succ] initial value for the sequence
|
|
21
|
+
attr_reader :initial
|
|
22
|
+
|
|
23
|
+
# @note Initial value must not be modified after the sequence is created,
|
|
24
|
+
# or the results will be unpredicatable. Consider always passing a frozen value.
|
|
25
|
+
#
|
|
26
|
+
# @param initial [#succ] initial value for the sequence
|
|
27
|
+
#
|
|
28
|
+
# @raise [ArgumentError] if +initial+ does not respond to #succ
|
|
29
|
+
def initialize(initial)
|
|
30
|
+
unless initial.respond_to?(:succ)
|
|
31
|
+
raise ArgumentError, "initial value must respond to #succ, #{initial.class} given"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@initial = initial
|
|
35
|
+
@container = Concurrent::MVar.new(initial)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get the next value in the sequence, starting with the initial value.
|
|
39
|
+
#
|
|
40
|
+
# @thread_safety Sequence traversal is synchronized,
|
|
41
|
+
# so no duplicate values will be returned.
|
|
42
|
+
#
|
|
43
|
+
# @return [#succ] next value
|
|
44
|
+
def next
|
|
45
|
+
@container.modify(&:succ)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Reset the sequence to its {#initial} value.
|
|
49
|
+
#
|
|
50
|
+
# @thread_safety Reset is synchronized with {#next}.
|
|
51
|
+
#
|
|
52
|
+
# @return [#succ] whatever value would be returned by {#next} before reset
|
|
53
|
+
def reset
|
|
54
|
+
@container.modify { @initial }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
alias rewind reset
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ObjectForge
|
|
4
|
+
# BasicObject with a few common methods copied from Object.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# @since 0.1.0
|
|
9
|
+
class UnBasicObject < ::BasicObject
|
|
10
|
+
# @!group Instance methods copied from Object
|
|
11
|
+
# @!method class
|
|
12
|
+
# @see Kernel#class
|
|
13
|
+
# @return [Class]
|
|
14
|
+
# @!method eql?(other)
|
|
15
|
+
# @see Object#eql?
|
|
16
|
+
# @return [Boolean]
|
|
17
|
+
# @!method freeze
|
|
18
|
+
# @see Kernel#freeze
|
|
19
|
+
# @return [self]
|
|
20
|
+
# @!method frozen?
|
|
21
|
+
# @see Kernel#frozen?
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
# @!method hash
|
|
24
|
+
# @see Object#hash
|
|
25
|
+
# @return [Integer]
|
|
26
|
+
# @!method inspect
|
|
27
|
+
# @see Object#inspect
|
|
28
|
+
# @return [String]
|
|
29
|
+
# @!method is_a?(class)
|
|
30
|
+
# @see Kernel#is_a?
|
|
31
|
+
# @return [Boolean]
|
|
32
|
+
# @!method respond_to?(symbol [, include_private])
|
|
33
|
+
# @see Object#respond_to?
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
# @!method to_s
|
|
36
|
+
# @see Object#to_s
|
|
37
|
+
# @return [String]
|
|
38
|
+
%i[class eql? freeze frozen? hash inspect is_a? respond_to? to_s].each do |m|
|
|
39
|
+
define_method(m, ::Object.instance_method(m))
|
|
40
|
+
end
|
|
41
|
+
alias kind_of? is_a?
|
|
42
|
+
# @!endgroup
|
|
43
|
+
|
|
44
|
+
%i[block_given? raise].each { |m| private define_method(m, ::Object.instance_method(m)) }
|
|
45
|
+
|
|
46
|
+
# @!macro pp_support
|
|
47
|
+
# Support for +pp+ (and IRB).
|
|
48
|
+
#
|
|
49
|
+
# @note This method dynamically calls UnboundMethod#bind_call, making it fairly slow.
|
|
50
|
+
#
|
|
51
|
+
# @api public
|
|
52
|
+
def pretty_print(...)
|
|
53
|
+
# We have to do it this way, instead of defining methods,
|
|
54
|
+
# because Object#pretty_print does not exist without requiring "pp".
|
|
55
|
+
::Object.instance_method(:pretty_print).bind_call(self, ...)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @!macro pp_support
|
|
59
|
+
def pretty_print_cycle(...)
|
|
60
|
+
# See comment for #pretty_print.
|
|
61
|
+
# :nocov:
|
|
62
|
+
::Object.instance_method(:pretty_print_cycle).bind_call(self, ...)
|
|
63
|
+
# :nocov:
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/object_forge.rb
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Dir["#{__dir__}/object_forge/**/*.rb"].each { require _1 }
|
|
4
|
+
|
|
5
|
+
# A simple all-purpose factory library with minimal assumptions.
|
|
6
|
+
#
|
|
7
|
+
# These are the main classes you should be aware of:
|
|
8
|
+
# - {Forgeyard} is a registry of named related Forges.
|
|
9
|
+
# A Forgeyard allows to {Forgeyard#define} a Forge,
|
|
10
|
+
# and {Forgeyard#forge} a new object using a defined Forge.
|
|
11
|
+
# - {Forge} is a factory for objects.
|
|
12
|
+
# Usually created through {Forgeyard#define}/{Forge.define} in a manner similar to FactoryBot,
|
|
13
|
+
# Forges can be used standalone, or as a part of a Forgeyard.
|
|
14
|
+
# - {Sequence} is a representation of a sequence of values.
|
|
15
|
+
# They are usually used implicitly through {ForgeDSL#sequence},
|
|
16
|
+
# but can be created explicitly to be shared (or used outside of ObjectForge).
|
|
17
|
+
#
|
|
18
|
+
# Additionally, successful use may depend on understanding these:
|
|
19
|
+
# - {ForgeDSL} is a block-based DSL inspired by FactoryBot and ROM::Factory.
|
|
20
|
+
# It allows defining arbitrary attributes (possibly using sequences),
|
|
21
|
+
# with support for traits (collections of attributes with non-default values).
|
|
22
|
+
# - {Crucible} is used to resolve attributes.
|
|
23
|
+
#
|
|
24
|
+
# @example Quick example
|
|
25
|
+
# Frobinator = Struct.new(:frob, :inator, keyword_init: true)
|
|
26
|
+
# # Forge's name and forged class are completely independent.
|
|
27
|
+
# ObjectForge.define(:frobber, Frobinator) do |f|
|
|
28
|
+
# f.frob { "Frob" + inator.call }
|
|
29
|
+
# f.inator { -> { "inator" } }
|
|
30
|
+
# f.trait :static do |tf|
|
|
31
|
+
# tf.frob { "Static" }
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
# # These methods are aliases:
|
|
35
|
+
# ObjectForge.forge(:frobber)
|
|
36
|
+
# # => #<struct Frobinator frob="Frobinator", inator=#<Proc:...>>
|
|
37
|
+
# ObjectForge.build(:frobber, frob: -> { "Frob" + inator }, inator: "orn")
|
|
38
|
+
# # => #<struct Frobinator frob="Froborn", inator="orn">
|
|
39
|
+
# ObjectForge[:frobber, :static, inator: "Value"]
|
|
40
|
+
# # => #<struct Frobinator frob="Static", inator="Value">
|
|
41
|
+
module ObjectForge
|
|
42
|
+
# Base error class for ObjectForge.
|
|
43
|
+
# @since 0.1.0
|
|
44
|
+
class Error < StandardError; end
|
|
45
|
+
# Error raised when a mistake is made in using DSL.
|
|
46
|
+
# @since 0.1.0
|
|
47
|
+
class DSLError < Error; end
|
|
48
|
+
|
|
49
|
+
# Default {Forgeyard} that is used by {.define} and {.forge}.
|
|
50
|
+
#
|
|
51
|
+
# @!macro default_forgeyard
|
|
52
|
+
# @note
|
|
53
|
+
# Default forgeyard is intended to be useful for non-shareable code,
|
|
54
|
+
# like simple application tests and specs.
|
|
55
|
+
# It should not be used in application code, and never in gems.
|
|
56
|
+
# @since 0.1.0
|
|
57
|
+
DEFAULT_YARD = Forgeyard.new
|
|
58
|
+
|
|
59
|
+
# @overload sequence(initial)
|
|
60
|
+
# Create a sequence, to be used wherever it needs to be.
|
|
61
|
+
#
|
|
62
|
+
# @see Sequence.new
|
|
63
|
+
# @since 0.1.0
|
|
64
|
+
#
|
|
65
|
+
# @param initial [#succ, Sequence]
|
|
66
|
+
# @return [Sequence]
|
|
67
|
+
def self.sequence(...)
|
|
68
|
+
Sequence.new(...)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @overload define(name, forged, &)
|
|
72
|
+
# Define and create a forge in {DEFAULT_YARD}.
|
|
73
|
+
#
|
|
74
|
+
# @!macro default_forgeyard
|
|
75
|
+
# @see Forgeyard#define
|
|
76
|
+
# @since 0.1.0
|
|
77
|
+
#
|
|
78
|
+
# @param name [Symbol] forge name
|
|
79
|
+
# @param forged [Class] class to forge
|
|
80
|
+
# @yieldparam f [ForgeDSL]
|
|
81
|
+
# @yieldreturn [void]
|
|
82
|
+
# @return [Forge] forge
|
|
83
|
+
def self.define(...)
|
|
84
|
+
DEFAULT_YARD.define(...)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @overload forge(name, *traits, **overrides)
|
|
88
|
+
# Build an instance using a forge from {DEFAULT_YARD}.
|
|
89
|
+
#
|
|
90
|
+
# @!macro default_forgeyard
|
|
91
|
+
# @see Forgeyard#forge
|
|
92
|
+
# @since 0.1.0
|
|
93
|
+
#
|
|
94
|
+
# @param name [Symbol] name of the forge
|
|
95
|
+
# @param traits [Array<Symbol>] traits to apply
|
|
96
|
+
# @param overrides [Hash{Symbol => Any}] attribute overrides
|
|
97
|
+
# @return [Any] built instance
|
|
98
|
+
def self.forge(...)
|
|
99
|
+
DEFAULT_YARD.forge(...)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class << self
|
|
103
|
+
# @since 0.1.0
|
|
104
|
+
alias build forge
|
|
105
|
+
# @since 0.1.0
|
|
106
|
+
alias [] forge
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
module ObjectForge
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
class DSLError < Error
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
interface _Sequenceable
|
|
8
|
+
def succ: -> self
|
|
9
|
+
def respond_to?: (Symbol name, ?bool include_private) -> bool
|
|
10
|
+
def class: -> Class
|
|
11
|
+
end
|
|
12
|
+
interface _Forgable
|
|
13
|
+
def new: (Hash[Symbol, untyped]) -> self
|
|
14
|
+
end
|
|
15
|
+
interface _ForgeParameters
|
|
16
|
+
def attributes: () -> Hash[Symbol, untyped]
|
|
17
|
+
def traits: () -> Hash[Symbol, Hash[Symbol, untyped]]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
VERSION: String
|
|
21
|
+
DEFAULT_YARD: ObjectForge::Forgeyard
|
|
22
|
+
|
|
23
|
+
def self.sequence
|
|
24
|
+
: (?(ObjectForge::_Sequenceable | ObjectForge::Sequence) initial) -> ObjectForge::Sequence
|
|
25
|
+
|
|
26
|
+
def self.define
|
|
27
|
+
: (Symbol name, ObjectForge::_Forgable forged) { (ObjectForge::ForgeDSL) -> void } -> ObjectForge::Forge
|
|
28
|
+
| (Symbol name, ObjectForge::_Forgable forged) { [self: ObjectForge::ForgeDSL] -> void } -> ObjectForge::Forge
|
|
29
|
+
|
|
30
|
+
def self.forge
|
|
31
|
+
: (Symbol name, *Symbol traits, **untyped overrides) -> ObjectForge::_Forgable
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class ObjectForge::Sequence
|
|
35
|
+
def self.new
|
|
36
|
+
: (?(ObjectForge::_Sequenceable | ObjectForge::Sequence) initial) -> ObjectForge::Sequence
|
|
37
|
+
|
|
38
|
+
attr_reader initial: ObjectForge::_Sequenceable
|
|
39
|
+
|
|
40
|
+
def initialize: (ObjectForge::_Sequenceable initial) -> void
|
|
41
|
+
|
|
42
|
+
def next: -> ObjectForge::_Sequenceable
|
|
43
|
+
|
|
44
|
+
def reset: -> ObjectForge::_Sequenceable
|
|
45
|
+
alias rewind reset
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class ObjectForge::Forgeyard
|
|
49
|
+
attr_reader forges: Concurrent::Map[Symbol, ObjectForge::Forge]
|
|
50
|
+
|
|
51
|
+
def initialize
|
|
52
|
+
: () -> void
|
|
53
|
+
|
|
54
|
+
def define
|
|
55
|
+
: (Symbol name, ObjectForge::_Forgable forged) { (ObjectForge::ForgeDSL) -> void } -> ObjectForge::Forge
|
|
56
|
+
| (Symbol name, ObjectForge::_Forgable forged) { [self: ObjectForge::ForgeDSL] -> void } -> ObjectForge::Forge
|
|
57
|
+
|
|
58
|
+
def register
|
|
59
|
+
: (Symbol name, ObjectForge::Forge forge) -> ObjectForge::Forge
|
|
60
|
+
|
|
61
|
+
def forge
|
|
62
|
+
: (Symbol name, *Symbol traits, **untyped overrides) -> ObjectForge::_Forgable
|
|
63
|
+
alias build forge
|
|
64
|
+
alias [] forge
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class ObjectForge::Forge
|
|
68
|
+
class Parameters
|
|
69
|
+
include ObjectForge::_ForgeParameters
|
|
70
|
+
|
|
71
|
+
def intitialize
|
|
72
|
+
: (attributes: Hash[Symbol, untyped], traits: Hash[Symbol, Hash[Symbol, untyped]]) -> void
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attr_reader forged: ObjectForge::_Forgable
|
|
76
|
+
attr_reader name: Symbol
|
|
77
|
+
|
|
78
|
+
def self.define
|
|
79
|
+
: (ObjectForge::_Forgable forged, ?name: Symbol?) { (ObjectForge::ForgeDSL) -> void } -> ObjectForge::Forge
|
|
80
|
+
| (ObjectForge::_Forgable forged, ?name: Symbol?) { [self: ObjectForge::ForgeDSL] -> void } -> ObjectForge::Forge
|
|
81
|
+
|
|
82
|
+
def initialize
|
|
83
|
+
: (ObjectForge::_Forgable forged, ObjectForge::_ForgeParameters parameters, ?name: Symbol?) -> void
|
|
84
|
+
|
|
85
|
+
def forge
|
|
86
|
+
: (*Symbol traits, **untyped overrides) -> ObjectForge::_Forgable
|
|
87
|
+
| (Array[Symbol] traits, Hash[Symbol, untyped] overrides) -> ObjectForge::_Forgable
|
|
88
|
+
alias build forge
|
|
89
|
+
alias [] forge
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def check_traits_and_overrides
|
|
94
|
+
: (Array[Symbol] traits, Hash[Symbol, untyped] overrides) -> [Array[Symbol], Hash[Symbol, untyped]]
|
|
95
|
+
| (Array[(Array[Symbol] | Hash[Symbol, untyped])], Hash[Symbol, untyped]) -> [Array[Symbol], Hash[Symbol, untyped]]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class ObjectForge::ForgeDSL < ObjectForge::UnBasicObject
|
|
99
|
+
include ObjectForge::_ForgeParameters
|
|
100
|
+
attr_reader sequences: Hash[Symbol, ObjectForge::Sequence]
|
|
101
|
+
|
|
102
|
+
@attributes: Hash[Symbol, Proc]
|
|
103
|
+
@sequences: Hash[Symbol, ObjectForge::Sequence]
|
|
104
|
+
@traits: Hash[Symbol, Hash[Symbol, Proc]]
|
|
105
|
+
|
|
106
|
+
def initialize
|
|
107
|
+
: () { (ObjectForge::ForgeDSL) -> void } -> void
|
|
108
|
+
| () { [self: ObjectForge::ForgeDSL] -> void } -> void
|
|
109
|
+
|
|
110
|
+
def freeze: -> self
|
|
111
|
+
|
|
112
|
+
def attribute
|
|
113
|
+
: (Symbol name) { -> untyped } -> Symbol
|
|
114
|
+
| (Symbol name) { (ObjectForge::_Sequenceable) -> untyped } -> Symbol
|
|
115
|
+
alias [] attribute
|
|
116
|
+
|
|
117
|
+
def sequence
|
|
118
|
+
: (Symbol name, ?(ObjectForge::_Sequenceable | ObjectForge::Sequence) initial) { (ObjectForge::_Sequenceable) -> untyped } -> Symbol
|
|
119
|
+
|
|
120
|
+
def trait
|
|
121
|
+
: (Symbol name) { (self) -> void } -> Symbol
|
|
122
|
+
|
|
123
|
+
def inspect: -> String
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def method_missing
|
|
128
|
+
: (Symbol name) { -> untyped } -> Symbol
|
|
129
|
+
# After freezing:
|
|
130
|
+
| (Symbol name) { -> untyped } -> void
|
|
131
|
+
|
|
132
|
+
def respond_to_missing?
|
|
133
|
+
: (Symbol name, bool include_all) -> bool
|
|
134
|
+
|
|
135
|
+
def rand: [T] (?(Float | Integer | Range[T])) -> (Float | Integer | T)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class ObjectForge::Crucible < ObjectForge::UnBasicObject
|
|
139
|
+
@attributes: Hash[Symbol, untyped]
|
|
140
|
+
@resolved_attributes: Set[Symbol]
|
|
141
|
+
|
|
142
|
+
def initialize
|
|
143
|
+
: (Hash[Symbol, untyped] attributes) -> void
|
|
144
|
+
|
|
145
|
+
def resolve!
|
|
146
|
+
: -> Hash[Symbol, untyped]
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def method_missing
|
|
151
|
+
: (Symbol name) -> untyped
|
|
152
|
+
alias [] method_missing
|
|
153
|
+
|
|
154
|
+
def respond_to_missing?
|
|
155
|
+
: (Symbol name, bool include_all) -> bool
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class ObjectForge::UnBasicObject < BasicObject
|
|
159
|
+
def class: -> Class
|
|
160
|
+
|
|
161
|
+
def eql?: (untyped other) -> bool
|
|
162
|
+
|
|
163
|
+
def freeze: -> self
|
|
164
|
+
|
|
165
|
+
def frozen?: -> bool
|
|
166
|
+
|
|
167
|
+
def hash: -> Integer
|
|
168
|
+
|
|
169
|
+
def inspect: -> String
|
|
170
|
+
|
|
171
|
+
def is_a?: (Module klass) -> bool
|
|
172
|
+
|
|
173
|
+
def respond_to?: (Symbol name, ?bool include_private) -> bool
|
|
174
|
+
|
|
175
|
+
def to_s: -> String
|
|
176
|
+
|
|
177
|
+
def pretty_print: (untyped) -> void
|
|
178
|
+
|
|
179
|
+
def pretty_print_cycle: (untyped) -> void
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
def block_given?: -> bool
|
|
184
|
+
|
|
185
|
+
def raise: (_Exception exception, ?String message) -> void
|
|
186
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: object_forge
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alexandr Bulancov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-07-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: concurrent-ruby
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.2'
|
|
27
|
+
description: |
|
|
28
|
+
ObjectForge provides a familiar way to build objects in any context
|
|
29
|
+
with minimal assumptions about usage environment.
|
|
30
|
+
It has no connection to any framework and, indeed, has nothing to do with a database.
|
|
31
|
+
To use, just define some factories and call them wherever you need,
|
|
32
|
+
be it in tests, console, or application code.
|
|
33
|
+
email:
|
|
34
|
+
executables: []
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- lib/object_forge.rb
|
|
39
|
+
- lib/object_forge/crucible.rb
|
|
40
|
+
- lib/object_forge/forge.rb
|
|
41
|
+
- lib/object_forge/forge_dsl.rb
|
|
42
|
+
- lib/object_forge/forgeyard.rb
|
|
43
|
+
- lib/object_forge/sequence.rb
|
|
44
|
+
- lib/object_forge/un_basic_object.rb
|
|
45
|
+
- lib/object_forge/version.rb
|
|
46
|
+
- sig/object_forge.rbs
|
|
47
|
+
homepage: https://github.com/trinistr/object_forge
|
|
48
|
+
licenses:
|
|
49
|
+
- MIT
|
|
50
|
+
metadata:
|
|
51
|
+
homepage_uri: https://github.com/trinistr/object_forge
|
|
52
|
+
bug_tracker_uri: https://github.com/trinistr/object_forge/issues
|
|
53
|
+
documentation_uri: https://rubydoc.info/gems/object_forge/0.1.0
|
|
54
|
+
source_code_uri: https://github.com/trinistr/object_forge/tree/v0.1.0
|
|
55
|
+
changelog_uri: https://github.com/trinistr/object_forge/blob/v0.1.0/CHANGELOG.md
|
|
56
|
+
rubygems_mfa_required: 'true'
|
|
57
|
+
post_install_message:
|
|
58
|
+
rdoc_options:
|
|
59
|
+
- "--tag"
|
|
60
|
+
- thread_safety:Thread safety
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 3.1.3
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 3.3.27
|
|
75
|
+
signing_key:
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: A simple factory for objects with minimal assumptions.
|
|
78
|
+
test_files: []
|