domainic-attributer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|