cattri 0.1.2 → 0.1.3
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/CHANGELOG.md +9 -0
- data/README.md +15 -8
- data/lib/cattri/attribute.rb +4 -2
- data/lib/cattri/attribute_definer.rb +20 -1
- data/lib/cattri/class_attributes.rb +75 -2
- data/lib/cattri/instance_attributes.rb +55 -5
- data/lib/cattri/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2495a8756d30c301267ad2db2dfbd4c9669e817119fa316b608408665988b747
|
4
|
+
data.tar.gz: 47d682eaf5465e4ab6cc4bdb7dcc74d053c93c6b8d1ee3a8cfda0925cf68662a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aae777877b7576a76b0be11a529aafaaac9c59066efd873fc9020689ba3ccefb78688411907b2246cb603dafa5e6868e1841e4724f296f634eac821a1bc27bc9
|
7
|
+
data.tar.gz: 8e919b6782d0cce71ed1a78bb46ebb92df995471238080a4e20604914d0fe958c041aec201eb598f9764598384fd776aaff03f5b131908082428c2d546c97cd9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## [0.1.3] - 2025-04-22
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
- ✅ Support for `predicate: true` on both `iattr` and `cattr` — defines a `:name?` method returning `!!send(:name)`
|
6
|
+
- ✅ `iattr_alias` and `cattr_alias` — define alias methods that delegate to existing attributes (e.g., `:foo?` for `:foo`)
|
7
|
+
- Predicate methods inherit visibility from the original attribute and are excluded from introspection (`iattrs`, `cattrs`)
|
8
|
+
- Raised error when attempting to define an attribute ending in `?`, with guidance to use `predicate: true` or `*_alias`
|
9
|
+
|
1
10
|
## [0.1.2] - 2025-04-22
|
2
11
|
|
3
12
|
### Added
|
data/README.md
CHANGED
@@ -44,12 +44,13 @@ class Config
|
|
44
44
|
|
45
45
|
# -- class‑level ----------------------------------
|
46
46
|
cattr :flag_a, :flag_b, default: true
|
47
|
-
cattr :enabled, default: true
|
47
|
+
cattr :enabled, default: true, predicate: true
|
48
48
|
cattr :timeout, default: -> { 5.0 }, instance_reader: false
|
49
49
|
|
50
50
|
# -- instance‑level -------------------------------
|
51
51
|
iattr :item_a, :item_b, default: true
|
52
52
|
iattr :name, default: "anonymous"
|
53
|
+
iattr_alias :username, :name
|
53
54
|
iattr :age, default: 0 do |val| # coercion block
|
54
55
|
Integer(val)
|
55
56
|
end
|
@@ -57,7 +58,9 @@ end
|
|
57
58
|
|
58
59
|
Config.enabled # => true
|
59
60
|
Config.enabled = false
|
61
|
+
Config.enabled? # => false (created with predicate: true flag)
|
60
62
|
Config.new.age = "42" # => 42
|
63
|
+
Config.new.username # proxy to Config.new.name
|
61
64
|
```
|
62
65
|
|
63
66
|
---
|
@@ -67,12 +70,14 @@ Config.new.age = "42" # => 42
|
|
67
70
|
### Class attributes (`cattr`)
|
68
71
|
|
69
72
|
```ruby
|
70
|
-
cattr :log_level,
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
cattr :log_level,
|
74
|
+
default: :info,
|
75
|
+
access: :protected, # respects current visibility by default
|
76
|
+
readonly: false,
|
77
|
+
predicate: true, # defines #{name}? predicate method that respects visibility
|
78
|
+
instance_reader: true do |value|
|
79
|
+
value.to_sym
|
80
|
+
end
|
76
81
|
```
|
77
82
|
|
78
83
|
### Instance attributes (`iattr`)
|
@@ -80,7 +85,8 @@ cattr :log_level, default: :info,
|
|
80
85
|
```ruby
|
81
86
|
iattr :token, default: -> { SecureRandom.hex(8) },
|
82
87
|
reader: true,
|
83
|
-
writer: false # read‑only
|
88
|
+
writer: false, # read‑only
|
89
|
+
predicate: true
|
84
90
|
```
|
85
91
|
|
86
92
|
Both forms accept:
|
@@ -92,6 +98,7 @@ Both forms accept:
|
|
92
98
|
| `reader:` / `writer:` | Disable reader or writer for instance attributes. |
|
93
99
|
| `readonly:` | Shorthand for class attributes (`writer` is always present). |
|
94
100
|
| `instance_reader:` | Expose class attribute as instance reader (default: **true**). |
|
101
|
+
| `predicate` | Define a `:name?` method that calls `!!send(name)`
|
95
102
|
|
96
103
|
If you pass a block, it’s treated as a **coercion setter** and receives the incoming value.
|
97
104
|
|
data/lib/cattri/attribute.rb
CHANGED
@@ -23,13 +23,15 @@ module Cattri
|
|
23
23
|
# Default options for class-level attributes.
|
24
24
|
DEFAULT_CLASS_ATTRIBUTE_OPTIONS = {
|
25
25
|
readonly: false,
|
26
|
-
instance_reader: true
|
26
|
+
instance_reader: true,
|
27
|
+
predicate: false
|
27
28
|
}.freeze
|
28
29
|
|
29
30
|
# Default options for instance-level attributes.
|
30
31
|
DEFAULT_INSTANCE_ATTRIBUTE_OPTIONS = {
|
31
32
|
reader: true,
|
32
|
-
writer: true
|
33
|
+
writer: true,
|
34
|
+
predicate: false
|
33
35
|
}.freeze
|
34
36
|
|
35
37
|
# @return [Symbol] the attribute name
|
@@ -47,13 +47,32 @@ module Cattri
|
|
47
47
|
def define_instance_level_reader(attribute, context)
|
48
48
|
return unless attribute.class_level?
|
49
49
|
|
50
|
-
|
50
|
+
define_instance_level_method(attribute, context) do
|
51
51
|
self.class.__send__(attribute.name)
|
52
52
|
end
|
53
53
|
|
54
54
|
context.send(:apply_access, attribute.name, attribute)
|
55
55
|
end
|
56
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
|
+
|
57
76
|
# Defines standard reader and writer methods for instance-level attributes.
|
58
77
|
#
|
59
78
|
# Skips definition if `reader: false` or `writer: false` is specified.
|
@@ -39,6 +39,8 @@ module Cattri
|
|
39
39
|
# @option options [Boolean] :readonly whether the attribute is read-only
|
40
40
|
# @option options [Boolean] :instance_reader whether to define an instance-level reader (default: true)
|
41
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
|
42
44
|
# @yieldparam value [Object] an optional custom setter block
|
43
45
|
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
44
46
|
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
@@ -47,7 +49,15 @@ module Cattri
|
|
47
49
|
def class_attribute(*names, **options, &block)
|
48
50
|
raise Cattri::AmbiguousBlockError if names.size > 1 && block_given?
|
49
51
|
|
50
|
-
names.each
|
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
|
51
61
|
end
|
52
62
|
|
53
63
|
# Defines a read-only class attribute.
|
@@ -60,6 +70,8 @@ module Cattri
|
|
60
70
|
# @option options [Boolean] :readonly whether the attribute is read-only
|
61
71
|
# @option options [Boolean] :instance_reader whether to define an instance-level reader (default: true)
|
62
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
|
63
75
|
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
64
76
|
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
65
77
|
# already defined or an error occurs while defining methods)
|
@@ -98,11 +110,33 @@ module Cattri
|
|
98
110
|
Cattri::AttributeDefiner.define_writer!(attribute, context)
|
99
111
|
end
|
100
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
|
+
|
101
131
|
# Returns a list of defined class attribute names.
|
102
132
|
#
|
103
133
|
# @return [Array<Symbol>]
|
104
134
|
def class_attributes
|
105
|
-
|
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
|
106
140
|
end
|
107
141
|
|
108
142
|
# Checks whether a class attribute has been defined.
|
@@ -136,6 +170,16 @@ module Cattri
|
|
136
170
|
# @see #class_attribute_reader
|
137
171
|
alias cattr_reader class_attribute_reader
|
138
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
|
+
|
139
183
|
# @!method cattrs
|
140
184
|
# Alias for {.class_attributes}
|
141
185
|
# @return [Array<Symbol>]
|
@@ -183,6 +227,35 @@ module Cattri
|
|
183
227
|
rescue StandardError => e
|
184
228
|
raise Cattri::AttributeDefinitionError.new(self, attribute, e)
|
185
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
|
186
259
|
end
|
187
260
|
|
188
261
|
# Internal registry of defined class-level attributes.
|
@@ -45,6 +45,8 @@ module Cattri
|
|
45
45
|
# @option options [Boolean] :reader whether to define a reader method (default: true)
|
46
46
|
# @option options [Boolean] :writer whether to define a writer method (default: true)
|
47
47
|
# @option options [Symbol] :access method visibility (:public, :protected, :private)
|
48
|
+
# @option options [Boolean] :predicate whether to define a predicate-style alias method
|
49
|
+
# (e.g., `foo?`) for the attribute
|
48
50
|
# @yieldparam value [Object] optional custom coercion logic for the setter
|
49
51
|
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
50
52
|
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
@@ -53,7 +55,15 @@ module Cattri
|
|
53
55
|
def instance_attribute(*names, **options, &block)
|
54
56
|
raise Cattri::AmbiguousBlockError if names.size > 1 && block_given?
|
55
57
|
|
56
|
-
names.each
|
58
|
+
names.each do |name|
|
59
|
+
if name.end_with?("?")
|
60
|
+
raise Cattri::AttributeError,
|
61
|
+
"Attribute names ending in '?' are not allowed. Use `predicate: true` or `iattr_alias` instead."
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
define_instance_attribute(name, options, block)
|
66
|
+
end
|
57
67
|
end
|
58
68
|
|
59
69
|
# Defines a read-only instance-level attribute.
|
@@ -65,6 +75,8 @@ module Cattri
|
|
65
75
|
# @option options [Object, Proc] :default the default value or lambda
|
66
76
|
# @option options [Boolean] :reader whether to define a reader method (default: true)
|
67
77
|
# @option options [Symbol] :access method visibility (:public, :protected, :private)
|
78
|
+
# @option options [Boolean] :predicate whether to define a predicate-style alias method
|
79
|
+
# (e.g., `foo?`) for the attribute
|
68
80
|
# @yieldparam value [Object] optional custom coercion logic for the setter
|
69
81
|
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
70
82
|
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
@@ -76,7 +88,8 @@ module Cattri
|
|
76
88
|
|
77
89
|
# Defines a write-only instance-level attribute.
|
78
90
|
#
|
79
|
-
# Equivalent to `instance_attribute(..., reader: false)
|
91
|
+
# Equivalent to `instance_attribute(..., reader: false)`. The predicate: option is not allowed
|
92
|
+
# when defining writer methods.
|
80
93
|
#
|
81
94
|
# @param names [Array<Symbol | String>] the names of the attributes to define
|
82
95
|
# @param options [Hash] additional options like `:default`, `:reader`, `:writer`
|
@@ -89,7 +102,7 @@ module Cattri
|
|
89
102
|
# already defined or an error occurs while defining methods)
|
90
103
|
# @return [void]
|
91
104
|
def instance_attribute_writer(*names, **options, &block)
|
92
|
-
instance_attribute(*names, **options.merge(reader: false), &block)
|
105
|
+
instance_attribute(*names, **options.merge(reader: false, predicate: false), &block)
|
93
106
|
end
|
94
107
|
|
95
108
|
# Updates the setter behavior of an existing instance-level attribute.
|
@@ -119,11 +132,33 @@ module Cattri
|
|
119
132
|
Cattri::AttributeDefiner.define_writer!(attribute, context)
|
120
133
|
end
|
121
134
|
|
135
|
+
# Defines an alias method for an existing instance-level attribute.
|
136
|
+
#
|
137
|
+
# This does **not** register a new attribute; it simply defines a method
|
138
|
+
# (e.g., a predicate-style alias like `foo?`) that delegates to an existing one.
|
139
|
+
#
|
140
|
+
# The alias method inherits the visibility of the original attribute.
|
141
|
+
#
|
142
|
+
# @param alias_name [Symbol, String] the new method name (e.g., `:foo?`)
|
143
|
+
# @param original [Symbol, String] the name of the existing attribute to delegate to (e.g., `:foo`)
|
144
|
+
# @raise [Cattri::AttributeNotDefinedError] if the original attribute is not defined
|
145
|
+
# @return [void]
|
146
|
+
def instance_attribute_alias(alias_name, original)
|
147
|
+
attribute = __cattri_instance_attributes[original.to_sym]
|
148
|
+
raise Cattri::AttributeNotDefinedError.new(:instance, original) if attribute.nil?
|
149
|
+
|
150
|
+
context.define_method(attribute, name: alias_name) { public_send(original) }
|
151
|
+
end
|
152
|
+
|
122
153
|
# Returns a list of defined instance-level attribute names.
|
123
154
|
#
|
124
155
|
# @return [Array<Symbol>]
|
125
156
|
def instance_attributes
|
126
|
-
|
157
|
+
([self] + ancestors + singleton_class.included_modules)
|
158
|
+
.uniq
|
159
|
+
.select { |mod| mod.respond_to?(:__cattri_instance_attributes, true) }
|
160
|
+
.flat_map { |mod| mod.send(:__cattri_instance_attributes).keys }
|
161
|
+
.uniq
|
127
162
|
end
|
128
163
|
|
129
164
|
# Checks if an instance-level attribute has been defined.
|
@@ -144,34 +179,47 @@ module Cattri
|
|
144
179
|
|
145
180
|
# @!method iattr(name, **options, &block)
|
146
181
|
# Alias for {#instance_attribute}
|
182
|
+
# @see #instance_attribute
|
147
183
|
alias iattr instance_attribute
|
148
184
|
|
149
185
|
# @!method iattr_accessor(name, **options, &block)
|
150
186
|
# Alias for {#instance_attribute}
|
187
|
+
# @see #instance_attribute
|
151
188
|
alias iattr_accessor instance_attribute
|
152
189
|
|
153
190
|
# @!method iattr_reader(name, **options)
|
154
191
|
# Alias for {#instance_attribute_reader}
|
192
|
+
# @see #instance_attribute_reader
|
155
193
|
alias iattr_reader instance_attribute_reader
|
156
194
|
|
157
195
|
# @!method iattr_writer(name, **options, &block)
|
158
196
|
# Alias for {#instance_attribute_writer}
|
197
|
+
# @see #instance_attribute_writer
|
159
198
|
alias iattr_writer instance_attribute_writer
|
160
199
|
|
161
200
|
# @!method iattr_setter(name, &block)
|
162
201
|
# Alias for {#instance_attribute_setter}
|
202
|
+
# @see #instance_attribute_setter
|
163
203
|
alias iattr_setter instance_attribute_setter
|
164
204
|
|
205
|
+
# @!method iattr_alias(name, &block)
|
206
|
+
# Alias for {#instance_attribute_alias}
|
207
|
+
# @see #instance_attribute_alias
|
208
|
+
alias iattr_alias instance_attribute_alias
|
209
|
+
|
165
210
|
# @!method iattrs
|
166
211
|
# Alias for {#instance_attributes}
|
212
|
+
# @see #instance_attributes
|
167
213
|
alias iattrs instance_attributes
|
168
214
|
|
169
215
|
# @!method iattr_defined?(name)
|
170
216
|
# Alias for {#instance_attribute_defined?}
|
217
|
+
# @see #instance_attribute_defined?
|
171
218
|
alias iattr_defined? instance_attribute_defined?
|
172
219
|
|
173
220
|
# @!method iattr_definition(name)
|
174
221
|
# Alias for {#instance_attribute_definition}
|
222
|
+
# @see #instance_attribute_definition
|
175
223
|
alias iattr_definition instance_attribute_definition
|
176
224
|
|
177
225
|
private
|
@@ -190,7 +238,7 @@ module Cattri
|
|
190
238
|
# @raise [Cattri::AttributeDefinitionError] if method definition fails
|
191
239
|
#
|
192
240
|
# @return [void]
|
193
|
-
def define_instance_attribute(name, options, block)
|
241
|
+
def define_instance_attribute(name, options, block) # rubocop:disable Metrics/AbcSize
|
194
242
|
options[:access] ||= __cattri_visibility
|
195
243
|
attribute = Cattri::Attribute.new(name, :instance, options, block)
|
196
244
|
|
@@ -202,6 +250,8 @@ module Cattri
|
|
202
250
|
rescue StandardError => e
|
203
251
|
raise Cattri::AttributeDefinitionError.new(self, attribute, e)
|
204
252
|
end
|
253
|
+
|
254
|
+
context.define_method(attribute, name: :"#{name}?") { !!send(attribute.name) } if options[:predicate]
|
205
255
|
end
|
206
256
|
|
207
257
|
# Internal registry of instance attributes defined on the class.
|
data/lib/cattri/version.rb
CHANGED