cattri 0.1.3 → 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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +34 -0
  3. data/.gitignore +72 -0
  4. data/.rubocop.yml +6 -3
  5. data/CHANGELOG.md +41 -0
  6. data/Gemfile +12 -0
  7. data/README.md +163 -151
  8. data/Steepfile +6 -0
  9. data/bin/console +33 -0
  10. data/bin/setup +8 -0
  11. data/cattri.gemspec +5 -5
  12. data/lib/cattri/attribute.rb +119 -155
  13. data/lib/cattri/attribute_compiler.rb +104 -0
  14. data/lib/cattri/attribute_options.rb +183 -0
  15. data/lib/cattri/attribute_registry.rb +155 -0
  16. data/lib/cattri/context.rb +124 -106
  17. data/lib/cattri/context_registry.rb +36 -0
  18. data/lib/cattri/deferred_attributes.rb +73 -0
  19. data/lib/cattri/dsl.rb +54 -0
  20. data/lib/cattri/error.rb +17 -90
  21. data/lib/cattri/inheritance.rb +35 -0
  22. data/lib/cattri/initializer_patch.rb +37 -0
  23. data/lib/cattri/internal_store.rb +104 -0
  24. data/lib/cattri/introspection.rb +56 -49
  25. data/lib/cattri/version.rb +3 -1
  26. data/lib/cattri.rb +38 -99
  27. data/sig/lib/cattri/attribute.rbs +105 -0
  28. data/sig/lib/cattri/attribute_compiler.rbs +61 -0
  29. data/sig/lib/cattri/attribute_options.rbs +150 -0
  30. data/sig/lib/cattri/attribute_registry.rbs +95 -0
  31. data/sig/lib/cattri/context.rbs +130 -0
  32. data/sig/lib/cattri/context_registry.rbs +31 -0
  33. data/sig/lib/cattri/deferred_attributes.rbs +53 -0
  34. data/sig/lib/cattri/dsl.rbs +55 -0
  35. data/sig/lib/cattri/error.rbs +28 -0
  36. data/sig/lib/cattri/inheritance.rbs +21 -0
  37. data/sig/lib/cattri/initializer_patch.rbs +26 -0
  38. data/sig/lib/cattri/internal_store.rbs +75 -0
  39. data/sig/lib/cattri/introspection.rbs +61 -0
  40. data/sig/lib/cattri/types.rbs +19 -0
  41. data/sig/lib/cattri/visibility.rbs +55 -0
  42. data/sig/lib/cattri.rbs +37 -0
  43. data/spec/cattri/attribute_compiler_spec.rb +179 -0
  44. data/spec/cattri/attribute_options_spec.rb +267 -0
  45. data/spec/cattri/attribute_registry_spec.rb +257 -0
  46. data/spec/cattri/attribute_spec.rb +297 -0
  47. data/spec/cattri/context_registry_spec.rb +45 -0
  48. data/spec/cattri/context_spec.rb +346 -0
  49. data/spec/cattri/deferred_attrributes_spec.rb +117 -0
  50. data/spec/cattri/dsl_spec.rb +69 -0
  51. data/spec/cattri/error_spec.rb +37 -0
  52. data/spec/cattri/inheritance_spec.rb +60 -0
  53. data/spec/cattri/initializer_patch_spec.rb +35 -0
  54. data/spec/cattri/internal_store_spec.rb +139 -0
  55. data/spec/cattri/introspection_spec.rb +90 -0
  56. data/spec/cattri/visibility_spec.rb +68 -0
  57. data/spec/cattri_spec.rb +54 -0
  58. data/spec/simplecov_helper.rb +21 -0
  59. data/spec/spec_helper.rb +16 -0
  60. metadata +79 -6
  61. data/lib/cattri/attribute_definer.rb +0 -143
  62. data/lib/cattri/class_attributes.rb +0 -277
  63. data/lib/cattri/instance_attributes.rb +0 -276
  64. data/sig/cattri.rbs +0 -4
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "simplecov_helper"
4
+ require "cattri"
5
+
6
+ RSpec.configure do |config|
7
+ # Enable flags like --only-failures and --next-failure
8
+ config.example_status_persistence_file_path = ".rspec_status"
9
+
10
+ # Disable RSpec exposing methods globally on `Module` and `main`
11
+ config.disable_monkey_patching!
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cattri
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Lucas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-23 00:00:00.000000000 Z
11
+ date: 2025-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: debride
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: steep
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: yard
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -102,25 +130,69 @@ executables: []
102
130
  extensions: []
103
131
  extra_rdoc_files: []
104
132
  files:
133
+ - ".github/workflows/main.yml"
134
+ - ".gitignore"
105
135
  - ".rspec"
106
136
  - ".rubocop.yml"
107
137
  - CHANGELOG.md
108
138
  - CODE_OF_CONDUCT.md
139
+ - Gemfile
109
140
  - LICENSE.txt
110
141
  - README.md
111
142
  - Rakefile
143
+ - Steepfile
144
+ - bin/console
145
+ - bin/setup
112
146
  - cattri.gemspec
113
147
  - lib/cattri.rb
114
148
  - lib/cattri/attribute.rb
115
- - lib/cattri/attribute_definer.rb
116
- - lib/cattri/class_attributes.rb
149
+ - lib/cattri/attribute_compiler.rb
150
+ - lib/cattri/attribute_options.rb
151
+ - lib/cattri/attribute_registry.rb
117
152
  - lib/cattri/context.rb
153
+ - lib/cattri/context_registry.rb
154
+ - lib/cattri/deferred_attributes.rb
155
+ - lib/cattri/dsl.rb
118
156
  - lib/cattri/error.rb
119
- - lib/cattri/instance_attributes.rb
157
+ - lib/cattri/inheritance.rb
158
+ - lib/cattri/initializer_patch.rb
159
+ - lib/cattri/internal_store.rb
120
160
  - lib/cattri/introspection.rb
121
161
  - lib/cattri/version.rb
122
162
  - lib/cattri/visibility.rb
123
- - sig/cattri.rbs
163
+ - sig/lib/cattri.rbs
164
+ - sig/lib/cattri/attribute.rbs
165
+ - sig/lib/cattri/attribute_compiler.rbs
166
+ - sig/lib/cattri/attribute_options.rbs
167
+ - sig/lib/cattri/attribute_registry.rbs
168
+ - sig/lib/cattri/context.rbs
169
+ - sig/lib/cattri/context_registry.rbs
170
+ - sig/lib/cattri/deferred_attributes.rbs
171
+ - sig/lib/cattri/dsl.rbs
172
+ - sig/lib/cattri/error.rbs
173
+ - sig/lib/cattri/inheritance.rbs
174
+ - sig/lib/cattri/initializer_patch.rbs
175
+ - sig/lib/cattri/internal_store.rbs
176
+ - sig/lib/cattri/introspection.rbs
177
+ - sig/lib/cattri/types.rbs
178
+ - sig/lib/cattri/visibility.rbs
179
+ - spec/cattri/attribute_compiler_spec.rb
180
+ - spec/cattri/attribute_options_spec.rb
181
+ - spec/cattri/attribute_registry_spec.rb
182
+ - spec/cattri/attribute_spec.rb
183
+ - spec/cattri/context_registry_spec.rb
184
+ - spec/cattri/context_spec.rb
185
+ - spec/cattri/deferred_attrributes_spec.rb
186
+ - spec/cattri/dsl_spec.rb
187
+ - spec/cattri/error_spec.rb
188
+ - spec/cattri/inheritance_spec.rb
189
+ - spec/cattri/initializer_patch_spec.rb
190
+ - spec/cattri/internal_store_spec.rb
191
+ - spec/cattri/introspection_spec.rb
192
+ - spec/cattri/visibility_spec.rb
193
+ - spec/cattri_spec.rb
194
+ - spec/simplecov_helper.rb
195
+ - spec/spec_helper.rb
124
196
  homepage: https://github.com/bnlucas/cattri
125
197
  licenses:
126
198
  - MIT
@@ -128,6 +200,7 @@ metadata:
128
200
  homepage_uri: https://github.com/bnlucas/cattri
129
201
  source_code_uri: https://github.com/bnlucas/cattri
130
202
  changelog_uri: https://github.com/bnlucas/cattri/blob/main/CHANGELOG.md
203
+ documentation_uri: https://www.rubydoc.info/gems/cattri
131
204
  rubygems_mfa_required: 'true'
132
205
  post_install_message:
133
206
  rdoc_options: []
@@ -1,143 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cattri
4
- # Defines attribute accessors on a given target class using Cattri's Context.
5
- #
6
- # This class provides a set of utility methods to generate reader and writer
7
- # methods dynamically, with support for default values, coercion, and memoization.
8
- #
9
- # All accessors are defined through the Cattri::Context abstraction to ensure
10
- # consistent scoping, visibility, and method tracking.
11
- class AttributeDefiner
12
- class << self
13
- # Defines a callable accessor for class-level attributes.
14
- #
15
- # The generated method:
16
- # - Returns the memoized default value when called with no args or if readonly
17
- # - Otherwise, calls the attribute’s setter and memoizes the result
18
- #
19
- # If the attribute is not readonly, a writer (`foo=`) is also defined.
20
- #
21
- # @param attribute [Cattri::Attribute]
22
- # @param context [Cattri::Context]
23
- # @return [void]
24
- # @raise [Cattri::AttributeError] if the setter raises an error
25
- def define_callable_accessor(attribute, context)
26
- return unless attribute.class_level?
27
-
28
- context.define_method(attribute) do |*args, **kwargs|
29
- readonly = (args.empty? && kwargs.empty?) || attribute[:readonly]
30
- return AttributeDefiner.send(:memoize_default_value, self, attribute) if readonly
31
-
32
- value = attribute.invoke_setter(*args, **kwargs)
33
- instance_variable_set(attribute.ivar, value)
34
- end
35
-
36
- define_writer(attribute, context) unless attribute[:readonly]
37
- end
38
-
39
- # Defines an instance-level reader for class-level attributes.
40
- #
41
- # This method delegates the instance-level call to the class method.
42
- # It is used when `instance_reader: true` is specified.
43
- #
44
- # @param attribute [Cattri::Attribute]
45
- # @param context [Cattri::Context]
46
- # @return [void]
47
- def define_instance_level_reader(attribute, context)
48
- return unless attribute.class_level?
49
-
50
- define_instance_level_method(attribute, context) do
51
- self.class.__send__(attribute.name)
52
- end
53
-
54
- context.send(:apply_access, attribute.name, attribute)
55
- end
56
-
57
- # Defines an instance-level method for a class-level attribute.
58
- #
59
- # This is a shared utility for defining instance methods that delegate to class attributes,
60
- # including both regular readers and predicate-style readers (`predicate: true`).
61
- #
62
- # Visibility is inherited from the attribute and applied to the defined method.
63
- #
64
- # @param attribute [Cattri::Attribute] the associated attribute metadata
65
- # @param context [Cattri::Context] the context in which to define the method
66
- # @param name [Symbol, nil] optional override for the method name (defaults to `attribute.name`)
67
- # @yield the method body to define
68
- # @return [void]
69
- def define_instance_level_method(attribute, context, name: nil, &block)
70
- name = (name || attribute.name).to_sym
71
- context.target.define_method(name, &block)
72
-
73
- context.send(:apply_access, name, attribute)
74
- end
75
-
76
- # Defines standard reader and writer methods for instance-level attributes.
77
- #
78
- # Skips definition if `reader: false` or `writer: false` is specified.
79
- #
80
- # @param attribute [Cattri::Attribute]
81
- # @param context [Cattri::Context]
82
- # @return [void]
83
- def define_accessor(attribute, context)
84
- define_reader(attribute, context) if attribute[:reader]
85
- define_writer(attribute, context) if attribute[:writer]
86
- end
87
-
88
- # Defines a memoizing reader for the given attribute.
89
- #
90
- # This is used for both class and instance attributes, and ensures that
91
- # the default value is computed only once and stored in the ivar.
92
- #
93
- # @param attribute [Cattri::Attribute]
94
- # @param context [Cattri::Context]
95
- # @return [void]
96
- def define_reader(attribute, context)
97
- context.define_method(attribute) do
98
- AttributeDefiner.send(:memoize_default_value, self, attribute)
99
- end
100
- end
101
-
102
- # Defines a writer method (`foo=`) that sets and coerces a value via the attribute setter.
103
- #
104
- # @param attribute [Cattri::Attribute]
105
- # @param context [Cattri::Context]
106
- # @return [void]
107
- def define_writer(attribute, context)
108
- context.define_method(attribute, name: :"#{attribute.name}=") do |value|
109
- coerced_value = attribute.setter.call(value)
110
- instance_variable_set(attribute.ivar, coerced_value)
111
- end
112
- end
113
-
114
- # Defines, or redefines, a writer method (`foo=`) that sets and coerces a value via the attribute setter.
115
- #
116
- # @param attribute [Cattri::Attribute]
117
- # @param context [Cattri::Context]
118
- # @return [void]
119
- def define_writer!(attribute, context)
120
- context.define_method!(attribute, name: :"#{attribute.name}=") do |value|
121
- coerced_value = attribute.setter.call(value)
122
- instance_variable_set(attribute.ivar, coerced_value)
123
- end
124
- end
125
-
126
- private
127
-
128
- # Returns the memoized value for an attribute or computes it from the default.
129
- #
130
- # This helper ensures lazy initialization while guarding against errors in the default proc.
131
- #
132
- # @param receiver [Object]
133
- # @param attribute [Cattri::Attribute]
134
- # @return [Object]
135
- # @raise [Cattri::AttributeError] if the default block raises an error
136
- def memoize_default_value(receiver, attribute)
137
- return receiver.instance_variable_get(attribute.ivar) if receiver.instance_variable_defined?(attribute.ivar)
138
-
139
- receiver.instance_variable_set(attribute.ivar, attribute.invoke_default)
140
- end
141
- end
142
- end
143
- end
@@ -1,277 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "attribute"
4
- require_relative "context"
5
- require_relative "attribute_definer"
6
- require_relative "visibility"
7
-
8
- module Cattri
9
- # Mixin that provides support for defining class-level attributes.
10
- #
11
- # This module is intended to be extended onto a class and provides a DSL
12
- # for defining configuration-style attributes at the class level using `cattr`.
13
- #
14
- # Features:
15
- # - Default values (static, frozen, or callable)
16
- # - Optional coercion via setter blocks
17
- # - Optional instance-level readers
18
- # - Visibility enforcement (`:public`, `:protected`, `:private`)
19
- #
20
- # Class attributes are stored internally as `Cattri::Attribute` instances and
21
- # values are memoized using class-level instance variables.
22
- module ClassAttributes
23
- # Defines one or more class-level attributes with optional default, coercion, and reader access.
24
- #
25
- # This method supports defining multiple attributes at once, provided they share the same options.
26
- # If a block is given, only one attribute may be defined to avoid ambiguity.
27
- #
28
- # @example Define multiple attributes with shared options
29
- # class_attribute :foo, :bar, default: 42
30
- #
31
- # @example Define a single attribute with a coercion block
32
- # class_attribute :path do |val|
33
- # Pathname(val)
34
- # end
35
- #
36
- # @param names [Array<Symbol | String>] the names of the attributes to define
37
- # @param options [Hash] additional attribute options
38
- # @option options [Object, Proc] :default the default value or lambda
39
- # @option options [Boolean] :readonly whether the attribute is read-only
40
- # @option options [Boolean] :instance_reader whether to define an instance-level reader (default: true)
41
- # @option options [Symbol] :access visibility level (:public, :protected, :private)
42
- # @option options [Boolean] :predicate whether to define a predicate-style alias method
43
- # (e.g., `foo?`) for the attribute
44
- # @yieldparam value [Object] an optional custom setter block
45
- # @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
46
- # `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
47
- # already defined or an error occurs while defining methods)
48
- # @return [void]
49
- def class_attribute(*names, **options, &block)
50
- raise Cattri::AmbiguousBlockError if names.size > 1 && block_given?
51
-
52
- names.each do |name|
53
- if name.end_with?("?")
54
- raise Cattri::AttributeError,
55
- "Attribute names ending in '?' are not allowed. Use `predicate: true` or `cattr_alias` instead."
56
-
57
- end
58
-
59
- define_class_attribute(name, options, block)
60
- end
61
- end
62
-
63
- # Defines a read-only class attribute.
64
- #
65
- # Equivalent to calling `class_attribute(name, readonly: true, ...)`
66
- #
67
- # @param names [Array<Symbol | String>] the names of the attributes to define
68
- # @param options [Hash] additional attribute options
69
- # @option options [Object, Proc] :default the default value or lambda
70
- # @option options [Boolean] :readonly whether the attribute is read-only
71
- # @option options [Boolean] :instance_reader whether to define an instance-level reader (default: true)
72
- # @option options [Symbol] :access visibility level (:public, :protected, :private)
73
- # @option options [Boolean] :predicate whether to define a predicate-style alias method
74
- # (e.g., `foo?`) for the attribute
75
- # @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
76
- # `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
77
- # already defined or an error occurs while defining methods)
78
- # @return [void]
79
- def class_attribute_reader(*names, **options)
80
- class_attribute(*names, **options, readonly: true)
81
- end
82
-
83
- # Updates the setter behavior of an existing class-level attribute.
84
- #
85
- # This allows coercion logic to be defined or overridden after the attribute
86
- # has been declared using `cattr`, as long as the writer method exists.
87
- #
88
- # @example Add coercion to an existing attribute
89
- # cattr :format
90
- # cattr_setter :format do |val|
91
- # val.to_s.downcase.to_sym
92
- # end
93
- #
94
- # @param name [Symbol, String] the name of the attribute
95
- # @yieldparam value [Object] the value passed to the setter
96
- # @yieldreturn [Object] the coerced value to be assigned
97
- # @raise [Cattri::AttributeNotDefinedError] if the attribute is not defined or the writer method does not exist
98
- # @raise [Cattri::AttributeDefinitionError] if method redefinition fails
99
- # @return [void]
100
- def class_attribute_setter(name, &block)
101
- name = name.to_sym
102
- attribute = __cattri_class_attributes[name]
103
- puts "<<< #{attribute} = #{name}>"
104
-
105
- if attribute.nil? || !context.method_defined?(:"#{name}=")
106
- raise Cattri::AttributeNotDefinedError.new(:class, name)
107
- end
108
-
109
- attribute.instance_variable_set(:@setter, attribute.send(:normalize_setter, block))
110
- Cattri::AttributeDefiner.define_writer!(attribute, context)
111
- end
112
-
113
- # Defines an alias method for an existing class-level attribute.
114
- #
115
- # This does **not** register a new attribute; it simply defines a method
116
- # (e.g., a predicate-style alias like `foo?`) that delegates to an existing one.
117
- #
118
- # The alias method inherits the visibility of the original attribute.
119
- #
120
- # @param alias_name [Symbol, String] the new method name (e.g., `:foo?`)
121
- # @param original [Symbol, String] the name of the existing attribute to delegate to (e.g., `:foo`)
122
- # @raise [Cattri::AttributeNotDefinedError] if the original attribute is not defined
123
- # @return [void]
124
- def class_attribute_alias(alias_name, original)
125
- attribute = __cattri_class_attributes[original.to_sym]
126
- raise Cattri::AttributeNotDefinedError.new(:class, original) if attribute.nil?
127
-
128
- context.define_method(attribute, name: alias_name) { public_send(original) }
129
- end
130
-
131
- # Returns a list of defined class attribute names.
132
- #
133
- # @return [Array<Symbol>]
134
- def class_attributes
135
- ([self] + ancestors + singleton_class.included_modules)
136
- .uniq
137
- .select { |mod| mod.respond_to?(:__cattri_class_attributes, true) }
138
- .flat_map { |mod| mod.send(:__cattri_class_attributes).keys }
139
- .uniq
140
- end
141
-
142
- # Checks whether a class attribute has been defined.
143
- #
144
- # @param name [Symbol]
145
- # @return [Boolean]
146
- def class_attribute_defined?(name)
147
- __cattri_class_attributes.key?(name.to_sym)
148
- end
149
-
150
- # Returns the full attribute definition object.
151
- #
152
- # @param name [Symbol]
153
- # @return [Cattri::Attribute, nil]
154
- def class_attribute_definition(name)
155
- __cattri_class_attributes[name.to_sym]
156
- end
157
-
158
- # @!method cattr(name, **options, &block)
159
- # Alias for {.class_attribute}
160
- # @see #class_attribute
161
- alias cattr class_attribute
162
-
163
- # @!method cattr_accessor(name, **options, &block)
164
- # Alias for {.class_attribute}
165
- # @see #class_attribute
166
- alias cattr_accessor class_attribute
167
-
168
- # @!method cattr_reader(name, **options)
169
- # Alias for {.class_attribute_reader}
170
- # @see #class_attribute_reader
171
- alias cattr_reader class_attribute_reader
172
-
173
- # @!method cattr_setter(name, **options)
174
- # Alias for {.class_attribute_setter}
175
- # @see #class_attribute_setter
176
- alias cattr_setter class_attribute_setter
177
-
178
- # @!method cattr_alias(name, **options)
179
- # Alias for {.class_attribute_alias}
180
- # @see #class_attribute_alias
181
- alias cattr_alias class_attribute_alias
182
-
183
- # @!method cattrs
184
- # Alias for {.class_attributes}
185
- # @return [Array<Symbol>]
186
- alias cattrs class_attributes
187
-
188
- # @!method cattr_defined?(name)
189
- # Alias for {.class_attribute_defined?}
190
- # @param name [Symbol]
191
- # @return [Boolean]
192
- alias cattr_defined? class_attribute_defined?
193
-
194
- # @!method cattr_definition(name)
195
- # Alias for {.class_attribute_definition}
196
- # @param name [Symbol]
197
- # @return [Cattri::Attribute, nil]
198
- alias cattr_definition class_attribute_definition
199
-
200
- private
201
-
202
- # Defines a single class-level attribute.
203
- #
204
- # This is the internal implementation used by {.class_attribute} and its aliases.
205
- # It constructs a `Cattri::Attribute`, registers it, and defines the necessary
206
- # class and instance methods.
207
- #
208
- # @param name [Symbol] the name of the attribute to define
209
- # @param options [Hash] additional attribute options (e.g., :default, :readonly)
210
- # @param block [Proc, nil] an optional setter block for coercion
211
- #
212
- # @raise [Cattri::AttributeDefinedError] if the attribute has already been defined
213
- # @raise [Cattri::AttributeDefinitionError] if method definition fails
214
- #
215
- # @return [void]
216
- def define_class_attribute(name, options, block) # rubocop:disable Metrics/AbcSize
217
- options[:access] ||= __cattri_visibility
218
- attribute = Cattri::Attribute.new(name, :class, options, block)
219
-
220
- raise Cattri::AttributeDefinedError.new(:class, name) if class_attribute_defined?(attribute.name)
221
-
222
- begin
223
- __cattri_class_attributes[name] = attribute
224
-
225
- Cattri::AttributeDefiner.define_callable_accessor(attribute, context)
226
- Cattri::AttributeDefiner.define_instance_level_reader(attribute, context) if attribute[:instance_reader]
227
- rescue StandardError => e
228
- raise Cattri::AttributeDefinitionError.new(self, attribute, e)
229
- end
230
-
231
- define_predicate_methods(attribute) if attribute[:predicate]
232
- end
233
-
234
- # Defines predicate-style (`:name?`) methods for a class-level attribute.
235
- #
236
- # If `attribute[:predicate]` is true, this defines a method named `:name?` that returns
237
- # a boolean based on the truthiness of the attribute's value (`!!value`).
238
- #
239
- # If `attribute[:instance_reader]` is also true, an instance-level predicate method
240
- # is defined that delegates to the class-level value.
241
- #
242
- # Visibility is inherited from the original attribute.
243
- #
244
- # @param attribute [Cattri::Attribute] the attribute for which to define predicate methods
245
- # @return [void]
246
- def define_predicate_methods(attribute)
247
- return unless attribute[:predicate]
248
-
249
- predicate_name = :"#{attribute.name}?"
250
-
251
- # rubocop:disable Style/DoubleNegation
252
- context.define_method(attribute, name: predicate_name) { !!send(attribute.name) }
253
- return unless attribute[:instance_reader]
254
-
255
- Cattri::AttributeDefiner.define_instance_level_method(attribute, context, name: predicate_name) do
256
- !!self.class.__send__(attribute.name)
257
- end
258
- # rubocop:enable Style/DoubleNegation
259
- end
260
-
261
- # Internal registry of defined class-level attributes.
262
- #
263
- # @return [Hash{Symbol => Cattri::Attribute}]
264
- def __cattri_class_attributes
265
- @__cattri_class_attributes ||= {}
266
- end
267
-
268
- # Context object used to define accessors with scoped visibility.
269
- #
270
- # @return [Cattri::Context]
271
- # :nocov:
272
- def context
273
- @context ||= Context.new(self)
274
- end
275
- # :nocov:
276
- end
277
- end