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,11 +1,9 @@
|
|
1
1
|
module Domainic
|
2
2
|
module Attributer
|
3
3
|
module DSL
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# default values, coercion, validation, visibility controls, and change tracking.
|
8
|
-
# It uses method chaining to allow natural, declarative attribute definitions.
|
4
|
+
# This class provides a rich DSL for configuring attributes with support for default values, coercion, validation,
|
5
|
+
# visibility controls, and change tracking. It uses method chaining to allow natural, declarative attribute
|
6
|
+
# definitions
|
9
7
|
#
|
10
8
|
# @author {https://aaronmallen.me Aaron Allen}
|
11
9
|
# @since 0.1.0
|
@@ -14,37 +12,146 @@ module Domainic
|
|
14
12
|
|
15
13
|
@options: OptionParser::result
|
16
14
|
|
17
|
-
# Initialize a new AttributeBuilder
|
15
|
+
# Initialize a new AttributeBuilder
|
18
16
|
#
|
19
17
|
# @param base [Class, Module] the class or module to build the attribute in
|
20
18
|
# @param attribute_name [String, Symbol] the name of the attribute
|
21
19
|
# @param attribute_type [String, Symbol] the type of attribute
|
22
20
|
# @param type_validator [Proc, Object, nil] optional type validator
|
23
|
-
# @param options [Hash] additional options for attribute configuration
|
24
|
-
#
|
21
|
+
# @param options [Hash{Symbol => Object}] additional options for attribute configuration. See
|
22
|
+
# {OptionParser#initialize} for details
|
25
23
|
#
|
26
|
-
# @return [
|
24
|
+
# @return [AttributeBuilder] the new AttributeBuilder instance
|
27
25
|
def initialize: (__todo__ base, String | Symbol attribute_name, String | Symbol attribute_type, ?Attribute::Validator::handler? type_validator, OptionParser::options options) ?{ (?) [self: AttributeBuilder] -> void } -> void
|
28
26
|
|
29
|
-
#
|
27
|
+
# Build and finalize the {Attribute}
|
28
|
+
#
|
29
|
+
# @!visibility private
|
30
|
+
# @api private
|
30
31
|
#
|
31
32
|
# @return [Attribute] the configured attribute
|
32
33
|
def build!: () -> Attribute
|
33
34
|
|
34
|
-
#
|
35
|
+
# Provides a way to automatically transform attribute values into the desired format or type. Coercion ensures
|
36
|
+
# input values conform to the expected structure by applying one or more handlers. Handlers can be Procs,
|
37
|
+
# lambdas, or method symbols.
|
38
|
+
#
|
39
|
+
# Coercions are applied during initialization or whenever the attribute value is updated.
|
40
|
+
#
|
41
|
+
# @note When coercion is used with nilable attributes, handlers should account for `nil` values appropriately.
|
42
|
+
#
|
43
|
+
# @example Simple coercion
|
44
|
+
# class Superhero
|
45
|
+
# include Domainic::Attributer
|
46
|
+
#
|
47
|
+
# argument :code_name do
|
48
|
+
# coerce_with ->(val) { val.to_s.upcase }
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# hero = Superhero.new("spiderman")
|
53
|
+
# hero.code_name # => "SPIDERMAN"
|
54
|
+
#
|
55
|
+
# @example Multiple coercions
|
56
|
+
# class Superhero
|
57
|
+
# include Domainic::Attributer
|
58
|
+
#
|
59
|
+
# option :power_level do
|
60
|
+
# coerce_with ->(val) { val.to_s } # Convert to string
|
61
|
+
# coerce_with do |val| # Remove non-digits
|
62
|
+
# val.gsub(/\D/, '')
|
63
|
+
# end
|
64
|
+
# coerce_with ->(val) { val.to_i } # Convert to integer
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# hero = Superhero.new(power_level: "over 9000!")
|
69
|
+
# hero.power_level # => 9000
|
70
|
+
#
|
71
|
+
# @example Coercion with an instance method
|
72
|
+
# class Superhero
|
73
|
+
# include Domainic::Attributer
|
74
|
+
#
|
75
|
+
# option :alias_name do
|
76
|
+
# coerce_with :format_alias
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# private
|
80
|
+
#
|
81
|
+
# def format_alias(value)
|
82
|
+
# value.to_s.downcase.split.map(&:capitalize).join(' ')
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# hero = Superhero.new(alias_name: "ironMAN")
|
87
|
+
# hero.alias_name # => "Ironman"
|
35
88
|
#
|
36
89
|
# @param proc_symbol [Proc, Symbol, nil] optional coercion handler
|
37
90
|
# @yield optional coercion block
|
91
|
+
# @yieldparam value [Object] the value to coerce
|
92
|
+
# @yieldreturn [Object] the coerced value
|
38
93
|
#
|
39
94
|
# @return [self] the builder for method chaining
|
40
95
|
def coerce_with: (?(Attribute::Coercer::proc | Object)? proc_symbol) ?{ (untyped value) -> untyped } -> self
|
41
96
|
|
42
97
|
alias coerce coerce_with
|
43
98
|
|
44
|
-
#
|
99
|
+
# Provides a way to assign default values to attributes. These values can be static or dynamically generated
|
100
|
+
# using a block. The default value is only applied when no explicit value is provided for the attribute
|
101
|
+
#
|
102
|
+
# @example Static default values
|
103
|
+
# class RPGCharacter
|
104
|
+
# include Domainic::Attributer
|
105
|
+
#
|
106
|
+
# option :level, Integer do
|
107
|
+
# default 1
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# option :health_max do
|
111
|
+
# default 100
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# hero = RPGCharacter.new
|
116
|
+
# hero.level # => 1
|
117
|
+
# hero.health_max # => 100
|
118
|
+
#
|
119
|
+
# @example Dynamic default values
|
120
|
+
# class RPGCharacter
|
121
|
+
# include Domainic::Attributer
|
122
|
+
#
|
123
|
+
# option :created_at do
|
124
|
+
# default { Time.now }
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# option :health_current do
|
128
|
+
# default { health_max }
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# hero = RPGCharacter.new
|
133
|
+
# hero.created_at # => Current timestamp
|
134
|
+
# hero.health_current # => Defaults to the value of `health_max`
|
135
|
+
#
|
136
|
+
# @example Complex dynamic default values
|
137
|
+
# class RPGCharacter
|
138
|
+
# include Domainic::Attributer
|
139
|
+
#
|
140
|
+
# option :inventory do
|
141
|
+
# default do
|
142
|
+
# base_items = ['Health Potion', 'Map']
|
143
|
+
# base_items << 'Lucky Coin' if Random.rand < 0.1
|
144
|
+
# base_items
|
145
|
+
# end
|
146
|
+
# end
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# hero = RPGCharacter.new
|
150
|
+
# hero.inventory # => ["Health Potion", "Map"] or ["Health Potion", "Map", "Lucky Coin"]
|
45
151
|
#
|
46
152
|
# @param value_or_proc [Object, Proc, nil] optional default value or generator
|
47
153
|
# @yield optional default value generator block
|
154
|
+
# @yieldreturn [Object] the default value
|
48
155
|
#
|
49
156
|
# @return [self] the builder for method chaining
|
50
157
|
def default: (?untyped? value_or_proc) ?{ (?) -> untyped } -> self
|
@@ -53,7 +160,33 @@ module Domainic
|
|
53
160
|
|
54
161
|
alias default_value default
|
55
162
|
|
56
|
-
#
|
163
|
+
# Provides a way to add descriptive metadata to attributes. Descriptions improve code clarity by documenting
|
164
|
+
# the purpose or behavior of an attribute. These descriptions can be short or detailed, depending on the
|
165
|
+
# context.
|
166
|
+
#
|
167
|
+
# @note Descriptions are optional but highly recommended for improving readability and maintainability of the
|
168
|
+
# code.
|
169
|
+
#
|
170
|
+
# @example Adding a short description
|
171
|
+
# class MagicItem
|
172
|
+
# include Domainic::Attributer
|
173
|
+
#
|
174
|
+
# argument :name do
|
175
|
+
# desc 'The name of the magic item, must be unique'
|
176
|
+
# end
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# @example Adding a detailed description
|
180
|
+
# class MagicItem
|
181
|
+
# include Domainic::Attributer
|
182
|
+
#
|
183
|
+
# option :power_level do
|
184
|
+
# description 'The magical power level of the item, ranging from 0 to 100.
|
185
|
+
# Higher power levels increase effectiveness but may come with
|
186
|
+
# increased risks during use.'
|
187
|
+
# validate_with ->(val) { val.between?(0, 100) }
|
188
|
+
# end
|
189
|
+
# end
|
57
190
|
#
|
58
191
|
# @param text [String] the description text
|
59
192
|
#
|
@@ -62,7 +195,33 @@ module Domainic
|
|
62
195
|
|
63
196
|
alias desc description
|
64
197
|
|
65
|
-
#
|
198
|
+
# Ensures that an attribute defined in the block DSL cannot have a `nil` value. This validation is enforced
|
199
|
+
# during initialization and when modifying the attribute value at runtime. Use `non_nilable` for attributes that
|
200
|
+
# must always have a value.
|
201
|
+
#
|
202
|
+
# @example Preventing `nil` values for an attribute
|
203
|
+
# class Ninja
|
204
|
+
# include Domainic::Attributer
|
205
|
+
#
|
206
|
+
# argument :code_name do
|
207
|
+
# non_nilable
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
#
|
211
|
+
# Ninja.new(nil) # Raises ArgumentError: nil value is not allowed
|
212
|
+
#
|
213
|
+
# @example Combining `non_nilable` with other features
|
214
|
+
# class Ninja
|
215
|
+
# include Domainic::Attributer
|
216
|
+
#
|
217
|
+
# argument :rank do
|
218
|
+
# desc 'The rank of the ninja, such as Genin or Chunin'
|
219
|
+
# non_nilable
|
220
|
+
# end
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# ninja = Ninja.new('Genin') # => Works
|
224
|
+
# ninja.rank = nil # Raises ArgumentError: nil value is not allowed
|
66
225
|
#
|
67
226
|
# @return [self] the builder for method chaining
|
68
227
|
def non_nilable: () -> self
|
@@ -81,68 +240,398 @@ module Domainic
|
|
81
240
|
|
82
241
|
alias not_nullable non_nilable
|
83
242
|
|
84
|
-
#
|
243
|
+
# Allows defining a callback to be triggered whenever the attribute's value changes. The callback receives the
|
244
|
+
# old value and the new value as arguments, enabling custom logic to be executed on changes. Use `on_change` to
|
245
|
+
# react to changes in attribute values, such as updating dependent attributes or triggering side effects.
|
246
|
+
#
|
247
|
+
# @example Reacting to changes in an attribute
|
248
|
+
# class VideoGame
|
249
|
+
# include Domainic::Attributer
|
250
|
+
#
|
251
|
+
# option :health do
|
252
|
+
# default 100
|
253
|
+
# on_change ->(old_value, new_value) {
|
254
|
+
# puts "Health changed from #{old_value} to #{new_value}"
|
255
|
+
# }
|
256
|
+
# end
|
257
|
+
# end
|
258
|
+
#
|
259
|
+
# game = VideoGame.new
|
260
|
+
# game.health = 50 # Outputs: Health changed from 100 to 50
|
261
|
+
#
|
262
|
+
# @example Performing complex logic on change
|
263
|
+
# class VideoGame
|
264
|
+
# include Domainic::Attributer
|
265
|
+
#
|
266
|
+
# option :power_ups do
|
267
|
+
# default []
|
268
|
+
# on_change do |old_value, new_value|
|
269
|
+
# new_items = new_value - old_value
|
270
|
+
# lost_items = old_value - new_value
|
271
|
+
#
|
272
|
+
# new_items.each { |item| activate_power_up(item) }
|
273
|
+
# lost_items.each { |item| deactivate_power_up(item) }
|
274
|
+
# end
|
275
|
+
# end
|
276
|
+
#
|
277
|
+
# private
|
278
|
+
#
|
279
|
+
# def activate_power_up(item)
|
280
|
+
# puts "Activated power-up: #{item}"
|
281
|
+
# end
|
282
|
+
#
|
283
|
+
# def deactivate_power_up(item)
|
284
|
+
# puts "Deactivated power-up: #{item}"
|
285
|
+
# end
|
286
|
+
# end
|
287
|
+
#
|
288
|
+
# game = VideoGame.new
|
289
|
+
# game.power_ups = ['Shield', 'Speed Boost']
|
290
|
+
# # Outputs: Activated power-up: Shield
|
291
|
+
# # Activated power-up: Speed Boost
|
292
|
+
# game.power_ups = ['Shield']
|
293
|
+
# # Outputs: Deactivated power-up: Speed Boost
|
85
294
|
#
|
86
295
|
# @param proc [Proc, nil] optional callback handler
|
87
296
|
# @yield optional callback block
|
297
|
+
# @yieldparam old_value [Object] the previous value of the attribute
|
298
|
+
# @yieldparam new_value [Object] the new value of the attribute
|
299
|
+
# @yieldreturn [void]
|
88
300
|
#
|
89
301
|
# @return [self] the builder for method chaining
|
90
302
|
def on_change: (?Attribute::Callback::handler? proc) ?{ (untyped old_value, untyped new_value) -> void } -> self
|
91
303
|
|
92
|
-
#
|
304
|
+
# Sets both the read and write visibility of an attribute to private. This ensures the attribute can only be
|
305
|
+
# accessed or modified within the class itself.
|
306
|
+
#
|
307
|
+
# @example Making an attribute private
|
308
|
+
# class SecretAgent
|
309
|
+
# include Domainic::Attributer
|
310
|
+
#
|
311
|
+
# option :real_name do
|
312
|
+
# desc 'The real name of the agent, hidden from external access.'
|
313
|
+
# private
|
314
|
+
# end
|
315
|
+
# end
|
316
|
+
#
|
317
|
+
# agent = SecretAgent.new(real_name: 'James Bond')
|
318
|
+
# agent.real_name # Raises NoMethodError: private method `real_name' called for #<SecretAgent>
|
319
|
+
# agent.real_name = 'John Doe' # Raises NoMethodError: private method `real_name=' called for #<SecretAgent>
|
93
320
|
#
|
94
321
|
# @return [self] the builder for method chaining
|
95
322
|
def private: () -> self
|
96
323
|
|
97
|
-
#
|
324
|
+
# Sets the read visibility of an attribute to private, allowing the attribute to be read only within the class
|
325
|
+
# itself. The write visibility remains unchanged unless explicitly modified. Use `private_read` when the value
|
326
|
+
# of an attribute should be hidden from external consumers but writable by external code if needed.
|
327
|
+
#
|
328
|
+
# @example Making the reader private
|
329
|
+
# class SecretAgent
|
330
|
+
# include Domainic::Attributer
|
331
|
+
#
|
332
|
+
# option :mission_code do
|
333
|
+
# desc 'The secret mission code, readable only within the class.'
|
334
|
+
# private_read
|
335
|
+
# default { generate_code }
|
336
|
+
# end
|
337
|
+
#
|
338
|
+
# private
|
339
|
+
#
|
340
|
+
# def generate_code
|
341
|
+
# "M-#{rand(1000..9999)}"
|
342
|
+
# end
|
343
|
+
# end
|
344
|
+
#
|
345
|
+
# agent = SecretAgent.new
|
346
|
+
# agent.mission_code # Raises NoMethodError: private method `mission_code' called for #<SecretAgent>
|
347
|
+
# agent.mission_code = 'Override Code' # Works, as write visibility is still public
|
98
348
|
#
|
99
349
|
# @return [self] the builder for method chaining
|
100
350
|
def private_read: () -> self
|
101
351
|
|
102
|
-
#
|
352
|
+
# Sets the write visibility of an attribute to private, allowing the attribute to be modified only within the
|
353
|
+
# class. The read visibility remains unchanged unless explicitly modified. Use `private_write` to ensure that an
|
354
|
+
# attribute's value can only be updated internally, while still allowing external code to read its value if
|
355
|
+
# needed.
|
356
|
+
#
|
357
|
+
# @example Making the writer private
|
358
|
+
# class SecretAgent
|
359
|
+
# include Domainic::Attributer
|
360
|
+
#
|
361
|
+
# option :mission_code do
|
362
|
+
# desc 'The secret mission code, writable only within the class.'
|
363
|
+
# private_write
|
364
|
+
# default { generate_code }
|
365
|
+
# end
|
366
|
+
#
|
367
|
+
# private
|
368
|
+
#
|
369
|
+
# def generate_code
|
370
|
+
# "M-#{rand(1000..9999)}"
|
371
|
+
# end
|
372
|
+
# end
|
373
|
+
#
|
374
|
+
# agent = SecretAgent.new
|
375
|
+
# agent.mission_code # => "M-1234"
|
376
|
+
# agent.mission_code = '007' # Raises NoMethodError: private method `mission_code=' called for #<SecretAgent>
|
103
377
|
#
|
104
378
|
# @return [self] the builder for method chaining
|
105
379
|
def private_write: () -> self
|
106
380
|
|
107
|
-
#
|
381
|
+
# Sets both the read and write visibility of an attribute to protected, allowing access only within the class
|
382
|
+
# and its subclasses. This visibility restricts external access entirely. Use `protected` to share attributes
|
383
|
+
# within a class hierarchy while keeping them hidden from external consumers.
|
384
|
+
#
|
385
|
+
# @example Defining a protected attribute
|
386
|
+
# class SecretAgent
|
387
|
+
# include Domainic::Attributer
|
388
|
+
#
|
389
|
+
# option :mission_code do
|
390
|
+
# protected
|
391
|
+
# description 'The mission code, accessible only within the class and its subclasses.'
|
392
|
+
# end
|
393
|
+
# end
|
394
|
+
#
|
395
|
+
# class DoubleAgent < SecretAgent
|
396
|
+
# def reveal_code
|
397
|
+
# self.mission_code
|
398
|
+
# end
|
399
|
+
# end
|
400
|
+
#
|
401
|
+
# agent = SecretAgent.new(mission_code: '007')
|
402
|
+
# agent.mission_code # Raises NoMethodError
|
403
|
+
# DoubleAgent.new.reveal_code # => '007'
|
108
404
|
#
|
109
405
|
# @return [self] the builder for method chaining
|
110
406
|
def protected: () -> self
|
111
407
|
|
112
|
-
#
|
408
|
+
# Sets both the read and write visibility of an attribute to protected. This allows the attribute to be accessed
|
409
|
+
# or modified only within the class and its subclasses. Use `protected` for attributes that should be accessible
|
410
|
+
# to the class and its subclasses but hidden from external consumers.
|
411
|
+
#
|
412
|
+
# @example Making an attribute protected
|
413
|
+
# class SecretAgent
|
414
|
+
# include Domainic::Attributer
|
415
|
+
#
|
416
|
+
# option :mission_code do
|
417
|
+
# desc 'The mission code, accessible only within the class or subclasses.'
|
418
|
+
# protected
|
419
|
+
# end
|
420
|
+
# end
|
421
|
+
#
|
422
|
+
# class DoubleAgent < SecretAgent
|
423
|
+
# def reveal_code
|
424
|
+
# self.mission_code
|
425
|
+
# end
|
426
|
+
# end
|
427
|
+
#
|
428
|
+
# agent = SecretAgent.new(mission_code: '007')
|
429
|
+
# agent.mission_code # Raises NoMethodError
|
430
|
+
# DoubleAgent.new.reveal_code # => '007'
|
113
431
|
#
|
114
432
|
# @return [self] the builder for method chaining
|
115
433
|
def protected_read: () -> self
|
116
434
|
|
117
|
-
#
|
435
|
+
# Sets both the read and write visibility of an attribute to protected. This allows the attribute to be accessed
|
436
|
+
# or modified only within the class and its subclasses. Use `protected` for attributes that should be accessible
|
437
|
+
# to the class and its subclasses but hidden from external consumers.
|
438
|
+
#
|
439
|
+
# @example Making an attribute protected
|
440
|
+
# class SecretAgent
|
441
|
+
# include Domainic::Attributer
|
442
|
+
#
|
443
|
+
# option :mission_code do
|
444
|
+
# desc 'The mission code, accessible only within the class or subclasses.'
|
445
|
+
# protected
|
446
|
+
# end
|
447
|
+
# end
|
448
|
+
#
|
449
|
+
# class DoubleAgent < SecretAgent
|
450
|
+
# def reveal_code
|
451
|
+
# self.mission_code
|
452
|
+
# end
|
453
|
+
# end
|
454
|
+
#
|
455
|
+
# agent = SecretAgent.new(mission_code: '007')
|
456
|
+
# agent.mission_code # Raises NoMethodError
|
457
|
+
# DoubleAgent.new.reveal_code # => '007'
|
118
458
|
#
|
119
459
|
# @return [self] the builder for method chaining
|
120
460
|
def protected_write: () -> self
|
121
461
|
|
122
|
-
#
|
462
|
+
# Explicitly sets both the read and write visibility of an attribute to public, overriding any inherited or
|
463
|
+
# previously set visibility. By default, attributes are public, so this is typically used to revert an
|
464
|
+
# attribute's visibility if it was changed in a parent class or module.
|
465
|
+
#
|
466
|
+
# @note Attributes are public by default. Use `public` explicitly to override inherited or modified visibility.
|
467
|
+
#
|
468
|
+
# @example Reverting visibility to public in a subclass
|
469
|
+
# class SecretAgent
|
470
|
+
# include Domainic::Attributer
|
471
|
+
#
|
472
|
+
# option :mission_code do
|
473
|
+
# desc 'The mission code, protected in the base class.'
|
474
|
+
# private
|
475
|
+
# end
|
476
|
+
# end
|
477
|
+
#
|
478
|
+
# class FieldAgent < SecretAgent
|
479
|
+
# option :mission_code do
|
480
|
+
# desc 'The mission code, made public in the subclass.'
|
481
|
+
# public
|
482
|
+
# end
|
483
|
+
# end
|
484
|
+
#
|
485
|
+
# agent = FieldAgent.new(mission_code: '007')
|
486
|
+
# agent.mission_code # => '007' (now accessible)
|
487
|
+
# agent.mission_code = '008' # Works, as visibility is public in the subclass
|
123
488
|
#
|
124
489
|
# @return [self] the builder for method chaining
|
125
490
|
def public: () -> self
|
126
491
|
|
127
|
-
#
|
492
|
+
# Explicitly sets the read visibility of an attribute to public, overriding any inherited or previously set
|
493
|
+
# visibility. By default, attributes are readable publicly, so this is typically used to revert the read
|
494
|
+
# visibility of an attribute if it was modified in a parent class or module.
|
495
|
+
#
|
496
|
+
# @note Attributes are publicly readable by default. Use `public_read` explicitly to override inherited or
|
497
|
+
# modified visibility.
|
498
|
+
#
|
499
|
+
# @example Reverting read visibility to public in a subclass
|
500
|
+
# class SecretAgent
|
501
|
+
# include Domainic::Attributer
|
502
|
+
#
|
503
|
+
# option :mission_code do
|
504
|
+
# desc 'The mission code, privately readable in the base class.'
|
505
|
+
# private_read
|
506
|
+
# end
|
507
|
+
# end
|
508
|
+
#
|
509
|
+
# class FieldAgent < SecretAgent
|
510
|
+
# option :mission_code do
|
511
|
+
# desc 'The mission code, made publicly readable in the subclass.'
|
512
|
+
# public_read
|
513
|
+
# end
|
514
|
+
# end
|
515
|
+
#
|
516
|
+
# agent = FieldAgent.new(mission_code: '007')
|
517
|
+
# agent.mission_code # => '007' (now publicly readable)
|
518
|
+
# agent.mission_code = '008' # Raises NoMethodError, as write visibility is still private
|
128
519
|
#
|
129
520
|
# @return [self] the builder for method chaining
|
130
521
|
def public_read: () -> self
|
131
522
|
|
132
|
-
#
|
523
|
+
# Explicitly sets the write visibility of an attribute to public, overriding any inherited or previously set
|
524
|
+
# visibility. By default, attributes are writable publicly, so this is typically used to revert the write
|
525
|
+
# visibility of an attribute if it was modified in a parent class or module.
|
526
|
+
#
|
527
|
+
# @note Attributes are publicly writable by default. Use `public_write` explicitly to override inherited or
|
528
|
+
# modified visibility.
|
529
|
+
#
|
530
|
+
# @example Reverting write visibility to public in a subclass
|
531
|
+
# class SecretAgent
|
532
|
+
# include Domainic::Attributer
|
533
|
+
#
|
534
|
+
# option :mission_code do
|
535
|
+
# desc 'The mission code, writable only within the class or subclasses in the base class.'
|
536
|
+
# private_write
|
537
|
+
# end
|
538
|
+
# end
|
539
|
+
#
|
540
|
+
# class FieldAgent < SecretAgent
|
541
|
+
# option :mission_code do
|
542
|
+
# desc 'The mission code, now writable publicly in the subclass.'
|
543
|
+
# public_write
|
544
|
+
# end
|
545
|
+
# end
|
546
|
+
#
|
547
|
+
# agent = FieldAgent.new(mission_code: '007')
|
548
|
+
# agent.mission_code # Raises NoMethodError, as read visibility remains restricted
|
549
|
+
# agent.mission_code = '008' # Works, as write visibility is now public
|
133
550
|
#
|
134
551
|
# @return [self] the builder for method chaining
|
135
552
|
def public_write: () -> self
|
136
553
|
|
137
|
-
#
|
554
|
+
# Marks an {ClassMethods#option option} attribute as required, ensuring that a value must be provided during
|
555
|
+
# initialization. If a required attribute is not supplied, an error is raised. Use `required` to enforce
|
556
|
+
# mandatory attributes.
|
557
|
+
#
|
558
|
+
# @note required options are enforced during initialization; as long as the option is provided
|
559
|
+
# (even if it is `nil`) no error will be raised.
|
560
|
+
#
|
561
|
+
# @example Defining a required attribute
|
562
|
+
# class Superhero
|
563
|
+
# include Domainic::Attributer
|
564
|
+
#
|
565
|
+
# option :name do
|
566
|
+
# desc 'The name of the superhero, which must be provided.'
|
567
|
+
# required
|
568
|
+
# end
|
569
|
+
# end
|
570
|
+
#
|
571
|
+
# Superhero.new # Raises ArgumentError: missing required attribute: name
|
572
|
+
# Superhero.new(name: 'Spiderman') # Works, as the required attribute is supplied
|
573
|
+
# Superhero.new(name: nil) # Works, as the required attribute is supplied (even if it is nil)
|
138
574
|
#
|
139
575
|
# @return [self] the builder for method chaining
|
140
576
|
def required: () -> self
|
141
577
|
|
142
|
-
#
|
578
|
+
# Adds a custom validation to an attribute, allowing you to define specific criteria that the attribute's value
|
579
|
+
# must meet. Validators can be Procs, lambdas, or symbols referencing instance methods. Validation occurs during
|
580
|
+
# initialization and whenever the attribute value is updated. Use `validate_with` to enforce rules beyond type
|
581
|
+
# or presence, such as ranges, formats, or custom logic.
|
582
|
+
#
|
583
|
+
# @example Adding a simple validation
|
584
|
+
# class Superhero
|
585
|
+
# include Domainic::Attributer
|
586
|
+
#
|
587
|
+
# argument :power_level do
|
588
|
+
# desc 'The power level of the superhero, which must be an integer between 0 and 100.'
|
589
|
+
# validate_with ->(val) { val.is_a?(Integer) && val.between?(0, 100) }
|
590
|
+
# end
|
591
|
+
# end
|
592
|
+
#
|
593
|
+
# Superhero.new(150) # Raises ArgumentError: invalid value for power_level
|
594
|
+
# Superhero.new(85) # Works, as 85 is within the valid range
|
595
|
+
#
|
596
|
+
# @example Using an instance method as a validator
|
597
|
+
# class Superhero
|
598
|
+
# include Domainic::Attributer
|
599
|
+
#
|
600
|
+
# argument :alias_name do
|
601
|
+
# desc 'The alias name of the superhero, validated using an instance method.'
|
602
|
+
# validate_with :validate_alias_name
|
603
|
+
# end
|
604
|
+
#
|
605
|
+
# private
|
606
|
+
#
|
607
|
+
# def validate_alias_name(value)
|
608
|
+
# value.is_a?(String) && value.match?(/\A[A-Z][a-z]+\z/)
|
609
|
+
# end
|
610
|
+
# end
|
611
|
+
#
|
612
|
+
# Superhero.new('Spiderman') # Works, as the alias name matches the validation criteria
|
613
|
+
# Superhero.new('spiderman') # Raises ArgumentError: invalid value for alias_name
|
614
|
+
#
|
615
|
+
# @example Combining multiple validators
|
616
|
+
# class Vehicle
|
617
|
+
# include Domainic::Attributer
|
618
|
+
#
|
619
|
+
# option :speed do
|
620
|
+
# desc 'The speed of the vehicle, which must be a non-negative number.'
|
621
|
+
# validate_with ->(val) { val.is_a?(Numeric) }
|
622
|
+
# validate_with do |val|
|
623
|
+
# val.zero? || val.positive?
|
624
|
+
# end
|
625
|
+
# end
|
626
|
+
# end
|
627
|
+
#
|
628
|
+
# Vehicle.new(speed: -10) # Raises ArgumentError: invalid value for speed
|
629
|
+
# Vehicle.new(speed: 50) # Works, as 50 meets all validation criteria
|
143
630
|
#
|
144
631
|
# @param object_or_proc [Object, Proc, nil] optional validation handler
|
145
632
|
# @yield optional validation block
|
633
|
+
# @yieldparam value [Object] the value to validate
|
634
|
+
# @yieldreturn [Boolean] `true` if the value is valid, `false` otherwise
|
146
635
|
#
|
147
636
|
# @return [self] the builder for method chaining
|
148
637
|
def validate_with: (?Attribute::Validator::handler? object_or_proc) ?{ (untyped value) -> boolish } -> self
|