domainic-attributer 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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +14 -0
  3. data/LICENSE +21 -0
  4. data/README.md +396 -0
  5. data/lib/domainic/attributer/attribute/callback.rb +68 -0
  6. data/lib/domainic/attributer/attribute/coercer.rb +93 -0
  7. data/lib/domainic/attributer/attribute/mixin/belongs_to_attribute.rb +68 -0
  8. data/lib/domainic/attributer/attribute/signature.rb +338 -0
  9. data/lib/domainic/attributer/attribute/validator.rb +128 -0
  10. data/lib/domainic/attributer/attribute.rb +256 -0
  11. data/lib/domainic/attributer/attribute_set.rb +208 -0
  12. data/lib/domainic/attributer/class_methods.rb +247 -0
  13. data/lib/domainic/attributer/dsl/attribute_builder/option_parser.rb +247 -0
  14. data/lib/domainic/attributer/dsl/attribute_builder.rb +233 -0
  15. data/lib/domainic/attributer/dsl/initializer.rb +130 -0
  16. data/lib/domainic/attributer/dsl/method_injector.rb +97 -0
  17. data/lib/domainic/attributer/dsl.rb +5 -0
  18. data/lib/domainic/attributer/instance_methods.rb +65 -0
  19. data/lib/domainic/attributer/undefined.rb +44 -0
  20. data/lib/domainic/attributer.rb +114 -0
  21. data/lib/domainic-attributer.rb +3 -0
  22. data/sig/domainic/attributer/attribute/callback.rbs +48 -0
  23. data/sig/domainic/attributer/attribute/coercer.rbs +59 -0
  24. data/sig/domainic/attributer/attribute/mixin/belongs_to_attribute.rbs +46 -0
  25. data/sig/domainic/attributer/attribute/signature.rbs +223 -0
  26. data/sig/domainic/attributer/attribute/validator.rbs +83 -0
  27. data/sig/domainic/attributer/attribute.rbs +150 -0
  28. data/sig/domainic/attributer/attribute_set.rbs +134 -0
  29. data/sig/domainic/attributer/class_methods.rbs +151 -0
  30. data/sig/domainic/attributer/dsl/attribute_builder/option_parser.rbs +130 -0
  31. data/sig/domainic/attributer/dsl/attribute_builder.rbs +156 -0
  32. data/sig/domainic/attributer/dsl/initializer.rbs +91 -0
  33. data/sig/domainic/attributer/dsl/method_injector.rbs +66 -0
  34. data/sig/domainic/attributer/dsl.rbs +1 -0
  35. data/sig/domainic/attributer/instance_methods.rbs +53 -0
  36. data/sig/domainic/attributer/undefined.rbs +14 -0
  37. data/sig/domainic/attributer.rbs +69 -0
  38. data/sig/domainic-attributer.rbs +1 -0
  39. data/sig/manifest.yaml +2 -0
  40. metadata +89 -0
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/attributer/attribute'
4
+ require 'domainic/attributer/dsl/attribute_builder/option_parser'
5
+ require 'domainic/attributer/undefined'
6
+
7
+ module Domainic
8
+ module Attributer
9
+ module DSL
10
+ # A class responsible for configuring attributes through a fluent interface.
11
+ #
12
+ # This class provides a rich DSL for configuring attributes with support for
13
+ # default values, coercion, validation, visibility controls, and change tracking.
14
+ # It uses method chaining to allow natural, declarative attribute definitions.
15
+ #
16
+ # @author {https://aaronmallen.me Aaron Allen}
17
+ # @since 0.1.0
18
+ class AttributeBuilder
19
+ # @rbs @base: __todo__
20
+ # @rbs @options: OptionParser::result
21
+
22
+ # Initialize a new AttributeBuilder.
23
+ #
24
+ # @param base [Class, Module] the class or module to build the attribute in
25
+ # @param attribute_name [String, Symbol] the name of the attribute
26
+ # @param attribute_type [String, Symbol] the type of attribute
27
+ # @param type_validator [Proc, Object, nil] optional type validator
28
+ # @param options [Hash] additional options for attribute configuration
29
+ # @yield configuration block for additional attribute settings
30
+ #
31
+ # @return [void]
32
+ # @rbs (
33
+ # __todo__ base,
34
+ # String | Symbol attribute_name,
35
+ # String | Symbol attribute_type,
36
+ # ?Attribute::Validator::handler? type_validator,
37
+ # OptionParser::options options,
38
+ # ) ? { (?) [self: AttributeBuilder] -> void } -> void
39
+ def initialize(base, attribute_name, attribute_type, type_validator = Undefined, **options, &block)
40
+ @base = base
41
+ # @type var options: OptionParser::options
42
+ @options = OptionParser.parse!(attribute_name, attribute_type, options)
43
+ @options[:validators] << type_validator if type_validator != Undefined
44
+ instance_exec(&block) if block
45
+ end
46
+
47
+ # Builds and finalizes the attribute.
48
+ #
49
+ # @return [Attribute] the configured attribute
50
+ # @rbs () -> Attribute
51
+ def build!
52
+ options = @options.compact
53
+ .reject { |_, value| value == Undefined || (value.respond_to?(:empty?) && value.empty?) }
54
+ Attribute.new(@base, **options) # steep:ignore InsufficientKeywordArguments
55
+ end
56
+
57
+ # Configure value coercion.
58
+ #
59
+ # @param proc_symbol [Proc, Symbol, nil] optional coercion handler
60
+ # @yield optional coercion block
61
+ #
62
+ # @return [self] the builder for method chaining
63
+ # @rbs (?(Attribute::Coercer::proc | Object)? proc_symbol) ?{ (untyped value) -> untyped } -> self
64
+ def coerce_with(proc_symbol = Undefined, &block)
65
+ handler = proc_symbol == Undefined ? block : proc_symbol #: Attribute::Coercer::handler
66
+ @options[:coercers] << handler
67
+ self
68
+ end
69
+ alias coerce coerce_with
70
+
71
+ # Configure default value.
72
+ #
73
+ # @param value_or_proc [Object, Proc, nil] optional default value or generator
74
+ # @yield optional default value generator block
75
+ #
76
+ # @return [self] the builder for method chaining
77
+ # @rbs (?untyped? value_or_proc) ?{ (?) -> untyped } -> self
78
+ def default(value_or_proc = Undefined, &block)
79
+ @options[:default] = value_or_proc == Undefined ? block : value_or_proc
80
+ self
81
+ end
82
+ alias default_generator default
83
+ alias default_value default
84
+
85
+ # Set attribute description.
86
+ #
87
+ # @param text [String] the description text
88
+ #
89
+ # @return [self] the builder for method chaining
90
+ # @rbs (String? text) -> self
91
+ def description(text)
92
+ @options[:description] = text
93
+ self
94
+ end
95
+ alias desc description
96
+
97
+ # Mark attribute as non-nilable.
98
+ #
99
+ # @return [self] the builder for method chaining
100
+ # @rbs () -> self
101
+ def non_nilable
102
+ @options[:nilable] = false
103
+ self
104
+ end
105
+ alias non_nil non_nilable
106
+ alias non_null non_nilable
107
+ alias non_nullable non_nilable
108
+ alias not_nil non_nilable
109
+ alias not_nilable non_nilable
110
+ alias not_null non_nilable
111
+ alias not_nullable non_nilable
112
+
113
+ # Configure change callback.
114
+ #
115
+ # @param proc [Proc, nil] optional callback handler
116
+ # @yield optional callback block
117
+ #
118
+ # @return [self] the builder for method chaining
119
+ # @rbs (?Attribute::Callback::handler? proc) ?{ (untyped old_value, untyped new_value) -> void } -> self
120
+ def on_change(proc = Undefined, &block)
121
+ handler = proc == Undefined ? block : proc #: Attribute::Callback::handler
122
+ @options[:callbacks] << handler
123
+ self
124
+ end
125
+
126
+ # Set private visibility for both read and write.
127
+ #
128
+ # @return [self] the builder for method chaining
129
+ # @rbs () -> self
130
+ def private
131
+ private_read
132
+ private_write
133
+ end
134
+
135
+ # Set private visibility for read.
136
+ #
137
+ # @return [self] the builder for method chaining
138
+ # @rbs () -> self
139
+ def private_read
140
+ @options[:read] = :private
141
+ self
142
+ end
143
+
144
+ # Set private visibility for write.
145
+ #
146
+ # @return [self] the builder for method chaining
147
+ # @rbs () -> self
148
+ def private_write
149
+ @options[:write] = :private
150
+ self
151
+ end
152
+
153
+ # Set protected visibility for both read and write.
154
+ #
155
+ # @return [self] the builder for method chaining
156
+ # @rbs () -> self
157
+ def protected
158
+ protected_read
159
+ protected_write
160
+ end
161
+
162
+ # Set protected visibility for read.
163
+ #
164
+ # @return [self] the builder for method chaining
165
+ # @rbs () -> self
166
+ def protected_read
167
+ @options[:read] = :protected
168
+ self
169
+ end
170
+
171
+ # Set protected visibility for write.
172
+ #
173
+ # @return [self] the builder for method chaining
174
+ # @rbs () -> self
175
+ def protected_write
176
+ @options[:write] = :protected
177
+ self
178
+ end
179
+
180
+ # Set public visibility for both read and write.
181
+ #
182
+ # @return [self] the builder for method chaining
183
+ # @rbs () -> self
184
+ def public
185
+ public_read
186
+ public_write
187
+ end
188
+
189
+ # Set public visibility for read.
190
+ #
191
+ # @return [self] the builder for method chaining
192
+ # @rbs () -> self
193
+ def public_read
194
+ @options[:read] = :public
195
+ self
196
+ end
197
+
198
+ # Set public visibility for write.
199
+ #
200
+ # @return [self] the builder for method chaining
201
+ # @rbs () -> self
202
+ def public_write
203
+ @options[:write] = :public
204
+ self
205
+ end
206
+
207
+ # Mark attribute as required.
208
+ #
209
+ # @return [self] the builder for method chaining
210
+ # @rbs () -> self
211
+ def required
212
+ @options[:required] = true
213
+ self
214
+ end
215
+
216
+ # Configure value validation.
217
+ #
218
+ # @param object_or_proc [Object, Proc, nil] optional validation handler
219
+ # @yield optional validation block
220
+ #
221
+ # @return [self] the builder for method chaining
222
+ # @rbs (?Attribute::Validator::handler? object_or_proc) ?{ (untyped value) -> boolish } -> self
223
+ def validate_with(object_or_proc = Undefined, &block)
224
+ handler = object_or_proc == Undefined ? block : object_or_proc #: Attribute::Validator::handler
225
+ @options[:validators] << handler
226
+ self
227
+ end
228
+ alias validate validate_with
229
+ alias validates validate_with
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Domainic
4
+ module Attributer
5
+ module DSL
6
+ # A class responsible for handling object initialization with attributes.
7
+ #
8
+ # This class manages the process of setting attribute values during object
9
+ # initialization. It handles both positional arguments and keyword options,
10
+ # applying them to their corresponding attributes while respecting default
11
+ # values and required attributes.
12
+ #
13
+ # @author {https://aaronmallen.me Aaron Allen}
14
+ # @since 0.1.0
15
+ class Initializer
16
+ # @rbs @argument_attributes: Array[Attribute]
17
+ # @rbs @attributes: AttributeSet
18
+ # @rbs @base: Object
19
+ # @rbs @option_attributes: Array[Attribute]
20
+
21
+ # Initialize a new Initializer.
22
+ #
23
+ # @param base [Object] the instance being initialized
24
+ #
25
+ # @return [void]
26
+ # @rbs (Object base) -> void
27
+ def initialize(base)
28
+ @base = base
29
+ @attributes ||= @base.class.send(:__attributes__)
30
+ end
31
+
32
+ # Assign values to attributes.
33
+ #
34
+ # Validates and applies both positional arguments and keyword options to
35
+ # their corresponding attributes. Raises an error if required arguments
36
+ # are missing.
37
+ #
38
+ # @param arguments [Array] positional arguments to assign
39
+ # @param keyword_arguments [Hash] keyword arguments to assign
40
+ #
41
+ # @raise [ArgumentError] if required arguments are missing
42
+ # @return [void]
43
+ # @rbs (*untyped arguments, **untyped keyword_arguments) -> void
44
+ def assign!(*arguments, **keyword_arguments)
45
+ validate_positional_arguments!(arguments)
46
+ apply_arguments(arguments)
47
+ apply_options!(keyword_arguments)
48
+ end
49
+
50
+ private
51
+
52
+ # Access to the current attribute set.
53
+ #
54
+ # @return [AttributeSet] the attribute set for this instance
55
+ attr_reader :attributes #: AttributeSet
56
+
57
+ # Apply positional arguments to their attributes.
58
+ #
59
+ # @param arguments [Array] the positional arguments to apply
60
+ #
61
+ # @return [void]
62
+ # @rbs (Array[untyped]) -> void
63
+ def apply_arguments(arguments)
64
+ argument_attributes.each_with_index do |attribute, index|
65
+ value = arguments.length > index ? arguments[index] : Undefined
66
+ assign_value(attribute.name, value)
67
+ end
68
+ end
69
+
70
+ # Apply keyword arguments to their attributes.
71
+ #
72
+ # @param options [Hash] the keyword options to apply
73
+ #
74
+ # @return [void]
75
+ # @rbs (Hash[String | Symbol, untyped]) -> void
76
+ def apply_options!(options)
77
+ options = options.transform_keys(&:to_sym)
78
+
79
+ option_attributes.each do |attribute|
80
+ if options.key?(attribute.name)
81
+ assign_value(attribute.name, options[attribute.name])
82
+ else
83
+ assign_value(attribute.name, Undefined)
84
+ end
85
+ end
86
+ end
87
+
88
+ # Get all argument attributes.
89
+ #
90
+ # @return [Array<Attribute>] the argument attributes
91
+ # @rbs () -> Array[Attribute]
92
+ def argument_attributes
93
+ @argument_attributes ||= attributes.select { |_, attribute| attribute.signature.argument? }.attributes
94
+ end
95
+
96
+ # Assign a value to an attribute.
97
+ #
98
+ # @param attribute_name [Symbol] the name of the attribute
99
+ # @param value [Object] the value to assign
100
+ #
101
+ # @return [void]
102
+ def assign_value(attribute_name, value)
103
+ @base.send(:"#{attribute_name}=", value)
104
+ end
105
+
106
+ # Get all option attributes.
107
+ #
108
+ # @return [Array<Attribute>] the option attributes
109
+ # @rbs () -> Array[Attribute]
110
+ def option_attributes
111
+ @option_attributes ||= attributes.select { |_, attribute| attribute.signature.option? }.attributes
112
+ end
113
+
114
+ # Validate that all required positional arguments are provided.
115
+ #
116
+ # @param arguments [Array] the arguments to validate
117
+ #
118
+ # @raise [ArgumentError] if required arguments are missing
119
+ # @return [void]
120
+ # @rbs (Array[untyped]) -> void
121
+ def validate_positional_arguments!(arguments)
122
+ required = argument_attributes.reject(&:default?)
123
+ return unless arguments.length < required.length
124
+
125
+ raise ArgumentError, "wrong number of arguments (given #{arguments.length}, expected #{required.length}+)"
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Domainic
4
+ module Attributer
5
+ module DSL
6
+ # A class responsible for injecting attribute methods into classes.
7
+ #
8
+ # This class handles the creation of reader and writer methods for attributes,
9
+ # ensuring they are injected safely without overwriting existing methods. It
10
+ # respects visibility settings and properly handles value assignment through
11
+ # the attribute system.
12
+ #
13
+ # @author {https://aaronmallen.me Aaron Allen}
14
+ # @since 0.1.0
15
+ class MethodInjector
16
+ # @rbs @attribute: Attribute
17
+ # @rbs @base: __todo__
18
+
19
+ # Inject methods for an attribute into a class.
20
+ #
21
+ # @param base [Class, Module] the class to inject methods into
22
+ # @param attribute [Attribute] the attribute to create methods for
23
+ #
24
+ # @return [void]
25
+ # @rbs (__todo__ base, Attribute attribute) -> void
26
+ def self.inject!(base, attribute)
27
+ new(base, attribute).inject!
28
+ end
29
+
30
+ # Initialize a new MethodInjector.
31
+ #
32
+ # @param base [Class, Module] the class to inject methods into
33
+ # @param attribute [Attribute] the attribute to create methods for
34
+ #
35
+ # @return [void]
36
+ # @rbs (__todo__ base, Attribute attribute) -> void
37
+ def initialize(base, attribute)
38
+ @attribute = attribute
39
+ @base = base
40
+ end
41
+
42
+ # Inject reader and writer methods.
43
+ #
44
+ # @return [void]
45
+ # @rbs () -> void
46
+ def inject!
47
+ inject_reader!
48
+ inject_writer!
49
+ end
50
+
51
+ private
52
+
53
+ # Define a method if it doesn't already exist.
54
+ #
55
+ # @param method_name [Symbol] the name of the method to define
56
+ # @yield the method body to define
57
+ #
58
+ # @return [void]
59
+ # @rbs (Symbol method_name) { (?) [self: untyped] -> void } -> void
60
+ def define_safe_method(method_name, &)
61
+ return if @base.method_defined?(method_name) || @base.private_method_defined?(method_name)
62
+
63
+ @base.define_method(method_name, &)
64
+ end
65
+
66
+ # Inject the attribute reader method.
67
+ #
68
+ # Creates a reader method with the configured visibility.
69
+ #
70
+ # @return [void]
71
+ # @rbs () -> void
72
+ def inject_reader!
73
+ @base.attr_reader @attribute.name
74
+ @base.send(@attribute.signature.read_visibility, @attribute.name)
75
+ end
76
+
77
+ # Inject the attribute writer method.
78
+ #
79
+ # Creates a writer method that processes values through the attribute
80
+ # system before assignment. Sets the configured visibility.
81
+ #
82
+ # @return [void]
83
+ # @rbs () -> void
84
+ def inject_writer!
85
+ attribute_name = @attribute.name
86
+
87
+ define_safe_method(:"#{attribute_name}=") do |value|
88
+ attribute = self.class.send(:__attributes__)[attribute_name] # steep:ignore NoMethod
89
+ attribute.apply!(self, value)
90
+ end
91
+
92
+ @base.send(@attribute.signature.write_visibility, :"#{attribute_name}=")
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/attributer/dsl/attribute_builder'
4
+ require 'domainic/attributer/dsl/initializer'
5
+ require 'domainic/attributer/dsl/method_injector'
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/attributer/dsl/initializer'
4
+
5
+ module Domainic
6
+ module Attributer
7
+ # A module providing instance-level attribute functionality.
8
+ #
9
+ # This module defines instance methods for objects that include Domainic::Attributer.
10
+ # It provides initialization handling and attribute serialization capabilities, making
11
+ # it easy to work with attribute values in a consistent way.
12
+ #
13
+ # @example Basic usage
14
+ # class Person
15
+ # include Domainic::Attributer
16
+ #
17
+ # argument :name
18
+ # option :age, default: nil
19
+ # option :role, default: 'user', private_read: true
20
+ # end
21
+ #
22
+ # person = Person.new('Alice', age: 30)
23
+ # person.to_h # => { name: 'Alice', age: 30 } # role is private, not included
24
+ #
25
+ # @author {https://aaronmallen.me Aaron Allen}
26
+ # @since 0.1.0
27
+ module InstanceMethods
28
+ # Initialize a new instance with attribute values.
29
+ #
30
+ # Handles both positional arguments and keyword options, applying them to their
31
+ # corresponding attributes. This process includes:
32
+ # 1. Validating required arguments
33
+ # 2. Applying default values
34
+ # 3. Type validation and coercion
35
+ # 4. Change notifications
36
+ #
37
+ # @raise [ArgumentError] if required arguments are missing
38
+ # @return [void]
39
+ # @rbs (*untyped arguments, **untyped keyword_arguments) -> void
40
+ def initialize(...)
41
+ DSL::Initializer.new(self).assign!(...)
42
+ end
43
+
44
+ # Convert public attribute values to a hash.
45
+ #
46
+ # Creates a hash containing all public readable attributes and their current values.
47
+ # Any attributes marked as private or protected for reading are excluded from
48
+ # the result.
49
+ #
50
+ # @example Basic conversion
51
+ # person = Person.new('Alice', age: 30)
52
+ # person.to_h # => { name: 'Alice', age: 30 }
53
+ #
54
+ # @return [Hash{Symbol => Object}] hash of attribute names to values
55
+ # @rbs () -> Hash[Symbol, untyped]
56
+ def to_hash
57
+ public = self.class.send(:__attributes__).select { |_, attribute| attribute.signature.public_read? }
58
+ public.attributes.each_with_object({}) do |attribute, result|
59
+ result[attribute.name] = public_send(attribute.name)
60
+ end
61
+ end
62
+ alias to_h to_hash
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Domainic
4
+ module Attributer
5
+ # A singleton object representing an undefined value.
6
+ #
7
+ # This object is used throughout Domainic::Attributer to represent values that
8
+ # are explicitly undefined, as opposed to nil which represents the absence of
9
+ # a value. It is immutable and implements custom string representations for
10
+ # debugging purposes.
11
+ #
12
+ # @author {https://aaronmallen.me Aaron Allen}
13
+ # @since 0.1.0
14
+ Undefined = Object.new.tap do |undefined|
15
+ # Returns self to prevent cloning.
16
+ #
17
+ # @return [Undefined] self
18
+ def undefined.clone(...)
19
+ self
20
+ end
21
+
22
+ # Returns self to prevent duplication.
23
+ #
24
+ # @return [Undefined] self
25
+ def undefined.dup
26
+ self
27
+ end
28
+
29
+ # Returns a string representation of the object.
30
+ #
31
+ # @return [String] the string 'Undefined'
32
+ def undefined.inspect
33
+ to_s
34
+ end
35
+
36
+ # Converts the object to a string.
37
+ #
38
+ # @return [String] the string 'Undefined'
39
+ def undefined.to_s
40
+ 'Undefined'
41
+ end
42
+ end.freeze #: Object
43
+ end
44
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/attributer/attribute'
4
+ require 'domainic/attributer/attribute_set'
5
+ require 'domainic/attributer/class_methods'
6
+ require 'domainic/attributer/dsl'
7
+ require 'domainic/attributer/instance_methods'
8
+ require 'domainic/attributer/undefined'
9
+
10
+ module Domainic
11
+ # Core functionality for defining and managing Ruby class attributes.
12
+ #
13
+ # This module provides a flexible attribute system for Ruby classes that supports
14
+ # positional arguments and keyword options with features like type validation,
15
+ # coercion, and visibility control.
16
+ #
17
+ # Can be included directly with default method names or customized via {Domainic.Attributer}.
18
+ #
19
+ # @example Basic usage with default method names
20
+ # class Person
21
+ # include Domainic::Attributer
22
+ #
23
+ # argument :name
24
+ # option :age
25
+ # end
26
+ #
27
+ # @example Custom method names
28
+ # class Person
29
+ # include Domainic.Attributer(argument: :param, option: :opt)
30
+ #
31
+ # param :name
32
+ # opt :age
33
+ # end
34
+ #
35
+ # @author {https://aaronmallen.me Aaron Allen}
36
+ # @since 0.1.0
37
+ module Attributer
38
+ class << self
39
+ # Create a customized Attributer module.
40
+ #
41
+ # @param argument [Symbol, String] custom name for the argument method
42
+ # @param option [Symbol, String] custom name for the option method
43
+ # @return [Module] configured Attributer module
44
+ # @rbs (?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> Module
45
+ def call(argument: :argument, option: :option)
46
+ Module.new do
47
+ @argument = argument
48
+ @option = option
49
+
50
+ # @rbs (untyped base) -> void
51
+ def self.included(base)
52
+ super
53
+ Domainic::Attributer.send(:include_attributer, base, argument: @argument, option: @option)
54
+ end
55
+ end
56
+ end
57
+
58
+ # Handle direct module inclusion.
59
+ #
60
+ # @param base [Class, Module] the including class/module
61
+ # @return [void]
62
+ # @rbs (untyped base) -> void
63
+ def included(base)
64
+ super
65
+ base.include(call)
66
+ end
67
+
68
+ private
69
+
70
+ # Configure base class with Attributer functionality.
71
+ #
72
+ # @param base [Class, Module] the target class/module
73
+ # @param options [Hash] method name customization options
74
+ # @return [void]
75
+ # @rbs (untyped base, ?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> void
76
+ def include_attributer(base, **options)
77
+ base.extend(ClassMethods)
78
+ base.include(InstanceMethods)
79
+ inject_custom_methods!(base, **options)
80
+ end
81
+
82
+ # Set up custom method names.
83
+ #
84
+ # @param base [Class, Module] the target class/module
85
+ # @param options [Hash] method name customization options
86
+ # @return [void]
87
+ # @rbs (untyped base, ?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> void
88
+ def inject_custom_methods!(base, **options)
89
+ options.each do |original, custom|
90
+ base.singleton_class.alias_method(custom, original) unless custom.nil?
91
+ if (custom.nil? || custom != original) && base.respond_to?(original)
92
+ base.singleton_class.undef_method(original)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ # Create a customized Attributer module.
100
+ #
101
+ # Provides a convenient way to include Attributer with customized method names.
102
+ #
103
+ # @example
104
+ # class Person
105
+ # include Domainic.Attributer(argument: :param, option: :opt)
106
+ # end
107
+ #
108
+ # @param options [Hash] method name customization options
109
+ # @return [Module] configured Attributer module
110
+ # @rbs (?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> Module
111
+ def self.Attributer(**options) # rubocop:disable Naming/MethodName
112
+ Domainic::Attributer.call(**options)
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/attributer'