domainic-attributer 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 +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +32 -1
- data/README.md +42 -355
- data/docs/USAGE.md +723 -0
- data/lib/domainic/attributer/attribute/callback.rb +21 -9
- data/lib/domainic/attributer/attribute/coercer.rb +28 -13
- data/lib/domainic/attributer/attribute/mixin/belongs_to_attribute.rb +16 -13
- data/lib/domainic/attributer/attribute/signature.rb +43 -32
- data/lib/domainic/attributer/attribute/validator.rb +46 -16
- data/lib/domainic/attributer/attribute.rb +28 -18
- data/lib/domainic/attributer/attribute_set.rb +21 -19
- data/lib/domainic/attributer/class_methods.rb +136 -83
- data/lib/domainic/attributer/dsl/attribute_builder/option_parser.rb +64 -22
- data/lib/domainic/attributer/dsl/attribute_builder.rb +515 -26
- data/lib/domainic/attributer/dsl/initializer.rb +23 -18
- data/lib/domainic/attributer/dsl/method_injector.rb +16 -14
- data/lib/domainic/attributer/errors/aggregate_error.rb +36 -0
- data/lib/domainic/attributer/errors/callback_execution_error.rb +30 -0
- data/lib/domainic/attributer/errors/coercion_execution_error.rb +37 -0
- data/lib/domainic/attributer/errors/error.rb +19 -0
- data/lib/domainic/attributer/errors/validation_execution_error.rb +30 -0
- data/lib/domainic/attributer/instance_methods.rb +11 -8
- data/lib/domainic/attributer/undefined.rb +9 -7
- data/lib/domainic/attributer.rb +88 -27
- data/sig/domainic/attributer/attribute/callback.rbs +10 -7
- data/sig/domainic/attributer/attribute/coercer.rbs +14 -11
- data/sig/domainic/attributer/attribute/mixin/belongs_to_attribute.rbs +14 -12
- data/sig/domainic/attributer/attribute/signature.rbs +43 -32
- data/sig/domainic/attributer/attribute/validator.rbs +28 -13
- data/sig/domainic/attributer/attribute.rbs +27 -17
- data/sig/domainic/attributer/attribute_set.rbs +21 -19
- data/sig/domainic/attributer/class_methods.rbs +133 -80
- data/sig/domainic/attributer/dsl/attribute_builder/option_parser.rbs +62 -22
- data/sig/domainic/attributer/dsl/attribute_builder.rbs +515 -26
- data/sig/domainic/attributer/dsl/initializer.rbs +21 -19
- data/sig/domainic/attributer/dsl/method_injector.rbs +16 -14
- data/sig/domainic/attributer/errors/aggregate_error.rbs +28 -0
- data/sig/domainic/attributer/errors/callback_execution_error.rbs +23 -0
- data/sig/domainic/attributer/errors/coercion_execution_error.rbs +29 -0
- data/sig/domainic/attributer/errors/error.rbs +17 -0
- data/sig/domainic/attributer/errors/validation_execution_error.rbs +23 -0
- data/sig/domainic/attributer/instance_methods.rbs +11 -8
- data/sig/domainic/attributer/undefined.rbs +5 -3
- data/sig/domainic/attributer.rbs +88 -27
- metadata +19 -6
@@ -1,15 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'domainic/attributer/undefined'
|
4
|
+
|
3
5
|
module Domainic
|
4
6
|
module Attributer
|
5
7
|
module DSL
|
6
|
-
# A class responsible for handling object initialization with attributes
|
8
|
+
# A class responsible for handling object initialization with attributes
|
7
9
|
#
|
8
10
|
# This class manages the process of setting attribute values during object
|
9
11
|
# initialization. It handles both positional arguments and keyword options,
|
10
12
|
# applying them to their corresponding attributes while respecting default
|
11
|
-
# values and required attributes
|
13
|
+
# values and required attributes
|
12
14
|
#
|
15
|
+
# @api private
|
16
|
+
# @!visibility private
|
13
17
|
# @author {https://aaronmallen.me Aaron Allen}
|
14
18
|
# @since 0.1.0
|
15
19
|
class Initializer
|
@@ -18,25 +22,25 @@ module Domainic
|
|
18
22
|
# @rbs @base: Object
|
19
23
|
# @rbs @option_attributes: Array[Attribute]
|
20
24
|
|
21
|
-
# Initialize a new Initializer
|
25
|
+
# Initialize a new Initializer
|
22
26
|
#
|
23
27
|
# @param base [Object] the instance being initialized
|
24
28
|
#
|
25
|
-
# @return [
|
29
|
+
# @return [Initializer] the new Initializer instance
|
26
30
|
# @rbs (Object base) -> void
|
27
31
|
def initialize(base)
|
28
32
|
@base = base
|
29
33
|
@attributes ||= @base.class.send(:__attributes__)
|
30
34
|
end
|
31
35
|
|
32
|
-
# Assign values to attributes
|
36
|
+
# Assign values to attributes
|
33
37
|
#
|
34
38
|
# Validates and applies both positional arguments and keyword options to
|
35
39
|
# their corresponding attributes. Raises an error if required arguments
|
36
|
-
# are missing
|
40
|
+
# are missing
|
37
41
|
#
|
38
|
-
# @param arguments [Array] positional arguments to assign
|
39
|
-
# @param keyword_arguments [Hash] keyword arguments to assign
|
42
|
+
# @param arguments [Array<Object>] positional arguments to assign
|
43
|
+
# @param keyword_arguments [Hash{Symbol => Object}] keyword arguments to assign
|
40
44
|
#
|
41
45
|
# @raise [ArgumentError] if required arguments are missing
|
42
46
|
# @return [void]
|
@@ -49,14 +53,14 @@ module Domainic
|
|
49
53
|
|
50
54
|
private
|
51
55
|
|
52
|
-
# Access to the current attribute set
|
56
|
+
# Access to the current attribute set
|
53
57
|
#
|
54
58
|
# @return [AttributeSet] the attribute set for this instance
|
55
59
|
attr_reader :attributes #: AttributeSet
|
56
60
|
|
57
|
-
# Apply positional arguments to their attributes
|
61
|
+
# Apply positional arguments to their attributes
|
58
62
|
#
|
59
|
-
# @param arguments [Array] the positional arguments to apply
|
63
|
+
# @param arguments [Array<Object>] the positional arguments to apply
|
60
64
|
#
|
61
65
|
# @return [void]
|
62
66
|
# @rbs (Array[untyped]) -> void
|
@@ -67,9 +71,9 @@ module Domainic
|
|
67
71
|
end
|
68
72
|
end
|
69
73
|
|
70
|
-
# Apply keyword arguments to their attributes
|
74
|
+
# Apply keyword arguments to their attributes
|
71
75
|
#
|
72
|
-
# @param options [Hash] the keyword options to apply
|
76
|
+
# @param options [Hash{Symbol => Object}] the keyword options to apply
|
73
77
|
#
|
74
78
|
# @return [void]
|
75
79
|
# @rbs (Hash[String | Symbol, untyped]) -> void
|
@@ -85,7 +89,7 @@ module Domainic
|
|
85
89
|
end
|
86
90
|
end
|
87
91
|
|
88
|
-
# Get all argument attributes
|
92
|
+
# Get all argument attributes
|
89
93
|
#
|
90
94
|
# @return [Array<Attribute>] the argument attributes
|
91
95
|
# @rbs () -> Array[Attribute]
|
@@ -93,17 +97,18 @@ module Domainic
|
|
93
97
|
@argument_attributes ||= attributes.select { |_, attribute| attribute.signature.argument? }.attributes
|
94
98
|
end
|
95
99
|
|
96
|
-
# Assign a value to an attribute
|
100
|
+
# Assign a value to an attribute
|
97
101
|
#
|
98
102
|
# @param attribute_name [Symbol] the name of the attribute
|
99
103
|
# @param value [Object] the value to assign
|
100
104
|
#
|
101
105
|
# @return [void]
|
106
|
+
# @rbs (String | Symbol attribute_name, untyped value) -> void
|
102
107
|
def assign_value(attribute_name, value)
|
103
108
|
@base.send(:"#{attribute_name}=", value)
|
104
109
|
end
|
105
110
|
|
106
|
-
# Get all option attributes
|
111
|
+
# Get all option attributes
|
107
112
|
#
|
108
113
|
# @return [Array<Attribute>] the option attributes
|
109
114
|
# @rbs () -> Array[Attribute]
|
@@ -111,9 +116,9 @@ module Domainic
|
|
111
116
|
@option_attributes ||= attributes.select { |_, attribute| attribute.signature.option? }.attributes
|
112
117
|
end
|
113
118
|
|
114
|
-
# Validate that all required positional arguments are provided
|
119
|
+
# Validate that all required positional arguments are provided
|
115
120
|
#
|
116
|
-
# @param arguments [Array] the arguments to validate
|
121
|
+
# @param arguments [Array<Object>] the arguments to validate
|
117
122
|
#
|
118
123
|
# @raise [ArgumentError] if required arguments are missing
|
119
124
|
# @return [void]
|
@@ -3,23 +3,25 @@
|
|
3
3
|
module Domainic
|
4
4
|
module Attributer
|
5
5
|
module DSL
|
6
|
-
# A class responsible for injecting attribute methods into classes
|
6
|
+
# A class responsible for injecting attribute methods into classes
|
7
7
|
#
|
8
8
|
# This class handles the creation of reader and writer methods for attributes,
|
9
9
|
# ensuring they are injected safely without overwriting existing methods. It
|
10
10
|
# respects visibility settings and properly handles value assignment through
|
11
|
-
# the attribute system
|
11
|
+
# the attribute system
|
12
12
|
#
|
13
|
+
# @api private
|
14
|
+
# @!visibility private
|
13
15
|
# @author {https://aaronmallen.me Aaron Allen}
|
14
16
|
# @since 0.1.0
|
15
17
|
class MethodInjector
|
16
18
|
# @rbs @attribute: Attribute
|
17
19
|
# @rbs @base: __todo__
|
18
20
|
|
19
|
-
# Inject methods for an attribute into a class
|
21
|
+
# Inject methods for an attribute into a class
|
20
22
|
#
|
21
23
|
# @param base [Class, Module] the class to inject methods into
|
22
|
-
# @param attribute [Attribute] the
|
24
|
+
# @param attribute [Attribute] the {Attribute} to create methods for
|
23
25
|
#
|
24
26
|
# @return [void]
|
25
27
|
# @rbs (__todo__ base, Attribute attribute) -> void
|
@@ -27,19 +29,19 @@ module Domainic
|
|
27
29
|
new(base, attribute).inject!
|
28
30
|
end
|
29
31
|
|
30
|
-
# Initialize a new MethodInjector
|
32
|
+
# Initialize a new MethodInjector
|
31
33
|
#
|
32
34
|
# @param base [Class, Module] the class to inject methods into
|
33
|
-
# @param attribute [Attribute] the
|
35
|
+
# @param attribute [Attribute] the {Attribute} to create methods for
|
34
36
|
#
|
35
|
-
# @return [
|
37
|
+
# @return [MethodInjector] the new MethodInjector instance
|
36
38
|
# @rbs (__todo__ base, Attribute attribute) -> void
|
37
39
|
def initialize(base, attribute)
|
38
40
|
@attribute = attribute
|
39
41
|
@base = base
|
40
42
|
end
|
41
43
|
|
42
|
-
# Inject reader and writer methods
|
44
|
+
# Inject reader and writer methods
|
43
45
|
#
|
44
46
|
# @return [void]
|
45
47
|
# @rbs () -> void
|
@@ -50,11 +52,11 @@ module Domainic
|
|
50
52
|
|
51
53
|
private
|
52
54
|
|
53
|
-
# Define a method if it doesn't already exist
|
55
|
+
# Define a method if it doesn't already exist
|
54
56
|
#
|
55
57
|
# @param method_name [Symbol] the name of the method to define
|
56
|
-
# @yield the method body to define
|
57
58
|
#
|
59
|
+
# @yield the method body to define
|
58
60
|
# @return [void]
|
59
61
|
# @rbs (Symbol method_name) { (?) [self: untyped] -> void } -> void
|
60
62
|
def define_safe_method(method_name, &)
|
@@ -63,9 +65,9 @@ module Domainic
|
|
63
65
|
@base.define_method(method_name, &)
|
64
66
|
end
|
65
67
|
|
66
|
-
# Inject the attribute reader method
|
68
|
+
# Inject the attribute reader method
|
67
69
|
#
|
68
|
-
# Creates a reader method with the configured visibility
|
70
|
+
# Creates a reader method with the configured visibility
|
69
71
|
#
|
70
72
|
# @return [void]
|
71
73
|
# @rbs () -> void
|
@@ -74,10 +76,10 @@ module Domainic
|
|
74
76
|
@base.send(@attribute.signature.read_visibility, @attribute.name)
|
75
77
|
end
|
76
78
|
|
77
|
-
# Inject the attribute writer method
|
79
|
+
# Inject the attribute writer method
|
78
80
|
#
|
79
81
|
# Creates a writer method that processes values through the attribute
|
80
|
-
# system before assignment. Sets the configured visibility
|
82
|
+
# system before assignment. Sets the configured visibility
|
81
83
|
#
|
82
84
|
# @return [void]
|
83
85
|
# @rbs () -> void
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/attributer/errors/error'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Attributer
|
7
|
+
# A specialized error class that combines multiple errors into a single error
|
8
|
+
#
|
9
|
+
# This class encapsulates multiple errors that occur during a single operation and
|
10
|
+
# need to be reported together. It maintains a list of individual errors while providing
|
11
|
+
# a formatted message that includes details from all errors
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
# @!visibility private
|
15
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
16
|
+
# @since 0.2.0
|
17
|
+
class AggregateError < Error
|
18
|
+
# Get the collection of errors that were aggregated
|
19
|
+
#
|
20
|
+
# @return [Array<StandardError>] the collection of errors
|
21
|
+
attr_reader :errors #: Array[StandardError]
|
22
|
+
|
23
|
+
# Initialize a new AggregateError instance
|
24
|
+
#
|
25
|
+
# @param message [String] the main error message
|
26
|
+
# @param errors [Array<StandardError>] the collection of errors to aggregate
|
27
|
+
#
|
28
|
+
# @return [AggregateError] the new AggregateError instance
|
29
|
+
# @rbs (String message, Array[StandardError] errors) -> void
|
30
|
+
def initialize(message, errors)
|
31
|
+
@errors = errors
|
32
|
+
super("#{message}\n#{errors.map { |error| " - #{error.message}" }.join("\n")}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/attributer/errors/aggregate_error'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Attributer
|
7
|
+
# A specialized error class for callback execution failures
|
8
|
+
#
|
9
|
+
# This error class is used when one or more callbacks fail during attribute value
|
10
|
+
# changes. It aggregates all callback-related errors into a single error with a
|
11
|
+
# descriptive message listing each individual failure
|
12
|
+
#
|
13
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
14
|
+
# @since 0.2.0
|
15
|
+
class CallbackExecutionError < AggregateError
|
16
|
+
# Initialize a new CallbackExecutionError instance
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
# @!visibility private
|
20
|
+
#
|
21
|
+
# @param errors [Array<StandardError>] the collection of callback errors to aggregate
|
22
|
+
#
|
23
|
+
# @return [CallbackExecutionError] the new CallbackExecutionError instance
|
24
|
+
# @rbs (Array[StandardError] errors) -> void
|
25
|
+
def initialize(errors)
|
26
|
+
super('The following errors occurred during callback execution:', errors)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/attributer/errors/error'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Attributer
|
7
|
+
# A specialized error class for coercion execution failures
|
8
|
+
#
|
9
|
+
# This error class is used when a coercion fails during attribute value
|
10
|
+
# processing. It captures the failing coercer to provide context about which
|
11
|
+
# step in the coercion chain caused the failure
|
12
|
+
#
|
13
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
14
|
+
# @since 0.2.0
|
15
|
+
class CoercionExecutionError < Error
|
16
|
+
# Get the coercer that failed
|
17
|
+
#
|
18
|
+
# @return [Proc, Symbol] the coercer that failed
|
19
|
+
attr_reader :coercer #: Attribute::Coercer::handler
|
20
|
+
|
21
|
+
# Initialize a new CoercionExecutionError instance
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
# @!visibility private
|
25
|
+
#
|
26
|
+
# @param message [String] the error message
|
27
|
+
# @param coercer [Proc, Symbol] the coercer that failed
|
28
|
+
#
|
29
|
+
# @return [CoercionExecutionError] the new CoercionExecutionError instance
|
30
|
+
# @rbs (String message, Attribute::Coercer::handler coercer) -> void
|
31
|
+
def initialize(message, coercer)
|
32
|
+
@coercer = coercer
|
33
|
+
super(message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domainic
|
4
|
+
module Attributer
|
5
|
+
# Base error class for all Attributer-related errors
|
6
|
+
#
|
7
|
+
# This class serves as the foundation for Attributer's error hierarchy, allowing
|
8
|
+
# for specific error types to be caught and handled appropriately. All custom
|
9
|
+
# errors within the Attributer system should inherit from this class to maintain
|
10
|
+
# a consistent error handling pattern
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @!visibility private
|
14
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
15
|
+
# @since 0.2.0
|
16
|
+
class Error < StandardError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/attributer/errors/aggregate_error'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Attributer
|
7
|
+
# A specialized error class for validation execution failures
|
8
|
+
#
|
9
|
+
# This error class is used when one or more validations encounter errors during
|
10
|
+
# their execution. It aggregates all validation-related errors into a single
|
11
|
+
# error with a descriptive message listing each individual failure
|
12
|
+
#
|
13
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
14
|
+
# @since 0.2.0
|
15
|
+
class ValidationExecutionError < AggregateError
|
16
|
+
# Initialize a new ValidationExecutionError instance
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
# @!visibility private
|
20
|
+
#
|
21
|
+
# @param errors [Array<StandardError>] the collection of validation errors to aggregate
|
22
|
+
#
|
23
|
+
# @return [ValidationExecutionError] the new ValidationExecutionError instance
|
24
|
+
# @rbs (Array[StandardError] errors) -> void
|
25
|
+
def initialize(errors)
|
26
|
+
super('The following errors occurred during validation execution:', errors)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -4,11 +4,11 @@ require 'domainic/attributer/dsl/initializer'
|
|
4
4
|
|
5
5
|
module Domainic
|
6
6
|
module Attributer
|
7
|
-
# A module providing instance-level attribute functionality
|
7
|
+
# A module providing instance-level attribute functionality
|
8
8
|
#
|
9
|
-
# This module defines instance methods for objects that include Domainic::Attributer.
|
9
|
+
# This module defines instance methods for objects that include {Domainic::Attributer}.
|
10
10
|
# It provides initialization handling and attribute serialization capabilities, making
|
11
|
-
# it easy to work with attribute values in a consistent way
|
11
|
+
# it easy to work with attribute values in a consistent way
|
12
12
|
#
|
13
13
|
# @example Basic usage
|
14
14
|
# class Person
|
@@ -25,7 +25,7 @@ module Domainic
|
|
25
25
|
# @author {https://aaronmallen.me Aaron Allen}
|
26
26
|
# @since 0.1.0
|
27
27
|
module InstanceMethods
|
28
|
-
# Initialize a new instance with attribute values
|
28
|
+
# Initialize a new instance with attribute values
|
29
29
|
#
|
30
30
|
# Handles both positional arguments and keyword options, applying them to their
|
31
31
|
# corresponding attributes. This process includes:
|
@@ -34,20 +34,23 @@ module Domainic
|
|
34
34
|
# 3. Type validation and coercion
|
35
35
|
# 4. Change notifications
|
36
36
|
#
|
37
|
+
# @example
|
38
|
+
# person = Person.new('Alice', age: 30)
|
39
|
+
#
|
37
40
|
# @raise [ArgumentError] if required arguments are missing
|
38
|
-
# @return [
|
41
|
+
# @return [InstanceMethods] the new InstanceMethods instance
|
39
42
|
# @rbs (*untyped arguments, **untyped keyword_arguments) -> void
|
40
43
|
def initialize(...)
|
41
44
|
DSL::Initializer.new(self).assign!(...)
|
42
45
|
end
|
43
46
|
|
44
|
-
# Convert public attribute values to a hash
|
47
|
+
# Convert public attribute values to a hash
|
45
48
|
#
|
46
49
|
# Creates a hash containing all public readable attributes and their current values.
|
47
50
|
# Any attributes marked as private or protected for reading are excluded from
|
48
|
-
# the result
|
51
|
+
# the result
|
49
52
|
#
|
50
|
-
# @example
|
53
|
+
# @example
|
51
54
|
# person = Person.new('Alice', age: 30)
|
52
55
|
# person.to_h # => { name: 'Alice', age: 30 }
|
53
56
|
#
|
@@ -2,38 +2,40 @@
|
|
2
2
|
|
3
3
|
module Domainic
|
4
4
|
module Attributer
|
5
|
-
# A singleton object representing an undefined value
|
5
|
+
# A singleton object representing an undefined value
|
6
6
|
#
|
7
|
-
# This object is used throughout Domainic::Attributer to represent values that
|
7
|
+
# This object is used throughout {Domainic::Attributer} to represent values that
|
8
8
|
# are explicitly undefined, as opposed to nil which represents the absence of
|
9
9
|
# a value. It is immutable and implements custom string representations for
|
10
|
-
# debugging purposes
|
10
|
+
# debugging purposes
|
11
11
|
#
|
12
|
+
# @api private
|
13
|
+
# @!visibility private
|
12
14
|
# @author {https://aaronmallen.me Aaron Allen}
|
13
15
|
# @since 0.1.0
|
14
16
|
Undefined = Object.new.tap do |undefined|
|
15
|
-
#
|
17
|
+
# Prevent cloning of the singleton
|
16
18
|
#
|
17
19
|
# @return [Undefined] self
|
18
20
|
def undefined.clone(...)
|
19
21
|
self
|
20
22
|
end
|
21
23
|
|
22
|
-
#
|
24
|
+
# Prevent duplication of the singleton
|
23
25
|
#
|
24
26
|
# @return [Undefined] self
|
25
27
|
def undefined.dup
|
26
28
|
self
|
27
29
|
end
|
28
30
|
|
29
|
-
#
|
31
|
+
# Get a string representation of the object
|
30
32
|
#
|
31
33
|
# @return [String] the string 'Undefined'
|
32
34
|
def undefined.inspect
|
33
35
|
to_s
|
34
36
|
end
|
35
37
|
|
36
|
-
#
|
38
|
+
# Convert the object to a string
|
37
39
|
#
|
38
40
|
# @return [String] the string 'Undefined'
|
39
41
|
def undefined.to_s
|
data/lib/domainic/attributer.rb
CHANGED
@@ -8,38 +8,64 @@ require 'domainic/attributer/instance_methods'
|
|
8
8
|
require 'domainic/attributer/undefined'
|
9
9
|
|
10
10
|
module Domainic
|
11
|
-
#
|
11
|
+
# `Domainic::Attributer` is a powerful toolkit that brings clarity and safety to your Ruby class attributes.
|
12
|
+
# Ever wished your class attributes could:
|
12
13
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
14
|
+
# * Validate themselves to ensure they only accept correct values?
|
15
|
+
# * Transform input data automatically into the right format?
|
16
|
+
# * Have clear, enforced visibility rules?
|
17
|
+
# * Handle their own default values intelligently?
|
18
|
+
# * Tell you when they change?
|
19
|
+
# * Distinguish between required arguments and optional settings?
|
16
20
|
#
|
17
|
-
#
|
21
|
+
# That's exactly what `Domainic::Attributer` does! It provides a declarative way to define and manage attributes
|
22
|
+
# in your Ruby classes, ensuring data integrity and clear interfaces. It's particularly valuable for:
|
18
23
|
#
|
19
|
-
#
|
20
|
-
#
|
24
|
+
# * Domain models and value objects
|
25
|
+
# * Service objects and command patterns
|
26
|
+
# * Configuration objects
|
27
|
+
# * Any class where attribute behavior matters
|
28
|
+
#
|
29
|
+
# Think of it as giving your attributes a brain - they know what they want, how they should behave, and
|
30
|
+
# they're not afraid to speak up when something's not right!
|
31
|
+
#
|
32
|
+
# @see file:docs/USAGE.md Usage Guide
|
33
|
+
# @abstract Can be included directly with default method names or customized via {.Attributer}
|
34
|
+
#
|
35
|
+
# @example Basic usage
|
36
|
+
# class SuperDev
|
21
37
|
# include Domainic::Attributer
|
22
38
|
#
|
23
|
-
# argument :
|
24
|
-
#
|
39
|
+
# argument :code_name, String
|
40
|
+
#
|
41
|
+
# option :power_level, Integer, default: 9000
|
42
|
+
# option :favorite_gem do
|
43
|
+
# validate_with ->(val) { val.to_s.end_with?('ruby') }
|
44
|
+
# coerce_with ->(val) { val.to_s.downcase }
|
45
|
+
# non_nilable
|
46
|
+
# end
|
25
47
|
# end
|
26
48
|
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# include Domainic.Attributer(argument: :param, option: :opt)
|
49
|
+
# dev = SuperDev.new('RubyNinja', favorite_gem: 'RAILS_RUBY')
|
50
|
+
# # => #<SuperDev:0x00000001083aeb58 @code_name="RubyNinja", @favorite_gem="rails_ruby", @power_level=9000>
|
30
51
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
52
|
+
# dev.favorite_gem # => "rails_ruby"
|
53
|
+
# dev.power_level = 9001 # => 9001
|
54
|
+
# dev.power_level = 'over 9000'
|
55
|
+
# # `SuperDev#power_level`: has invalid value: "over 9000" (ArgumentError)
|
34
56
|
#
|
35
57
|
# @author {https://aaronmallen.me Aaron Allen}
|
36
58
|
# @since 0.1.0
|
37
59
|
module Attributer
|
38
60
|
class << self
|
39
|
-
# Create a customized Attributer module
|
61
|
+
# Create a customized Attributer module
|
62
|
+
#
|
63
|
+
# @!visibility private
|
64
|
+
# @api private
|
40
65
|
#
|
41
66
|
# @param argument [Symbol, String] custom name for the argument method
|
42
67
|
# @param option [Symbol, String] custom name for the option method
|
68
|
+
#
|
43
69
|
# @return [Module] configured Attributer module
|
44
70
|
# @rbs (?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> Module
|
45
71
|
def call(argument: :argument, option: :option)
|
@@ -55,9 +81,13 @@ module Domainic
|
|
55
81
|
end
|
56
82
|
end
|
57
83
|
|
58
|
-
# Handle direct module inclusion
|
84
|
+
# Handle direct module inclusion
|
85
|
+
#
|
86
|
+
# @!visibility private
|
87
|
+
# @api private
|
59
88
|
#
|
60
89
|
# @param base [Class, Module] the including class/module
|
90
|
+
#
|
61
91
|
# @return [void]
|
62
92
|
# @rbs (untyped base) -> void
|
63
93
|
def included(base)
|
@@ -67,10 +97,11 @@ module Domainic
|
|
67
97
|
|
68
98
|
private
|
69
99
|
|
70
|
-
# Configure base class with Attributer functionality
|
100
|
+
# Configure base class with Attributer functionality
|
71
101
|
#
|
72
102
|
# @param base [Class, Module] the target class/module
|
73
|
-
# @param options [Hash] method name customization options
|
103
|
+
# @param options [Hash{Symbol => String, Symbol}] method name customization options
|
104
|
+
#
|
74
105
|
# @return [void]
|
75
106
|
# @rbs (untyped base, ?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> void
|
76
107
|
def include_attributer(base, **options)
|
@@ -79,10 +110,11 @@ module Domainic
|
|
79
110
|
inject_custom_methods!(base, **options)
|
80
111
|
end
|
81
112
|
|
82
|
-
# Set up custom method names
|
113
|
+
# Set up custom method names
|
83
114
|
#
|
84
115
|
# @param base [Class, Module] the target class/module
|
85
|
-
# @param options [Hash] method name customization options
|
116
|
+
# @param options [Hash{Symbol => String, Symbol}] method name customization options
|
117
|
+
#
|
86
118
|
# @return [void]
|
87
119
|
# @rbs (untyped base, ?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> void
|
88
120
|
def inject_custom_methods!(base, **options)
|
@@ -96,17 +128,46 @@ module Domainic
|
|
96
128
|
end
|
97
129
|
end
|
98
130
|
|
99
|
-
#
|
100
|
-
#
|
101
|
-
# Provides a convenient way to include Attributer with customized method names.
|
131
|
+
# Provides a convenient way to include {Attributer} with customized method names
|
102
132
|
#
|
103
|
-
# @example
|
133
|
+
# @example Customizing method names
|
104
134
|
# class Person
|
105
135
|
# include Domainic.Attributer(argument: :param, option: :opt)
|
136
|
+
#
|
137
|
+
# param :name, String
|
138
|
+
# opt :age, Integer
|
106
139
|
# end
|
107
140
|
#
|
108
|
-
#
|
109
|
-
#
|
141
|
+
# Person.respond_to?(:argument) # => false
|
142
|
+
# Person.respond_to?(:param) # => true
|
143
|
+
# Person.respond_to?(:option) # => false
|
144
|
+
# Person.respond_to?(:opt) # => true
|
145
|
+
#
|
146
|
+
# person = Person.new('Alice', age: 30)
|
147
|
+
# # => #<Person:0x000000010865d188 @age=30, @name="Alice">
|
148
|
+
#
|
149
|
+
# @example Turning off a method
|
150
|
+
#
|
151
|
+
# class Person
|
152
|
+
# include Domainic.Attributer(argument: nil)
|
153
|
+
#
|
154
|
+
# option :name, String
|
155
|
+
# option :age, Integer
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# Person.respond_to?(:argument) # => false
|
159
|
+
# Person.respond_to?(:option) # => true
|
160
|
+
#
|
161
|
+
# person = Person.new(name: 'Alice', age: 30)
|
162
|
+
# # => #<Person:0x000000010865d188 @age=30, @name="Alice">
|
163
|
+
#
|
164
|
+
# @param options [Hash{Symbol => String, Symbol, nil}] method name customization options
|
165
|
+
# @option options [String, Symbol, nil] :argument custom name for the {Attributer::ClassMethods#argument argument}
|
166
|
+
# method. Set to `nil` to disable the method entirely
|
167
|
+
# @option options [String, Symbol, nil] :option custom name for the {Attributer::ClassMethods#option option}
|
168
|
+
# method. Set to `nil` to disable the method entirely
|
169
|
+
#
|
170
|
+
# @return [Module] the configured {Attributer} module
|
110
171
|
# @rbs (?argument: (String | Symbol)?, ?option: (String | Symbol)?) -> Module
|
111
172
|
def self.Attributer(**options) # rubocop:disable Naming/MethodName
|
112
173
|
Domainic::Attributer.call(**options)
|