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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +34 -0
- data/.gitignore +72 -0
- data/.rubocop.yml +6 -3
- data/CHANGELOG.md +41 -0
- data/Gemfile +12 -0
- data/README.md +163 -151
- data/Steepfile +6 -0
- data/bin/console +33 -0
- data/bin/setup +8 -0
- data/cattri.gemspec +5 -5
- data/lib/cattri/attribute.rb +119 -155
- data/lib/cattri/attribute_compiler.rb +104 -0
- data/lib/cattri/attribute_options.rb +183 -0
- data/lib/cattri/attribute_registry.rb +155 -0
- data/lib/cattri/context.rb +124 -106
- data/lib/cattri/context_registry.rb +36 -0
- data/lib/cattri/deferred_attributes.rb +73 -0
- data/lib/cattri/dsl.rb +54 -0
- data/lib/cattri/error.rb +17 -90
- data/lib/cattri/inheritance.rb +35 -0
- data/lib/cattri/initializer_patch.rb +37 -0
- data/lib/cattri/internal_store.rb +104 -0
- data/lib/cattri/introspection.rb +56 -49
- data/lib/cattri/version.rb +3 -1
- data/lib/cattri.rb +38 -99
- data/sig/lib/cattri/attribute.rbs +105 -0
- data/sig/lib/cattri/attribute_compiler.rbs +61 -0
- data/sig/lib/cattri/attribute_options.rbs +150 -0
- data/sig/lib/cattri/attribute_registry.rbs +95 -0
- data/sig/lib/cattri/context.rbs +130 -0
- data/sig/lib/cattri/context_registry.rbs +31 -0
- data/sig/lib/cattri/deferred_attributes.rbs +53 -0
- data/sig/lib/cattri/dsl.rbs +55 -0
- data/sig/lib/cattri/error.rbs +28 -0
- data/sig/lib/cattri/inheritance.rbs +21 -0
- data/sig/lib/cattri/initializer_patch.rbs +26 -0
- data/sig/lib/cattri/internal_store.rbs +75 -0
- data/sig/lib/cattri/introspection.rbs +61 -0
- data/sig/lib/cattri/types.rbs +19 -0
- data/sig/lib/cattri/visibility.rbs +55 -0
- data/sig/lib/cattri.rbs +37 -0
- data/spec/cattri/attribute_compiler_spec.rb +179 -0
- data/spec/cattri/attribute_options_spec.rb +267 -0
- data/spec/cattri/attribute_registry_spec.rb +257 -0
- data/spec/cattri/attribute_spec.rb +297 -0
- data/spec/cattri/context_registry_spec.rb +45 -0
- data/spec/cattri/context_spec.rb +346 -0
- data/spec/cattri/deferred_attrributes_spec.rb +117 -0
- data/spec/cattri/dsl_spec.rb +69 -0
- data/spec/cattri/error_spec.rb +37 -0
- data/spec/cattri/inheritance_spec.rb +60 -0
- data/spec/cattri/initializer_patch_spec.rb +35 -0
- data/spec/cattri/internal_store_spec.rb +139 -0
- data/spec/cattri/introspection_spec.rb +90 -0
- data/spec/cattri/visibility_spec.rb +68 -0
- data/spec/cattri_spec.rb +54 -0
- data/spec/simplecov_helper.rb +21 -0
- data/spec/spec_helper.rb +16 -0
- metadata +79 -6
- data/lib/cattri/attribute_definer.rb +0 -143
- data/lib/cattri/class_attributes.rb +0 -277
- data/lib/cattri/instance_attributes.rb +0 -276
- data/sig/cattri.rbs +0 -4
data/spec/spec_helper.rb
ADDED
@@ -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.
|
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-
|
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/
|
116
|
-
- lib/cattri/
|
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/
|
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
|