object_forge 0.1.0 → 0.2.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: 13df90e0eb14f3fb52c1b9be5706a3b2da6c844369005f0500fe31f67ced6091
4
- data.tar.gz: 6ce301c70ae87b0e46662482cd46af63d8e282aca65f92e220dc02e4a6b3e2b6
3
+ metadata.gz: 83440b5319d1e909379d16b67830ad1f67f575358473dd4ba5914cb549c417b9
4
+ data.tar.gz: 279a5756a157f96252081cb60ce0541bdffdf1e21a25fe72db19b622c7d717e8
5
5
  SHA512:
6
- metadata.gz: b75f0d0ddacd4f102a02bbb74bd0189e8b2759eb4e9a4f3f62de84a028356800908d3313ec5361299078e49a2101abcedda91358601cfc03cf76ac2bb3fb05b7
7
- data.tar.gz: 76231dd798153526c679874b3528d120a7f3562c8dfc540001be54465b46f53a50afa46f04d17f8bc2ddfcb152708d9003e417fd4d8c6d220890c62597930104
6
+ metadata.gz: e16b3091daf563ffffbb5da39b50a6cc2298a288a1e71528ab9de905e0c959718555ff8d3b6b2a3478f094fa3ebff8727f0a95e97c6adef05d3c7abb1ae6676f
7
+ data.tar.gz: 04455c660052171b8a4456323b700f91805d0c05190329427baee1468b42ccfee3c315b79534f997b45a1f2cd716f85076e164f8007cad821f43047a5ea8fcbf
@@ -12,7 +12,6 @@ module ObjectForge
12
12
  #
13
13
  # @thread_safety Attribute resolution is idempotent,
14
14
  # but modifies instance variables, making it unsafe to share the Crucible
15
- #
16
15
  # @since 0.1.0
17
16
  class Crucible < UnBasicObject
18
17
  %i[rand].each { |m| private define_method(m, ::Object.instance_method(m)) }
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "crucible"
4
4
  require_relative "forge_dsl"
5
+ require_relative "molds"
5
6
 
6
7
  module ObjectForge
7
8
  # Object instantitation forge.
@@ -19,14 +20,22 @@ module ObjectForge
19
20
  # @!attribute [r] traits
20
21
  # Attributes belonging to traits.
21
22
  # @return [Hash{Symbol => Hash{Symbol => Any}}]
22
- Parameters = Struct.new(:attributes, :traits, keyword_init: true)
23
+ #
24
+ # @!attribute [r] mold
25
+ # An object that knows how to build the instance.
26
+ # Must have a +call+ method that takes a class and a hash of attributes.
27
+ # @since 0.2.0
28
+ # @return [#call, nil]
29
+ Parameters = Struct.new(:attributes, :traits, :mold, keyword_init: true)
30
+
31
+ MOLD_MOLD = Molds::MoldMold.new.freeze
23
32
 
24
33
  # Define (and create) a forge using DSL.
25
34
  #
26
35
  # @see ForgeDSL
27
36
  # @thread_safety Thread-safe if DSL definition is thread-safe.
28
37
  #
29
- # @param forged [Class] class to forge
38
+ # @param forged [Class, Any] class or object to forge
30
39
  # @param name [Symbol, nil] forge name
31
40
  # @yieldparam f [ForgeDSL]
32
41
  # @yieldreturn [void]
@@ -38,25 +47,26 @@ module ObjectForge
38
47
  # @return [Symbol, nil] forge name
39
48
  attr_reader :name
40
49
 
41
- # @return [Class] class to forge
50
+ # @return [Class, Any] class or object to forge
42
51
  attr_reader :forged
43
52
 
44
53
  # @return [Parameters, ForgeDSL] forge parameters
45
54
  attr_reader :parameters
46
55
 
47
- # @param forged [Class] class to forge
56
+ # @param forged [Class, Any] class or object to forge
48
57
  # @param parameters [Parameters, ForgeDSL] forge parameters
49
58
  # @param name [Symbol, nil] forge name
50
59
  def initialize(forged, parameters, name: nil)
51
60
  @name = name
52
61
  @forged = forged
53
62
  @parameters = parameters
63
+ @mold = parameters.mold || MOLD_MOLD.call(forged: forged)
54
64
  end
55
65
 
56
66
  # Forge a new instance.
57
67
  #
58
- # @overload forge(*traits, **overrides)
59
- # @overload forge(traits, overrides)
68
+ # @overload forge(*traits, **overrides, &)
69
+ # @overload forge(traits, overrides, &)
60
70
  #
61
71
  # Positional arguments are taken as trait names, keyword arguments as attribute overrides,
62
72
  # unless there are exactly two positional arguments: an array and a hash.
@@ -64,19 +74,21 @@ module ObjectForge
64
74
  # All traits and overrides are applied in argument order,
65
75
  # with overrides always applied after traits.
66
76
  #
77
+ # If a block is given, forged instance is yielded to it after being built.
78
+ #
67
79
  # @thread_safety Forging is thread-safe if {#parameters},
68
- # +traits+ and +overrides+ are thread-safe.
80
+ # +traits+ and +overrides+ are thread-safe.
69
81
  #
70
82
  # @param traits [Array<Symbol>] traits to apply
71
83
  # @param overrides [Hash{Symbol => Any}] attribute overrides
84
+ # @yieldparam object [Any] forged instance
85
+ # @yieldreturn [void]
72
86
  # @return [Any] built instance
73
87
  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)
88
+ resolved_attributes = resolve_attributes(traits, overrides)
89
+ instance = @mold.call(forged: @forged, attributes: resolved_attributes)
90
+ yield instance if block_given?
91
+ instance
80
92
  end
81
93
 
82
94
  alias build forge
@@ -84,19 +96,9 @@ module ObjectForge
84
96
 
85
97
  private
86
98
 
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
99
+ def resolve_attributes(traits, overrides)
100
+ attributes = @parameters.attributes.merge(*@parameters.traits.values_at(*traits), overrides)
101
+ Crucible.new(attributes).resolve!
100
102
  end
101
103
  end
102
104
  end
@@ -3,6 +3,8 @@
3
3
  require_relative "sequence"
4
4
  require_relative "un_basic_object"
5
5
 
6
+ require_relative "molds/wrapped_mold"
7
+
6
8
  module ObjectForge
7
9
  # DSL for defining a forge.
8
10
  #
@@ -14,7 +16,6 @@ module ObjectForge
14
16
  # especially in attribute definitions.
15
17
  # The instance itself is frozen after initialization,
16
18
  # so it should be safe to share.
17
- #
18
19
  # @since 0.1.0
19
20
  class ForgeDSL < UnBasicObject
20
21
  # @return [Hash{Symbol => Proc}] attribute definitions
@@ -26,6 +27,9 @@ module ObjectForge
26
27
  # @return [Hash{Symbol => Hash{Symbol => Proc}}] trait definitions
27
28
  attr_reader :traits
28
29
 
30
+ # @return [#call, nil] forge mold
31
+ attr_reader :mold
32
+
29
33
  # Define forge's parameters through DSL.
30
34
  #
31
35
  # If the block has a parameter, an object will be yielded,
@@ -71,9 +75,34 @@ module ObjectForge
71
75
  @attributes.freeze
72
76
  @sequences.freeze
73
77
  @traits.freeze
78
+ @mold.freeze
74
79
  self
75
80
  end
76
81
 
82
+ # Set the forge mold.
83
+ #
84
+ # Mold is an object that knows how to take a hash of attributes
85
+ # and create an object from them.
86
+ # It can also be a class with +#call+, in which case a new mold will be instantiated
87
+ # automatically for each build. If a single instance is enough,
88
+ # please call +.new+ yourself once.
89
+ #
90
+ # @since 0.2.0
91
+ #
92
+ # @param mold [Class, #call, nil]
93
+ # @return [Class, #call, nil]
94
+ #
95
+ # @raise [DSLError] if +mold+ does not respond to or implement +#call+
96
+ def mold=(mold)
97
+ if nil == mold || mold.respond_to?(:call) # rubocop:disable Style/YodaCondition
98
+ @mold = mold
99
+ elsif ::Class === mold && mold.public_method_defined?(:call)
100
+ @mold = Molds::WrappedMold.new(mold)
101
+ else
102
+ raise DSLError, "mold must respond to or implement #call"
103
+ end
104
+ end
105
+
77
106
  # Define an attribute, possibly transient.
78
107
  #
79
108
  # DSL does not know or care what attributes the forged class has,
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "concurrent/map"
4
4
 
5
+ require_relative "forge"
6
+
5
7
  module ObjectForge
6
8
  # A registry for forges, making them accessible by name.
7
9
  #
@@ -20,7 +22,7 @@ module ObjectForge
20
22
  # @see Forge.define
21
23
  #
22
24
  # @param name [Symbol] name to register forge under
23
- # @param forged [Class] class to forge
25
+ # @param forged [Class, Any] class or object to forge
24
26
  # @yieldparam f [ForgeDSL]
25
27
  # @yieldreturn [void]
26
28
  # @return [Forge] forge
@@ -50,11 +52,13 @@ module ObjectForge
50
52
  # @param name [Symbol] name of the forge
51
53
  # @param traits [Array<Symbol>] traits to apply
52
54
  # @param overrides [Hash{Symbol => Any}] attribute overrides
55
+ # @yieldparam object [Any] forged instance
56
+ # @yieldreturn [void]
53
57
  # @return [Any] built instance
54
58
  #
55
59
  # @raise [KeyError] if forge with the specified name is not registered
56
- def forge(name, *traits, **overrides)
57
- @forges.fetch(name)[traits, overrides]
60
+ def forge(name, ...)
61
+ @forges.fetch(name).[](...)
58
62
  end
59
63
 
60
64
  alias build forge
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ObjectForge
4
+ module Molds
5
+ # Mold for constructing Hashes.
6
+ #
7
+ # @thread_safety Thread-safe on its own,
8
+ # but using unshareable default value or block is not thread-safe.
9
+ #
10
+ # @since 0.2.0
11
+ class HashMold
12
+ # Default value to be assigned to each produced hash.
13
+ # @return [Any, nil]
14
+ attr_reader :default
15
+ # Default proc to be assigned to each produced hash.
16
+ # @return [Proc, nil]
17
+ attr_reader :default_proc
18
+
19
+ # Initialize new HashMold with default value or default proc
20
+ # to be assigned to each produced hash.
21
+ #
22
+ # The same exact objects are used for each hash.
23
+ # It is not advised to use mutable objects as default values.
24
+ # Be aware that using a default proc with assignment
25
+ # is inherently not safe, see this Ruby issue:
26
+ # https://bugs.ruby-lang.org/issues/19237.
27
+ #
28
+ # @see Hash.new
29
+ #
30
+ # @param default_value [Any]
31
+ # @yieldparam hash [Hash]
32
+ # @yieldparam key [Any]
33
+ # @yieldreturn [Any]
34
+ def initialize(default_value = nil, &default_proc)
35
+ @default = default_value
36
+ @default_proc = default_proc
37
+ end
38
+
39
+ # Build a new hash using +forged.[]+.
40
+ #
41
+ # @see Hash.[]
42
+ #
43
+ # @param forged [Class] Hash or a subclass of Hash
44
+ # @param attributes [Hash{Symbol => Any}]
45
+ # @return [Hash]
46
+ def call(forged:, attributes:, **_)
47
+ hash = forged[attributes]
48
+ hash.default = @default if @default
49
+ hash.default_proc = @default_proc if @default_proc
50
+ hash
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ObjectForge
4
+ module Molds
5
+ # Basic mold which calls +forged.new(**attributes)+.
6
+ #
7
+ # Can be used instead of {SingleArgumentMold},
8
+ # but performance is about 1.5 times worse.
9
+ #
10
+ # @thread_safety Thread-safe.
11
+ # @since 0.2.0
12
+ class KeywordsMold
13
+ # Instantiate +forged+ with a hash of attributes.
14
+ #
15
+ # @param forged [Class, #new]
16
+ # @param attributes [Hash{Symbol => Any}]
17
+ # @return [Any]
18
+ def call(forged:, attributes:, **_)
19
+ forged.new(**attributes)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ObjectForge
4
+ module Molds
5
+ # Special "mold" that returns appropriate mold for the given forged object.
6
+ # Probably not the best fit though.
7
+ #
8
+ # Currently provides specific recognition for:
9
+ # - subclasses of +Struct+ ({StructMold}),
10
+ # - subclasses of +Data+ ({KeywordsMold}),
11
+ # - +Hash+ and subclasses ({HashMold}).
12
+ # Other objects just get {SingleArgumentMold}.
13
+ #
14
+ # @thread_safety Thread-safe.
15
+ # @since 0.2.0
16
+ class MoldMold
17
+ # Get maybe appropriate mold for the given forged object.
18
+ #
19
+ # @param forged [Class, Any]
20
+ # @return [#call] an instance of a mold
21
+ def call(forged:, **_)
22
+ # rubocop:disable Style/YodaCondition
23
+ if ::Class === forged
24
+ if ::Struct > forged
25
+ StructMold.new
26
+ elsif defined?(::Data) && ::Data > forged
27
+ KeywordsMold.new
28
+ elsif ::Hash >= forged
29
+ HashMold.new
30
+ else
31
+ SingleArgumentMold.new
32
+ end
33
+ else
34
+ SingleArgumentMold.new
35
+ end
36
+ # rubocop:enable Style/YodaCondition
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ObjectForge
4
+ module Molds
5
+ # Basic mold which calls +forged.new(attributes)+.
6
+ #
7
+ # @thread_safety Thread-safe.
8
+ # @since 0.2.0
9
+ class SingleArgumentMold
10
+ # Instantiate +forged+ with a hash of attributes.
11
+ #
12
+ # @param forged [Class, #new]
13
+ # @param attributes [Hash{Symbol => Any}]
14
+ # @return [Any]
15
+ def call(forged:, attributes:, **_)
16
+ forged.new(attributes)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ObjectForge
4
+ module Molds
5
+ # Mold for building Structs.
6
+ #
7
+ # Supports all variations of +keyword_init+.
8
+ #
9
+ # @thread_safety Thread-safe.
10
+ # @since 0.2.0
11
+ class StructMold
12
+ # Does Struct automatically use keyword initialization
13
+ # when +keyword_init+ is not specified / +nil+?
14
+ #
15
+ # @return [Boolean]
16
+ RUBY_FEATURE_AUTO_KEYWORDS = (::Struct.new(:a, :b).new(a: 1, b: 2).a == 1)
17
+
18
+ # Whether to work around argument hashes with extra keys.
19
+ #
20
+ # @return [Boolean]
21
+ attr_reader :lax
22
+ alias lax? lax
23
+
24
+ # @param lax [Boolean]
25
+ # whether to work around argument hashes with extra keys
26
+ # (when keyword_init is false, workaround always happens for technical reasons)
27
+ # - if +true+, arguments can contain extra keys, but building is slower;
28
+ # - if +false+, building may raise an error if extra keys are present;
29
+ def initialize(lax: true)
30
+ @lax = lax
31
+ end
32
+
33
+ # Instantiate +forged+ struct with a hash of attributes.
34
+ #
35
+ # @param forged [Class] a subclass of Struct
36
+ # @param attributes [Hash{Symbol => Any}]
37
+ # @return [Struct]
38
+ def call(forged:, attributes:, **_)
39
+ if forged.keyword_init?
40
+ lax ? forged.new(attributes.slice(*forged.members)) : forged.new(attributes)
41
+ elsif forged.keyword_init? == false
42
+ forged.new(*attributes.values_at(*forged.members))
43
+ else
44
+ build_struct_with_unspecified_keyword_init(forged, attributes)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ if RUBY_FEATURE_AUTO_KEYWORDS
51
+ # Build struct by using keywords to specify member values.
52
+ def build_struct_with_unspecified_keyword_init(forged, attributes)
53
+ if lax
54
+ forged.new(**attributes.slice(*forged.members))
55
+ else
56
+ forged.new(**attributes)
57
+ end
58
+ end
59
+ else
60
+ # :nocov:
61
+ # Build struct by using positional arguments to specify member values.
62
+ def build_struct_with_unspecified_keyword_init(forged, attributes)
63
+ forged.new(*attributes.values_at(*forged.members))
64
+ end
65
+ # :nocov:
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ObjectForge
4
+ module Molds
5
+ # Mold that wraps a mold class.
6
+ #
7
+ # Wrapping a mold class is useful when its +#call+ is stateful,
8
+ # making it unsafe to use multiple times or in shared environments.
9
+ #
10
+ # @thread_safety Thread-safe if {wrapped_mold} does not use global state.
11
+ # @since 0.2.0
12
+ class WrappedMold
13
+ # @return [Class] wrapped mold class
14
+ attr_reader :wrapped_mold
15
+
16
+ # @param wrapped_mold [Class] class with +#call+ method
17
+ def initialize(wrapped_mold)
18
+ @wrapped_mold = wrapped_mold
19
+ end
20
+
21
+ # @overload call(...)
22
+ # Instantiate {wrapped_mold} and call it.
23
+ #
24
+ # @return [Any] result of +wrapped_mold.new.call(...)+
25
+ def call(...)
26
+ wrapped_mold.new.call(...)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ObjectForge
4
+ # This module provides a collection of predefined molds to be used in common cases.
5
+ #
6
+ # Molds are +#call+able objects responsible for actually building objects produced by factories
7
+ # (or doing other, interesting things with them (truly, only the code review is the limit!)).
8
+ # They are supposed to be immutable, shareable, and persistent:
9
+ # initialize once, use for the whole runtime.
10
+ #
11
+ # A simple mold can easily be just a +Proc+.
12
+ # All molds must have the following +#call+ signature: +call(forged:, attributes:, **)+.
13
+ # The extra keywords are for future extensions.
14
+ #
15
+ # @example A very basic FactoryBot replacement
16
+ # creator = ->(forged:, attributes:, **) do
17
+ # instance = forged.new
18
+ # attributes.each_pair { instance.public_send(:"#{_1}=", _2) }
19
+ # instance.save!
20
+ # end
21
+ # creator.call(forged: User, attributes: { name: "John", age: 30 })
22
+ # # => <User name="John" age=30>
23
+ # @example Using a mold to serialize collection of objects (contrivedly)
24
+ # dumpy = ->(forged:, attributes:, **) do
25
+ # Enumerator.new(attributes.size) do |y|
26
+ # attributes.each_pair { y << forged.dump(_1 => _2) }
27
+ # end
28
+ # end
29
+ # dumpy.call(forged: JSON, attributes: {a:1, b:2}).to_a
30
+ # # => ["{\"a\":1}", "{\"b\":2}"]
31
+ # dumpy.call(forged: YAML, attributes: {a:1, b:2}).to_a
32
+ # # => ["---\n:a: 1\n", "---\n:b: 2\n"]
33
+ # @example Abstract factory pattern (kind of)
34
+ # class FurnitureFactory
35
+ # def call(forged:, attributes:, **)
36
+ # concrete_factory = concrete_factory(forged)
37
+ # attributes[:pieces].map do |piece|
38
+ # concrete_factory.public_send(piece, attributes.dig(:color, piece))
39
+ # end
40
+ # end
41
+ # private def concrete_factory(style)
42
+ # case style
43
+ # when :hitech
44
+ # HiTechFactory.new
45
+ # when :retro
46
+ # RetroFactory.new
47
+ # end
48
+ # end
49
+ # end
50
+ # FurnitureFactory.new.call(forged: :hitech, attributes: {
51
+ # pieces: [:chair, :table], color: { chair: :black, table: :white }
52
+ # })
53
+ # # => [<#HiTech::Chair color=:black>, <#HiTech::Table color=:white>]
54
+ # @example Abusing molds
55
+ # printer = ->(forged:, attributes:, **) { PP.pp(attributes, forged) }
56
+ # printer.call(forged: $stderr, attributes: {a:1, b:2})
57
+ # # outputs "{:a=>1, :b=>2}" to $stderr
58
+ #
59
+ # @since 0.2.0
60
+ module Molds
61
+ Dir["#{__dir__}/molds/*.rb"].each { require_relative _1 }
62
+ end
63
+ end
@@ -32,7 +32,7 @@ module ObjectForge
32
32
  end
33
33
 
34
34
  @initial = initial
35
- @container = Concurrent::MVar.new(initial)
35
+ @container = Concurrent::MVar.new(initial) # steep:ignore UnknownConstant
36
36
  end
37
37
 
38
38
  # Get the next value in the sequence, starting with the initial value.
@@ -4,7 +4,6 @@ module ObjectForge
4
4
  # BasicObject with a few common methods copied from Object.
5
5
  #
6
6
  # @api private
7
- #
8
7
  # @since 0.1.0
9
8
  class UnBasicObject < ::BasicObject
10
9
  # @!group Instance methods copied from Object
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ObjectForge
4
4
  # Current version
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
data/lib/object_forge.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir["#{__dir__}/object_forge/**/*.rb"].each { require _1 }
3
+ require_relative "object_forge/forgeyard"
4
+ require_relative "object_forge/sequence"
5
+ require_relative "object_forge/version"
4
6
 
5
7
  # A simple all-purpose factory library with minimal assumptions.
6
8
  #
@@ -76,7 +78,7 @@ module ObjectForge
76
78
  # @since 0.1.0
77
79
  #
78
80
  # @param name [Symbol] forge name
79
- # @param forged [Class] class to forge
81
+ # @param forged [Class, Any] class or object to forge
80
82
  # @yieldparam f [ForgeDSL]
81
83
  # @yieldreturn [void]
82
84
  # @return [Forge] forge
@@ -84,7 +86,7 @@ module ObjectForge
84
86
  DEFAULT_YARD.define(...)
85
87
  end
86
88
 
87
- # @overload forge(name, *traits, **overrides)
89
+ # @overload forge(name, *traits, **overrides, &)
88
90
  # Build an instance using a forge from {DEFAULT_YARD}.
89
91
  #
90
92
  # @!macro default_forgeyard
@@ -94,6 +96,8 @@ module ObjectForge
94
96
  # @param name [Symbol] name of the forge
95
97
  # @param traits [Array<Symbol>] traits to apply
96
98
  # @param overrides [Hash{Symbol => Any}] attribute overrides
99
+ # @yieldparam object [Any] forged instance
100
+ # @yieldreturn [void]
97
101
  # @return [Any] built instance
98
102
  def self.forge(...)
99
103
  DEFAULT_YARD.forge(...)
@@ -0,0 +1,72 @@
1
+ module ObjectForge
2
+ interface _Mold
3
+ def call
4
+ : (forged: untyped, attributes: Hash[Symbol, untyped], **untyped) -> untyped
5
+ end
6
+
7
+ module Molds
8
+ class MoldMold
9
+ def call
10
+ : (forged: untyped, **untyped) -> ObjectForge::_Mold
11
+ end
12
+
13
+ class SingleArgumentMold
14
+ include ObjectForge::_Mold
15
+ end
16
+
17
+ class KeywordsMold
18
+ include ObjectForge::_Mold
19
+ end
20
+
21
+ class WrappedMold
22
+ interface _MoldClass[T]
23
+ def new: () -> T
24
+ end
25
+
26
+ include ObjectForge::_Mold
27
+
28
+ attr_reader wrapped_mold: _MoldClass[ObjectForge::mold]
29
+
30
+ def initialize: (_MoldClass[ObjectForge::mold] wrapped_mold) -> void
31
+ end
32
+
33
+ class StructMold
34
+ interface _StructSubclass[T]
35
+ def new
36
+ : (*untyped) -> T
37
+ | (**untyped) -> T
38
+ | (Hash[Symbol, untyped]) -> T
39
+ def members: -> Array[Symbol]
40
+ def keyword_init?: -> bool?
41
+ end
42
+
43
+ RUBY_FEATURE_AUTO_KEYWORDS: bool
44
+ attr_reader lax: bool
45
+
46
+ def initialize: (?lax: bool) -> void
47
+
48
+ def call
49
+ : [T < Struct] (forged: _StructSubclass[T], attributes: Hash[Symbol, untyped], **untyped) -> T
50
+
51
+ private
52
+
53
+ def build_struct_with_unspecified_keyword_init
54
+ : [T < Struct] (_StructSubclass[T] forged, Hash[Symbol, untyped] attributes) -> T
55
+ end
56
+
57
+ class HashMold
58
+ interface _HashSubclass[T]
59
+ def []: (Hash[untyped, untyped]) -> T
60
+ end
61
+
62
+ attr_reader default: untyped?
63
+ attr_reader default_proc: Proc?
64
+
65
+ def initialize
66
+ : (?untyped? default_value) ?{ (Hash[untyped, untyped] hash, untyped key) -> untyped} -> void
67
+
68
+ def call
69
+ : [T < Hash] (forged: _HashSubclass[T], attributes: Hash[Symbol, untyped], **untyped) -> T
70
+ end
71
+ end
72
+ end
data/sig/object_forge.rbs CHANGED
@@ -1,47 +1,53 @@
1
1
  module ObjectForge
2
- class Error < StandardError
3
- end
4
- class DSLError < Error
5
- end
2
+ type mold = ObjectForge::_RespondTo & ObjectForge::_Mold
3
+ type sequenceable = ObjectForge::_RespondTo & ObjectForge::_Sequenceable
6
4
 
7
- interface _Sequenceable
8
- def succ: -> self
5
+ interface _RespondTo
9
6
  def respond_to?: (Symbol name, ?bool include_private) -> bool
10
7
  def class: -> Class
11
8
  end
9
+ interface _Sequenceable
10
+ def succ: -> self
11
+ end
12
12
  interface _Forgable
13
13
  def new: (Hash[Symbol, untyped]) -> self
14
14
  end
15
15
  interface _ForgeParameters
16
16
  def attributes: () -> Hash[Symbol, untyped]
17
17
  def traits: () -> Hash[Symbol, Hash[Symbol, untyped]]
18
+ def mold: () -> ObjectForge::mold?
19
+ end
20
+
21
+ class Error < StandardError
22
+ end
23
+ class DSLError < Error
18
24
  end
19
25
 
20
26
  VERSION: String
21
27
  DEFAULT_YARD: ObjectForge::Forgeyard
22
28
 
23
29
  def self.sequence
24
- : (?(ObjectForge::_Sequenceable | ObjectForge::Sequence) initial) -> ObjectForge::Sequence
30
+ : (?(ObjectForge::sequenceable | ObjectForge::Sequence) initial) -> ObjectForge::Sequence
25
31
 
26
32
  def self.define
27
33
  : (Symbol name, ObjectForge::_Forgable forged) { (ObjectForge::ForgeDSL) -> void } -> ObjectForge::Forge
28
34
  | (Symbol name, ObjectForge::_Forgable forged) { [self: ObjectForge::ForgeDSL] -> void } -> ObjectForge::Forge
29
35
 
30
36
  def self.forge
31
- : (Symbol name, *Symbol traits, **untyped overrides) -> ObjectForge::_Forgable
37
+ : (Symbol name, *Symbol traits, **untyped overrides) ?{ (untyped) -> void } -> ObjectForge::_Forgable
32
38
  end
33
39
 
34
40
  class ObjectForge::Sequence
35
41
  def self.new
36
- : (?(ObjectForge::_Sequenceable | ObjectForge::Sequence) initial) -> ObjectForge::Sequence
42
+ : (?(ObjectForge::sequenceable | ObjectForge::Sequence) initial) -> ObjectForge::Sequence
37
43
 
38
- attr_reader initial: ObjectForge::_Sequenceable
44
+ attr_reader initial: ObjectForge::sequenceable
39
45
 
40
- def initialize: (ObjectForge::_Sequenceable initial) -> void
46
+ def initialize: (ObjectForge::sequenceable initial) -> void
41
47
 
42
- def next: -> ObjectForge::_Sequenceable
48
+ def next: -> ObjectForge::sequenceable
43
49
 
44
- def reset: -> ObjectForge::_Sequenceable
50
+ def reset: -> ObjectForge::sequenceable
45
51
  alias rewind reset
46
52
  end
47
53
 
@@ -59,7 +65,7 @@ class ObjectForge::Forgeyard
59
65
  : (Symbol name, ObjectForge::Forge forge) -> ObjectForge::Forge
60
66
 
61
67
  def forge
62
- : (Symbol name, *Symbol traits, **untyped overrides) -> ObjectForge::_Forgable
68
+ : (Symbol name, *Symbol traits, **untyped overrides) ?{ (untyped) -> void } -> ObjectForge::_Forgable
63
69
  alias build forge
64
70
  alias [] forge
65
71
  end
@@ -68,10 +74,12 @@ class ObjectForge::Forge
68
74
  class Parameters
69
75
  include ObjectForge::_ForgeParameters
70
76
 
71
- def intitialize
77
+ def initialize
72
78
  : (attributes: Hash[Symbol, untyped], traits: Hash[Symbol, Hash[Symbol, untyped]]) -> void
73
79
  end
74
80
 
81
+ MOLD_MOLD: ObjectForge::Molds::MoldMold
82
+
75
83
  attr_reader forged: ObjectForge::_Forgable
76
84
  attr_reader name: Symbol
77
85
 
@@ -83,20 +91,19 @@ class ObjectForge::Forge
83
91
  : (ObjectForge::_Forgable forged, ObjectForge::_ForgeParameters parameters, ?name: Symbol?) -> void
84
92
 
85
93
  def forge
86
- : (*Symbol traits, **untyped overrides) -> ObjectForge::_Forgable
87
- | (Array[Symbol] traits, Hash[Symbol, untyped] overrides) -> ObjectForge::_Forgable
94
+ : (*Symbol traits, **untyped overrides) ?{ (untyped) -> void } -> ObjectForge::_Forgable
88
95
  alias build forge
89
96
  alias [] forge
90
97
 
91
98
  private
92
99
 
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]]
100
+ def resolve_attributes
101
+ : (Array[Symbol] traits, Hash[Symbol, untyped] overrides) -> Hash[Symbol, untyped]
96
102
  end
97
103
 
98
104
  class ObjectForge::ForgeDSL < ObjectForge::UnBasicObject
99
105
  include ObjectForge::_ForgeParameters
106
+
100
107
  attr_reader sequences: Hash[Symbol, ObjectForge::Sequence]
101
108
 
102
109
  @attributes: Hash[Symbol, Proc]
@@ -109,13 +116,15 @@ class ObjectForge::ForgeDSL < ObjectForge::UnBasicObject
109
116
 
110
117
  def freeze: -> self
111
118
 
119
+ def mold=
120
+ : (ObjectForge::mold) -> void
121
+
112
122
  def attribute
113
- : (Symbol name) { -> untyped } -> Symbol
114
- | (Symbol name) { (ObjectForge::_Sequenceable) -> untyped } -> Symbol
123
+ : (Symbol name) { [self: ObjectForge::Crucible] -> untyped } -> Symbol
115
124
  alias [] attribute
116
125
 
117
126
  def sequence
118
- : (Symbol name, ?(ObjectForge::_Sequenceable | ObjectForge::Sequence) initial) { (ObjectForge::_Sequenceable) -> untyped } -> Symbol
127
+ : (Symbol name, ?(ObjectForge::sequenceable | ObjectForge::Sequence) initial) { (ObjectForge::sequenceable) [self: ObjectForge::Crucible] -> untyped } -> Symbol
119
128
 
120
129
  def trait
121
130
  : (Symbol name) { (self) -> void } -> Symbol
@@ -125,7 +134,7 @@ class ObjectForge::ForgeDSL < ObjectForge::UnBasicObject
125
134
  private
126
135
 
127
136
  def method_missing
128
- : (Symbol name) { -> untyped } -> Symbol
137
+ : (Symbol name) { [self: ObjectForge::Crucible] -> untyped } -> Symbol
129
138
  # After freezing:
130
139
  | (Symbol name) { -> untyped } -> void
131
140
 
@@ -182,5 +191,5 @@ class ObjectForge::UnBasicObject < BasicObject
182
191
 
183
192
  def block_given?: -> bool
184
193
 
185
- def raise: (_Exception exception, ?String message) -> void
194
+ def raise: (_Exception exception, ?String message, ?Array[String] backtrace, ?cause: _Exception) -> void
186
195
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: object_forge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandr Bulancov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-30 00:00:00.000000000 Z
11
+ date: 2025-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -30,6 +30,7 @@ description: |
30
30
  It has no connection to any framework and, indeed, has nothing to do with a database.
31
31
  To use, just define some factories and call them wherever you need,
32
32
  be it in tests, console, or application code.
33
+ If needed, almost any part of the process can be easily replaced with a custom solution.
33
34
  email:
34
35
  executables: []
35
36
  extensions: []
@@ -40,19 +41,27 @@ files:
40
41
  - lib/object_forge/forge.rb
41
42
  - lib/object_forge/forge_dsl.rb
42
43
  - lib/object_forge/forgeyard.rb
44
+ - lib/object_forge/molds.rb
45
+ - lib/object_forge/molds/hash_mold.rb
46
+ - lib/object_forge/molds/keywords_mold.rb
47
+ - lib/object_forge/molds/mold_mold.rb
48
+ - lib/object_forge/molds/single_argument_mold.rb
49
+ - lib/object_forge/molds/struct_mold.rb
50
+ - lib/object_forge/molds/wrapped_mold.rb
43
51
  - lib/object_forge/sequence.rb
44
52
  - lib/object_forge/un_basic_object.rb
45
53
  - lib/object_forge/version.rb
46
54
  - sig/object_forge.rbs
55
+ - sig/object_forge/molds.rbs
47
56
  homepage: https://github.com/trinistr/object_forge
48
57
  licenses:
49
58
  - MIT
50
59
  metadata:
51
60
  homepage_uri: https://github.com/trinistr/object_forge
52
61
  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
62
+ documentation_uri: https://rubydoc.info/gems/object_forge/0.2.0
63
+ source_code_uri: https://github.com/trinistr/object_forge/tree/v0.2.0
64
+ changelog_uri: https://github.com/trinistr/object_forge/blob/v0.2.0/CHANGELOG.md
56
65
  rubygems_mfa_required: 'true'
57
66
  post_install_message:
58
67
  rdoc_options:
@@ -71,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
80
  - !ruby/object:Gem::Version
72
81
  version: '0'
73
82
  requirements: []
74
- rubygems_version: 3.3.27
83
+ rubygems_version: 3.5.22
75
84
  signing_key:
76
85
  specification_version: 4
77
86
  summary: A simple factory for objects with minimal assumptions.