cattri 0.1.1 → 0.1.2
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 +23 -0
- data/README.md +49 -5
- data/lib/cattri/attribute_definer.rb +12 -0
- data/lib/cattri/class_attributes.rb +88 -19
- data/lib/cattri/context.rb +17 -1
- data/lib/cattri/error.rb +37 -4
- data/lib/cattri/instance_attributes.rb +98 -22
- data/lib/cattri/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4558aae18122f29cebf5ddb34fe452ee6b00898d994b4cd8f2b0a825c5a599aa
|
4
|
+
data.tar.gz: '092f86968324144a229510426858c9a4cee0245267503a160e27b6087384e88e'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61ac08cebe59853a0a4c163052bdabef6e70db7476bf0d93ff4a1184b706e326fab8583ad6b77533f7c297e5be15cd15cd3414ef8c751443d4f1ed8a06b768a6
|
7
|
+
data.tar.gz: dc18a8a83dc56d9a4c2b122c3f824808f8037f0d1cb08a2d71e82ef6aa933d73c75efa083df12cb4b5a7dfb30a0ce22c0558871fe07365d73b601b2e32b537fd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
## [0.1.2] - 2025-04-22
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
- Support for defining multiple attributes in a single call to `cattr` or `iattr`.
|
6
|
+
- Example: `cattr :foo, :bar, default: 1`
|
7
|
+
- Shared options apply to all attributes.
|
8
|
+
- Adds `cattr_setter` and `iattr_setter` for defining setters on attributes, useful when defining multiple attributes since ambiguous blocks are not allow.
|
9
|
+
```ruby
|
10
|
+
class Config
|
11
|
+
include Cattri
|
12
|
+
|
13
|
+
cattr :a, :b # new functionality, does not allow setter blocks.
|
14
|
+
# creates writers as def a=(val); @a = val; end
|
15
|
+
|
16
|
+
cattr_setter :a do |val| # redefines a= as def a=(val); val.to_s.downcase.to_sym; end
|
17
|
+
val.to_s.downcase.to_sym
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
21
|
+
- Validation to prevent use of a block when defining multiple attributes.
|
22
|
+
- Raises `Cattri::AmbiguousBlockError` if `&block` is passed with more than one attribute.
|
23
|
+
|
1
24
|
## [0.1.1] - 2025-04-22
|
2
25
|
|
3
26
|
### Added
|
data/README.md
CHANGED
@@ -43,12 +43,14 @@ class Config
|
|
43
43
|
include Cattri # exposes `cattr` & `iattr`
|
44
44
|
|
45
45
|
# -- class‑level ----------------------------------
|
46
|
-
cattr :
|
47
|
-
cattr :
|
46
|
+
cattr :flag_a, :flag_b, default: true
|
47
|
+
cattr :enabled, default: true
|
48
|
+
cattr :timeout, default: -> { 5.0 }, instance_reader: false
|
48
49
|
|
49
50
|
# -- instance‑level -------------------------------
|
50
|
-
iattr :
|
51
|
-
iattr :
|
51
|
+
iattr :item_a, :item_b, default: true
|
52
|
+
iattr :name, default: "anonymous"
|
53
|
+
iattr :age, default: 0 do |val| # coercion block
|
52
54
|
Integer(val)
|
53
55
|
end
|
54
56
|
end
|
@@ -95,6 +97,48 @@ If you pass a block, it’s treated as a **coercion setter** and receives the in
|
|
95
97
|
|
96
98
|
---
|
97
99
|
|
100
|
+
## Post-definition coercion with `*_setter`
|
101
|
+
|
102
|
+
If you define multiple attributes at once, you can't provide a coercion block inline:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
cattr :foo, :bar, default: nil # ❌ cannot use block here
|
106
|
+
```
|
107
|
+
|
108
|
+
Instead, define them first, then apply a coercion later using:
|
109
|
+
|
110
|
+
- `cattr_setter` for class attributes
|
111
|
+
- `iattr_setter` for instance attributes
|
112
|
+
|
113
|
+
These allow you to attach or override the setter logic after the fact:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class Config
|
117
|
+
include Cattri
|
118
|
+
|
119
|
+
cattr :log_level
|
120
|
+
cattr_setter :log_level do |val|
|
121
|
+
val.to_s.downcase.to_sym
|
122
|
+
end
|
123
|
+
|
124
|
+
iattr_writer :token
|
125
|
+
iattr_setter :token do |val|
|
126
|
+
val.strip
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
Coercion is only applied when the attribute is written (via `=` or callable form), not when read.
|
132
|
+
|
133
|
+
Attempting to use `*_setter` on an undefined attribute or one without a writer will raise:
|
134
|
+
|
135
|
+
- `Cattri::AttributeNotDefinedError` – the attribute doesn't exist or wasn't fully defined
|
136
|
+
- `Cattri::AttributeDefinitionError` – the attribute is marked as readonly
|
137
|
+
|
138
|
+
These APIs ensure your DSL stays consistent and extensible, even when bulk-declaring attributes up front.
|
139
|
+
|
140
|
+
---
|
141
|
+
|
98
142
|
## Visibility tracking
|
99
143
|
|
100
144
|
Cattri watches calls to `public`, `protected`, and `private` while you define methods:
|
@@ -180,7 +224,7 @@ end
|
|
180
224
|
* **ActiveSupport** extends the API but still relies on mutable class variables and offers no visibility control.
|
181
225
|
* **Dry‑configurable** is robust yet heavyweight when you only need a handful of attributes outside a full config object.
|
182
226
|
|
183
|
-
Cattri sits in the sweet spot: **micro‑sized (~
|
227
|
+
Cattri sits in the sweet spot: **micro‑sized (~300 LOC)**, dependency‑free, and purpose‑built for attribute declaration.
|
184
228
|
|
185
229
|
---
|
186
230
|
|
@@ -92,6 +92,18 @@ module Cattri
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
95
|
+
# Defines, or redefines, a writer method (`foo=`) that sets and coerces a value via the attribute setter.
|
96
|
+
#
|
97
|
+
# @param attribute [Cattri::Attribute]
|
98
|
+
# @param context [Cattri::Context]
|
99
|
+
# @return [void]
|
100
|
+
def define_writer!(attribute, context)
|
101
|
+
context.define_method!(attribute, name: :"#{attribute.name}=") do |value|
|
102
|
+
coerced_value = attribute.setter.call(value)
|
103
|
+
instance_variable_set(attribute.ivar, coerced_value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
95
107
|
private
|
96
108
|
|
97
109
|
# Returns the memoized value for an attribute or computes it from the default.
|
@@ -20,9 +20,20 @@ module Cattri
|
|
20
20
|
# Class attributes are stored internally as `Cattri::Attribute` instances and
|
21
21
|
# values are memoized using class-level instance variables.
|
22
22
|
module ClassAttributes
|
23
|
-
# Defines
|
23
|
+
# Defines one or more class-level attributes with optional default, coercion, and reader access.
|
24
24
|
#
|
25
|
-
#
|
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
|
26
37
|
# @param options [Hash] additional attribute options
|
27
38
|
# @option options [Object, Proc] :default the default value or lambda
|
28
39
|
# @option options [Boolean] :readonly whether the attribute is read-only
|
@@ -32,31 +43,59 @@ module Cattri
|
|
32
43
|
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
33
44
|
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
34
45
|
# already defined or an error occurs while defining methods)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
raise Cattri::AttributeDefinedError, attribute if class_attribute_defined?(attribute.name)
|
40
|
-
|
41
|
-
begin
|
42
|
-
__cattri_class_attributes[name] = attribute
|
46
|
+
# @return [void]
|
47
|
+
def class_attribute(*names, **options, &block)
|
48
|
+
raise Cattri::AmbiguousBlockError if names.size > 1 && block_given?
|
43
49
|
|
44
|
-
|
45
|
-
Cattri::AttributeDefiner.define_instance_level_reader(attribute, context) if attribute[:instance_reader]
|
46
|
-
rescue StandardError => e
|
47
|
-
raise Cattri::AttributeDefinitionError.new(self, attribute, e)
|
48
|
-
end
|
50
|
+
names.each { |name| define_class_attribute(name, options, block) }
|
49
51
|
end
|
50
52
|
|
51
53
|
# Defines a read-only class attribute.
|
52
54
|
#
|
53
55
|
# Equivalent to calling `class_attribute(name, readonly: true, ...)`
|
54
56
|
#
|
55
|
-
# @param
|
56
|
-
# @param options [Hash]
|
57
|
+
# @param names [Array<Symbol | String>] the names of the attributes to define
|
58
|
+
# @param options [Hash] additional attribute options
|
59
|
+
# @option options [Object, Proc] :default the default value or lambda
|
60
|
+
# @option options [Boolean] :readonly whether the attribute is read-only
|
61
|
+
# @option options [Boolean] :instance_reader whether to define an instance-level reader (default: true)
|
62
|
+
# @option options [Symbol] :access visibility level (:public, :protected, :private)
|
63
|
+
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
64
|
+
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
65
|
+
# already defined or an error occurs while defining methods)
|
66
|
+
# @return [void]
|
67
|
+
def class_attribute_reader(*names, **options)
|
68
|
+
class_attribute(*names, **options, readonly: true)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Updates the setter behavior of an existing class-level attribute.
|
72
|
+
#
|
73
|
+
# This allows coercion logic to be defined or overridden after the attribute
|
74
|
+
# has been declared using `cattr`, as long as the writer method exists.
|
75
|
+
#
|
76
|
+
# @example Add coercion to an existing attribute
|
77
|
+
# cattr :format
|
78
|
+
# cattr_setter :format do |val|
|
79
|
+
# val.to_s.downcase.to_sym
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# @param name [Symbol, String] the name of the attribute
|
83
|
+
# @yieldparam value [Object] the value passed to the setter
|
84
|
+
# @yieldreturn [Object] the coerced value to be assigned
|
85
|
+
# @raise [Cattri::AttributeNotDefinedError] if the attribute is not defined or the writer method does not exist
|
86
|
+
# @raise [Cattri::AttributeDefinitionError] if method redefinition fails
|
57
87
|
# @return [void]
|
58
|
-
def
|
59
|
-
|
88
|
+
def class_attribute_setter(name, &block)
|
89
|
+
name = name.to_sym
|
90
|
+
attribute = __cattri_class_attributes[name]
|
91
|
+
puts "<<< #{attribute} = #{name}>"
|
92
|
+
|
93
|
+
if attribute.nil? || !context.method_defined?(:"#{name}=")
|
94
|
+
raise Cattri::AttributeNotDefinedError.new(:class, name)
|
95
|
+
end
|
96
|
+
|
97
|
+
attribute.instance_variable_set(:@setter, attribute.send(:normalize_setter, block))
|
98
|
+
Cattri::AttributeDefiner.define_writer!(attribute, context)
|
60
99
|
end
|
61
100
|
|
62
101
|
# Returns a list of defined class attribute names.
|
@@ -116,6 +155,36 @@ module Cattri
|
|
116
155
|
|
117
156
|
private
|
118
157
|
|
158
|
+
# Defines a single class-level attribute.
|
159
|
+
#
|
160
|
+
# This is the internal implementation used by {.class_attribute} and its aliases.
|
161
|
+
# It constructs a `Cattri::Attribute`, registers it, and defines the necessary
|
162
|
+
# class and instance methods.
|
163
|
+
#
|
164
|
+
# @param name [Symbol] the name of the attribute to define
|
165
|
+
# @param options [Hash] additional attribute options (e.g., :default, :readonly)
|
166
|
+
# @param block [Proc, nil] an optional setter block for coercion
|
167
|
+
#
|
168
|
+
# @raise [Cattri::AttributeDefinedError] if the attribute has already been defined
|
169
|
+
# @raise [Cattri::AttributeDefinitionError] if method definition fails
|
170
|
+
#
|
171
|
+
# @return [void]
|
172
|
+
def define_class_attribute(name, options, block) # rubocop:disable Metrics/AbcSize
|
173
|
+
options[:access] ||= __cattri_visibility
|
174
|
+
attribute = Cattri::Attribute.new(name, :class, options, block)
|
175
|
+
|
176
|
+
raise Cattri::AttributeDefinedError.new(:class, name) if class_attribute_defined?(attribute.name)
|
177
|
+
|
178
|
+
begin
|
179
|
+
__cattri_class_attributes[name] = attribute
|
180
|
+
|
181
|
+
Cattri::AttributeDefiner.define_callable_accessor(attribute, context)
|
182
|
+
Cattri::AttributeDefiner.define_instance_level_reader(attribute, context) if attribute[:instance_reader]
|
183
|
+
rescue StandardError => e
|
184
|
+
raise Cattri::AttributeDefinitionError.new(self, attribute, e)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
119
188
|
# Internal registry of defined class-level attributes.
|
120
189
|
#
|
121
190
|
# @return [Hash{Symbol => Cattri::Attribute}]
|
data/lib/cattri/context.rb
CHANGED
@@ -63,11 +63,27 @@ module Cattri
|
|
63
63
|
name = (name || attribute.name).to_sym
|
64
64
|
return if method_defined?(name)
|
65
65
|
|
66
|
+
define_method!(attribute, name: name, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Defines a method on the target class or singleton class, regardless of whether it already exists.
|
70
|
+
#
|
71
|
+
# This bypasses any checks for prior definition and forcibly installs the method using `define_method`.
|
72
|
+
# The method will be assigned visibility based on the attribute's `:access` setting.
|
73
|
+
#
|
74
|
+
# Used internally by attribute definers to (re)define writers, readers, or callables.
|
75
|
+
#
|
76
|
+
# @param attribute [Cattri::Attribute] the attribute whose context and access rules apply
|
77
|
+
# @param name [Symbol, nil] the method name to define (defaults to attribute name)
|
78
|
+
# @yield the method body to define
|
79
|
+
# @raise [Cattri::AttributeDefinitionError] if method definition fails
|
80
|
+
# @return [void]
|
81
|
+
def define_method!(attribute, name: nil, &block)
|
66
82
|
target = target_for(attribute)
|
67
83
|
|
68
84
|
begin
|
69
85
|
target.define_method(name, &block)
|
70
|
-
@defined_methods << name
|
86
|
+
@defined_methods << name unless method_defined?(name)
|
71
87
|
apply_access(name, attribute)
|
72
88
|
rescue StandardError => e
|
73
89
|
raise Cattri::AttributeDefinitionError.new(target, attribute, e)
|
data/lib/cattri/error.rb
CHANGED
@@ -30,9 +30,31 @@ module Cattri
|
|
30
30
|
# rescue Cattri::AttributeDefinedError => e
|
31
31
|
# puts e.message
|
32
32
|
class AttributeDefinedError < Cattri::AttributeError
|
33
|
-
# @param
|
34
|
-
|
35
|
-
|
33
|
+
# @param type [Symbol, String] either :class or :instance
|
34
|
+
# @param name [Symbol, String] the name of the missing attribute
|
35
|
+
def initialize(type, name)
|
36
|
+
super("#{type.capitalize} attribute :#{name} has already been defined")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Raised when attempting to access or modify an attribute that has not been defined.
|
41
|
+
#
|
42
|
+
# This applies to both class-level and instance-level attributes.
|
43
|
+
# It is typically raised when calling `.class_attribute_setter` or `.instance_attribute_setter`
|
44
|
+
# on a name that does not exist or lacks the expected method (e.g., writer).
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# raise Cattri::AttributeNotDefinedError.new(:class, :foo)
|
48
|
+
# # => Class attribute :foo has not been defined
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# rescue Cattri::AttributeNotDefinedError => e
|
52
|
+
# puts e.message
|
53
|
+
class AttributeNotDefinedError < Cattri::AttributeError
|
54
|
+
# @param type [Symbol, String] either :class or :instance
|
55
|
+
# @param name [Symbol, String] the name of the missing attribute
|
56
|
+
def initialize(type, name)
|
57
|
+
super("#{type.capitalize} attribute :#{name} has not been defined")
|
36
58
|
end
|
37
59
|
end
|
38
60
|
|
@@ -63,10 +85,21 @@ module Cattri
|
|
63
85
|
# @example
|
64
86
|
# raise Cattri::UnsupportedTypeError.new(:foo)
|
65
87
|
# # => Attribute type :foo is not supported
|
66
|
-
class UnsupportedTypeError <
|
88
|
+
class UnsupportedTypeError < Cattri::AttributeError
|
67
89
|
# @param type [Symbol] the invalid type that triggered the error
|
68
90
|
def initialize(type)
|
69
91
|
super("Attribute type :#{type} is not supported")
|
70
92
|
end
|
71
93
|
end
|
94
|
+
|
95
|
+
# Raised when a block is provided when defining a group of attributes `cattr :attr_a, :attr_b do ... end`
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# raise Cattri::AmbiguousBlockError
|
99
|
+
# # => Cannot define multiple attributes with a block
|
100
|
+
class AmbiguousBlockError < Cattri::AttributeError
|
101
|
+
def initialize
|
102
|
+
super("Cannot define multiple attributes with a block")
|
103
|
+
end
|
104
|
+
end
|
72
105
|
end
|
@@ -26,9 +26,20 @@ module Cattri
|
|
26
26
|
|
27
27
|
# Defines instance-level attribute DSL methods.
|
28
28
|
module ClassMethods
|
29
|
-
# Defines
|
29
|
+
# Defines one or more instance-level attributes with optional default and coercion.
|
30
30
|
#
|
31
|
-
#
|
31
|
+
# This method supports defining multiple attributes at once, provided they share the same options.
|
32
|
+
# If a block is given, only one attribute may be defined to avoid ambiguity.
|
33
|
+
#
|
34
|
+
# @example Define multiple attributes with shared defaults
|
35
|
+
# iattr :foo, :bar, default: []
|
36
|
+
#
|
37
|
+
# @example Define a single attribute with coercion
|
38
|
+
# iattr :level do |val|
|
39
|
+
# Integer(val)
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @param names [Array<Symbol | String>] the names of the attributes to define
|
32
43
|
# @param options [Hash] additional options like `:default`, `:reader`, `:writer`
|
33
44
|
# @option options [Object, Proc] :default the default value or lambda
|
34
45
|
# @option options [Boolean] :reader whether to define a reader method (default: true)
|
@@ -38,41 +49,74 @@ module Cattri
|
|
38
49
|
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
39
50
|
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
40
51
|
# already defined or an error occurs while defining methods)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
raise Cattri::AttributeDefinedError, attribute if instance_attribute_defined?(attribute.name)
|
52
|
+
# @return [void]
|
53
|
+
def instance_attribute(*names, **options, &block)
|
54
|
+
raise Cattri::AmbiguousBlockError if names.size > 1 && block_given?
|
46
55
|
|
47
|
-
|
48
|
-
__cattri_instance_attributes[name.to_sym] = attribute
|
49
|
-
Cattri::AttributeDefiner.define_accessor(attribute, context)
|
50
|
-
rescue StandardError => e
|
51
|
-
raise Cattri::AttributeDefinitionError.new(self, attribute, e)
|
52
|
-
end
|
56
|
+
names.each { |name| define_instance_attribute(name, options, block) }
|
53
57
|
end
|
54
58
|
|
55
59
|
# Defines a read-only instance-level attribute.
|
56
60
|
#
|
57
61
|
# Equivalent to `instance_attribute(..., writer: false)`
|
58
62
|
#
|
59
|
-
# @param
|
60
|
-
# @param options [Hash]
|
63
|
+
# @param names [Array<Symbol | String>] the names of the attributes to define
|
64
|
+
# @param options [Hash] additional options like `:default`, `:reader`, `:writer`
|
65
|
+
# @option options [Object, Proc] :default the default value or lambda
|
66
|
+
# @option options [Boolean] :reader whether to define a reader method (default: true)
|
67
|
+
# @option options [Symbol] :access method visibility (:public, :protected, :private)
|
68
|
+
# @yieldparam value [Object] optional custom coercion logic for the setter
|
69
|
+
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
70
|
+
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
71
|
+
# already defined or an error occurs while defining methods)
|
61
72
|
# @return [void]
|
62
|
-
def instance_attribute_reader(
|
63
|
-
instance_attribute(
|
73
|
+
def instance_attribute_reader(*names, **options)
|
74
|
+
instance_attribute(*names, **options, writer: false)
|
64
75
|
end
|
65
76
|
|
66
77
|
# Defines a write-only instance-level attribute.
|
67
78
|
#
|
68
79
|
# Equivalent to `instance_attribute(..., reader: false)`
|
69
80
|
#
|
70
|
-
# @param
|
71
|
-
# @param options [Hash]
|
72
|
-
# @
|
81
|
+
# @param names [Array<Symbol | String>] the names of the attributes to define
|
82
|
+
# @param options [Hash] additional options like `:default`, `:reader`, `:writer`
|
83
|
+
# @option options [Object, Proc] :default the default value or lambda
|
84
|
+
# @option options [Boolean] :writer whether to define a writer method (default: true)
|
85
|
+
# @option options [Symbol] :access method visibility (:public, :protected, :private)
|
86
|
+
# @yieldparam value [Object] optional custom coercion logic for the setter
|
87
|
+
# @raise [Cattri::AttributeError] or its subclasses, including `Cattri::AttributeDefinedError` or
|
88
|
+
# `Cattri::AttributeDefinitionError` if defining the attribute fails (e.g., if the attribute is
|
89
|
+
# already defined or an error occurs while defining methods)
|
73
90
|
# @return [void]
|
74
|
-
def instance_attribute_writer(
|
75
|
-
instance_attribute(
|
91
|
+
def instance_attribute_writer(*names, **options, &block)
|
92
|
+
instance_attribute(*names, **options.merge(reader: false), &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Updates the setter behavior of an existing instance-level attribute.
|
96
|
+
#
|
97
|
+
# This allows coercion logic to be defined or overridden after the attribute
|
98
|
+
# has been declared using `iattr`, as long as the writer method exists.
|
99
|
+
#
|
100
|
+
# @example Add coercion to an existing attribute
|
101
|
+
# iattr :format
|
102
|
+
# iattr_setter :format do |val|
|
103
|
+
# val.to_s.downcase.to_sym
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# @param name [Symbol, String] the name of the attribute
|
107
|
+
# @yieldparam value [Object] the value passed to the setter
|
108
|
+
# @yieldreturn [Object] the coerced value to be assigned
|
109
|
+
# @raise [Cattri::AttributeNotDefinedError] if the attribute is not defined or the writer method does not exist
|
110
|
+
# @raise [Cattri::AttributeDefinitionError] if method redefinition fails
|
111
|
+
# @return [void]
|
112
|
+
def instance_attribute_setter(name, &block)
|
113
|
+
attribute = __cattri_instance_attributes[name.to_sym]
|
114
|
+
|
115
|
+
raise Cattri::AttributeNotDefinedError.new(:instance, name) if attribute.nil?
|
116
|
+
raise Cattri::AttributeError, "Cannot define setter for readonly attribute :#{name}" unless attribute[:writer]
|
117
|
+
|
118
|
+
attribute.instance_variable_set(:@setter, attribute.send(:normalize_setter, block))
|
119
|
+
Cattri::AttributeDefiner.define_writer!(attribute, context)
|
76
120
|
end
|
77
121
|
|
78
122
|
# Returns a list of defined instance-level attribute names.
|
@@ -114,6 +158,10 @@ module Cattri
|
|
114
158
|
# Alias for {#instance_attribute_writer}
|
115
159
|
alias iattr_writer instance_attribute_writer
|
116
160
|
|
161
|
+
# @!method iattr_setter(name, &block)
|
162
|
+
# Alias for {#instance_attribute_setter}
|
163
|
+
alias iattr_setter instance_attribute_setter
|
164
|
+
|
117
165
|
# @!method iattrs
|
118
166
|
# Alias for {#instance_attributes}
|
119
167
|
alias iattrs instance_attributes
|
@@ -128,6 +176,34 @@ module Cattri
|
|
128
176
|
|
129
177
|
private
|
130
178
|
|
179
|
+
# Defines a single instance-level attribute.
|
180
|
+
#
|
181
|
+
# This is the internal implementation used by {.instance_attribute} and its aliases.
|
182
|
+
# It creates a `Cattri::Attribute`, registers it, and defines the appropriate
|
183
|
+
# reader and/or writer methods on the class.
|
184
|
+
#
|
185
|
+
# @param name [Symbol, String] the attribute name
|
186
|
+
# @param options [Hash] additional options for the attribute
|
187
|
+
# @param block [Proc, nil] optional setter coercion logic
|
188
|
+
#
|
189
|
+
# @raise [Cattri::AttributeDefinedError] if the attribute has already been defined
|
190
|
+
# @raise [Cattri::AttributeDefinitionError] if method definition fails
|
191
|
+
#
|
192
|
+
# @return [void]
|
193
|
+
def define_instance_attribute(name, options, block)
|
194
|
+
options[:access] ||= __cattri_visibility
|
195
|
+
attribute = Cattri::Attribute.new(name, :instance, options, block)
|
196
|
+
|
197
|
+
raise Cattri::AttributeDefinedError.new(:instance, name) if instance_attribute_defined?(attribute.name)
|
198
|
+
|
199
|
+
begin
|
200
|
+
__cattri_instance_attributes[name.to_sym] = attribute
|
201
|
+
Cattri::AttributeDefiner.define_accessor(attribute, context)
|
202
|
+
rescue StandardError => e
|
203
|
+
raise Cattri::AttributeDefinitionError.new(self, attribute, e)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
131
207
|
# Internal registry of instance attributes defined on the class.
|
132
208
|
#
|
133
209
|
# @return [Hash{Symbol => Cattri::Attribute}]
|
data/lib/cattri/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cattri
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
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-
|
11
|
+
date: 2025-04-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|