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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8689b6e0fa1e741fc271fece58183191d290875fe203af5622f069aaf3c4cf7
4
- data.tar.gz: 2435ba9915a003f39d2d0275c725064c0c5c885940e7de109bb852a7258dfdd6
3
+ metadata.gz: 4558aae18122f29cebf5ddb34fe452ee6b00898d994b4cd8f2b0a825c5a599aa
4
+ data.tar.gz: '092f86968324144a229510426858c9a4cee0245267503a160e27b6087384e88e'
5
5
  SHA512:
6
- metadata.gz: 847f1396957fa8567868273f61c1906e95001e0fd8c632de72ed3f520ec4a7edb1c91c7c1f6a02101913c392d119b24a32a4d835827dab864338ab0e6b668574
7
- data.tar.gz: f7347d6cb14bb1b0501ff45799ba513388462ede2c95a47f84efb9d4a99ce7c9a6c6a8c726e605b4d475b26664c5f8861f8fc50922da4ebfbe4b139a257af131
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 :enabled, default: true
47
- cattr :timeout, default: -> { 5.0 }, instance_reader: false
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 :name, default: "anonymous"
51
- iattr :age, default: 0 do |val| # coercion block
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 (~200 LOC)**, dependency‑free, and purpose‑built for attribute declaration.
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 a class-level attribute with optional default, coercion, and reader access.
23
+ # Defines one or more class-level attributes with optional default, coercion, and reader access.
24
24
  #
25
- # @param name [Symbol] the attribute name
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
- def class_attribute(name, **options, &block)
36
- options[:access] ||= __cattri_visibility
37
- attribute = Cattri::Attribute.new(name, :class, options, block)
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
- Cattri::AttributeDefiner.define_callable_accessor(attribute, context)
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 name [Symbol]
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 class_attribute_reader(name, **options)
59
- class_attribute(name, readonly: true, **options)
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}]
@@ -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 attribute [Cattri::Attribute] the conflicting attribute
34
- def initialize(attribute)
35
- super("#{attribute.type.capitalize} attribute :#{attribute.name} has already been defined")
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 < Error
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 an instance-level attribute with optional default and coercion.
29
+ # Defines one or more instance-level attributes with optional default and coercion.
30
30
  #
31
- # @param name [Symbol, String] the name of the attribute
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
- def instance_attribute(name, **options, &block)
42
- options[:access] ||= __cattri_visibility
43
- attribute = Cattri::Attribute.new(name, :instance, options, block)
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
- begin
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 name [Symbol, String]
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(name, **options)
63
- instance_attribute(name, writer: false, **options)
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 name [Symbol, String]
71
- # @param options [Hash]
72
- # @yieldparam value [Object] optional coercion logic
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(name, **options, &block)
75
- instance_attribute(name, reader: false, **options, &block)
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}]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cattri
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
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.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-22 00:00:00.000000000 Z
11
+ date: 2025-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec