cattri 0.1.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 +7 -0
- data/.idea/workspace.xml +350 -0
- data/.rspec +3 -0
- data/.rspec_status +75 -0
- data/.rubocop.yml +36 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +153 -0
- data/Rakefile +12 -0
- data/cattri.gemspec +40 -0
- data/lib/cattri/class_attributes.rb +246 -0
- data/lib/cattri/error.rb +5 -0
- data/lib/cattri/helpers.rb +75 -0
- data/lib/cattri/instance_attributes.rb +198 -0
- data/lib/cattri/introspection.rb +70 -0
- data/lib/cattri/version.rb +5 -0
- data/lib/cattri.rb +36 -0
- data/sig/cattri.rbs +4 -0
- metadata +150 -0
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# Cattri
|
2
|
+
|
3
|
+
Cattri is a lightweight Ruby DSL for defining class-level and instance-level attributes with optional defaults, coercion, and reset capabilities.
|
4
|
+
|
5
|
+
It provides fine-grained control over attribute behavior, including:
|
6
|
+
|
7
|
+
- Class-level attributes (`cattr`) with optional instance accessors
|
8
|
+
- Instance-level attributes (`iattr`) with coercion and lazy defaults
|
9
|
+
- Optional locking of class attribute definitions to prevent subclass redefinition
|
10
|
+
- Simple, expressive DSL for reusable metaprogramming
|
11
|
+
|
12
|
+
## โจ Features
|
13
|
+
|
14
|
+
- โ
Define readable/writable class and instance attributes
|
15
|
+
- ๐งฑ Static or callable default values
|
16
|
+
- ๐ Optional coercion logic via blocks
|
17
|
+
- ๐งผ Reset attributes to default
|
18
|
+
- ๐ Lock class attribute definitions in base class
|
19
|
+
- ๐ Introspect attribute definitions and values (optional)
|
20
|
+
|
21
|
+
## ๐ฆ Installation
|
22
|
+
|
23
|
+
```bash
|
24
|
+
bundle add cattri
|
25
|
+
```
|
26
|
+
|
27
|
+
Or add to your Gemfile:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem "cattri"
|
31
|
+
```
|
32
|
+
|
33
|
+
## ๐ Usage
|
34
|
+
|
35
|
+
### Class & Instance Attributes
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class MyConfig
|
39
|
+
include Cattri
|
40
|
+
|
41
|
+
cattr :enabled, default: true
|
42
|
+
iattr :name, default: "anonymous"
|
43
|
+
end
|
44
|
+
|
45
|
+
MyConfig.enabled # => true
|
46
|
+
MyConfig.new.name # => "anonymous"
|
47
|
+
```
|
48
|
+
|
49
|
+
### Class Attributes
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class MyConfig
|
53
|
+
extend Cattri::ClassAttributes
|
54
|
+
|
55
|
+
cattr :format, default: :json
|
56
|
+
cattr_reader :version, default: "1.0.0"
|
57
|
+
cattr :enabled, default: true do |value|
|
58
|
+
!!value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
MyConfig.format # => :json
|
63
|
+
MyConfig.format :xml
|
64
|
+
MyConfig.format # => :xml
|
65
|
+
|
66
|
+
MyConfig.version # => "1.0.0"
|
67
|
+
```
|
68
|
+
|
69
|
+
#### Instance Access
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
MyConfig.new.format # => :xml
|
73
|
+
```
|
74
|
+
|
75
|
+
#### Locking Class Attribute Definitions
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
MyConfig.lock_cattrs!
|
79
|
+
```
|
80
|
+
|
81
|
+
This prevents redefinition of existing class attributes in subclasses.
|
82
|
+
|
83
|
+
### Instance Attributes
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
class Request
|
87
|
+
include Cattri::InstanceAttributes
|
88
|
+
|
89
|
+
iattr :headers, default: -> { {} }
|
90
|
+
iattr_writer :raw_body do |val|
|
91
|
+
val.to_s.strip
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
req = Request.new
|
96
|
+
req.headers["Content-Type"] = "application/json"
|
97
|
+
req.raw_body = " data "
|
98
|
+
```
|
99
|
+
|
100
|
+
### Resetting Attributes
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
MyConfig.reset_cattrs! # Reset all class attributes
|
104
|
+
MyConfig.reset_cattr!(:format)
|
105
|
+
|
106
|
+
req.reset_iattr!(:headers) # Reset a specific instance attribute
|
107
|
+
```
|
108
|
+
|
109
|
+
## ๐ Introspection
|
110
|
+
|
111
|
+
If you include the `Cattri::Introspection` module:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class MyConfig
|
115
|
+
include Cattri
|
116
|
+
include Cattri::Introspection
|
117
|
+
|
118
|
+
cattr :items, default: []
|
119
|
+
end
|
120
|
+
|
121
|
+
MyConfig.items << :a
|
122
|
+
MyConfig.snapshot_class_attributes # => { items: [:a] }
|
123
|
+
```
|
124
|
+
|
125
|
+
## ๐ API Overview
|
126
|
+
|
127
|
+
| Method | Description |
|
128
|
+
|----------------------------------|--------------------------------------------|
|
129
|
+
| `cattr`, `cattr_reader` | Define class-level attributes |
|
130
|
+
| `iattr`, `iattr_reader`, `iattr_writer` | Define instance-level attributes |
|
131
|
+
| `reset_cattr!`, `reset_iattr!` | Reset specific attributes |
|
132
|
+
| `cattr_definition(:name)` | Get attribute metadata |
|
133
|
+
| `lock_cattrs!` | Prevent redefinition in subclasses |
|
134
|
+
|
135
|
+
## ๐งช Testing
|
136
|
+
|
137
|
+
```bash
|
138
|
+
bundle exec rspec
|
139
|
+
```
|
140
|
+
|
141
|
+
## ๐ก Why Cattri?
|
142
|
+
|
143
|
+
Cattri provides a cleaner alternative to `class_attribute`, `attr_accessor`, and configuration gems like `Dry::Configurable` or `ActiveSupport::Configurable`, without monkey-patching or runtime surprises.
|
144
|
+
|
145
|
+
## ๐ License
|
146
|
+
|
147
|
+
MIT ยฉ [Nathan Lucas](https://github.com/bnlucas). See [LICENSE](LICENSE).
|
148
|
+
|
149
|
+
---
|
150
|
+
|
151
|
+
## ๐ Credits
|
152
|
+
|
153
|
+
Created with โค๏ธ by [Nathan Lucas](https://github.com/bnlucas)
|
data/Rakefile
ADDED
data/cattri.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/cattri/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "cattri"
|
7
|
+
spec.version = Cattri::VERSION
|
8
|
+
spec.authors = ["Nathan Lucas"]
|
9
|
+
spec.email = ["bnlucas@outlook.com"]
|
10
|
+
|
11
|
+
spec.summary = "Simple class and instance attribute DSL for Ruby."
|
12
|
+
spec.description = "Cattri provides a clean DSL for defining class-level and instance-level attributes " \
|
13
|
+
"with optional defaults, coercion, accessors, and inheritance support."
|
14
|
+
spec.homepage = "https://github.com/bnlucas/cattri"
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = ">= 2.7.0"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/bnlucas/cattri"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/bnlucas/cattri/blob/main/CHANGELOG.md"
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
22
|
+
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
(File.expand_path(f) == __FILE__) ||
|
26
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Runtime dependencies
|
31
|
+
# spec.add_dependency "gem"
|
32
|
+
|
33
|
+
# Development dependencies
|
34
|
+
spec.add_development_dependency "rspec"
|
35
|
+
spec.add_development_dependency "rubocop"
|
36
|
+
spec.add_development_dependency "simplecov"
|
37
|
+
spec.add_development_dependency "simplecov-cobertura"
|
38
|
+
spec.add_development_dependency "simplecov-html"
|
39
|
+
spec.add_development_dependency "yard"
|
40
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "error"
|
4
|
+
require_relative "helpers"
|
5
|
+
|
6
|
+
module Cattri
|
7
|
+
# Provides a DSL for defining class-level attributes with support for:
|
8
|
+
#
|
9
|
+
# - Static or dynamic default values
|
10
|
+
# - Optional coercion via setter blocks
|
11
|
+
# - Optional instance-level readers
|
12
|
+
# - Read-only attribute enforcement
|
13
|
+
# - Inheritance-safe duplication
|
14
|
+
# - Attribute locking to prevent mutation in subclasses
|
15
|
+
#
|
16
|
+
# This module is designed for advanced metaprogramming needs such as DSL builders,
|
17
|
+
# configuration objects, and plugin systems that require reusable and introspectable
|
18
|
+
# class-level state.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# class MyClass
|
22
|
+
# extend Cattri::ClassAttributes
|
23
|
+
#
|
24
|
+
# cattr :format, default: :json
|
25
|
+
# cattr_reader :version, default: "1.0.0"
|
26
|
+
# cattr :enabled, default: true do |value|
|
27
|
+
# !!value
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# MyClass.format # => :json
|
32
|
+
# MyClass.format :xml
|
33
|
+
# MyClass.format # => :xml
|
34
|
+
# MyClass.version # => "1.0.0"
|
35
|
+
#
|
36
|
+
# instance = MyClass.new
|
37
|
+
# instance.format # => :xml
|
38
|
+
module ClassAttributes
|
39
|
+
include Cattri::Helpers
|
40
|
+
|
41
|
+
# Default options applied to all class-level attributes.
|
42
|
+
DEFAULT_OPTIONS = { default: nil, readonly: false }.freeze
|
43
|
+
|
44
|
+
# Defines a class-level attribute with optional default, coercion, and reader access.
|
45
|
+
#
|
46
|
+
# @param name [Symbol] the attribute name
|
47
|
+
# @param options [Hash] additional attribute options
|
48
|
+
# @option options [Object, Proc] :default the default value or callable
|
49
|
+
# @option options [Boolean] :readonly whether the attribute is read-only
|
50
|
+
# @option options [Boolean] :instance_reader whether to define an instance-level reader
|
51
|
+
# @yield [*args] Optional setter block for custom coercion
|
52
|
+
# @raise [Cattri::Error] if attribute is already defined
|
53
|
+
# @return [void]
|
54
|
+
def class_attribute(name, **options, &block)
|
55
|
+
define_inheritance unless respond_to?(:__cattri_class_attributes)
|
56
|
+
|
57
|
+
name, definition = define_attribute(name, options, block, DEFAULT_OPTIONS)
|
58
|
+
raise Cattri::Error, "Class attribute `#{name}` already defined" if class_attribute_defined?(name)
|
59
|
+
|
60
|
+
__cattri_class_attributes[name] = definition
|
61
|
+
define_accessor(name, definition)
|
62
|
+
define_instance_reader(name) if options.fetch(:instance_reader, true)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Defines a read-only class attribute (no writer).
|
66
|
+
#
|
67
|
+
# @param name [Symbol]
|
68
|
+
# @param options [Hash]
|
69
|
+
# @return [void]
|
70
|
+
def class_attribute_reader(name, **options)
|
71
|
+
class_attribute(name, readonly: true, **options)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns all defined class-level attribute names.
|
75
|
+
#
|
76
|
+
# @return [Array<Symbol>]
|
77
|
+
def class_attributes
|
78
|
+
__cattri_class_attributes.keys
|
79
|
+
end
|
80
|
+
|
81
|
+
# Checks whether a class-level attribute is defined.
|
82
|
+
#
|
83
|
+
# @param name [Symbol]
|
84
|
+
# @return [Boolean]
|
85
|
+
def class_attribute_defined?(name)
|
86
|
+
__cattri_class_attributes.key?(name.to_sym)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns metadata for a given class-level attribute.
|
90
|
+
#
|
91
|
+
# @param name [Symbol]
|
92
|
+
# @return [Hash, nil]
|
93
|
+
def class_attribute_definition(name)
|
94
|
+
__cattri_class_attributes[name.to_sym]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Resets all defined class attributes to their default values.
|
98
|
+
#
|
99
|
+
# @return [void]
|
100
|
+
def reset_class_attributes!
|
101
|
+
reset_attributes!(self, __cattri_class_attributes.values)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Resets a single class attribute to its default value.
|
105
|
+
#
|
106
|
+
# @param name [Symbol]
|
107
|
+
# @return [void]
|
108
|
+
def reset_class_attribute!(name)
|
109
|
+
definition = __cattri_class_attributes[name]
|
110
|
+
return unless definition
|
111
|
+
|
112
|
+
reset_attributes!(self, [definition])
|
113
|
+
end
|
114
|
+
|
115
|
+
# alias lock_cattrs! lock_class_attributes!
|
116
|
+
# alias cattrs_locked? class_attributes_locked?
|
117
|
+
|
118
|
+
# @!method cattr(name, **options, &block)
|
119
|
+
# Alias for {.class_attribute}
|
120
|
+
# @see .class_attribute
|
121
|
+
alias cattr class_attribute
|
122
|
+
|
123
|
+
# @!method cattr_accessor(name, **options, &block)
|
124
|
+
# Alias for {.class_attribute}
|
125
|
+
# @see .class_attribute
|
126
|
+
alias cattr_accessor class_attribute
|
127
|
+
|
128
|
+
# @!method cattr_reader(name, **options)
|
129
|
+
# Alias for {.class_attribute_reader}
|
130
|
+
# @see .class_attribute_reader
|
131
|
+
alias cattr_reader class_attribute_reader
|
132
|
+
|
133
|
+
# @!method cattrs
|
134
|
+
# @return [Array<Symbol>] all defined class attribute names
|
135
|
+
# @see .class_attributes
|
136
|
+
alias cattrs class_attributes
|
137
|
+
|
138
|
+
# @!method cattr_defined?(name)
|
139
|
+
# @return [Boolean] whether the given attribute has been defined
|
140
|
+
# @see .class_attribute_defined?
|
141
|
+
alias cattr_defined? class_attribute_defined?
|
142
|
+
|
143
|
+
# @!method cattr_for(name)
|
144
|
+
# @return [Hash, nil] the internal metadata hash for a defined attribute
|
145
|
+
# @see .class_attribute_for
|
146
|
+
alias cattr_definition class_attribute_definition
|
147
|
+
|
148
|
+
# @!method reset_cattrs!
|
149
|
+
# Resets all class attributes to their default values.
|
150
|
+
# @see .reset_class_attributes!
|
151
|
+
alias reset_cattrs! reset_class_attributes!
|
152
|
+
|
153
|
+
# @!method reset_cattr!(name)
|
154
|
+
# Resets a specific class attribute to its default value.
|
155
|
+
# @see .reset_class_attribute!
|
156
|
+
alias reset_cattr! reset_class_attribute!
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
# Defines class-level inheritance behavior for declared attributes.
|
161
|
+
#
|
162
|
+
# @return [void]
|
163
|
+
def define_inheritance
|
164
|
+
unless singleton_class.method_defined?(:__cattri_class_attributes)
|
165
|
+
define_singleton_method(:__cattri_class_attributes) { @__cattri_class_attributes ||= {} }
|
166
|
+
end
|
167
|
+
|
168
|
+
define_singleton_method(:inherited) do |subclass|
|
169
|
+
super(subclass)
|
170
|
+
subclass_attributes = {}
|
171
|
+
|
172
|
+
__cattri_class_attributes.each do |name, definition|
|
173
|
+
apply_attribute!(subclass, subclass_attributes, name, definition)
|
174
|
+
end
|
175
|
+
|
176
|
+
subclass.instance_variable_set(:@__cattri_class_attributes, subclass_attributes)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Defines the primary accessor method on the class.
|
181
|
+
#
|
182
|
+
# @param name [Symbol]
|
183
|
+
# @param definition [Hash]
|
184
|
+
# @return [void]
|
185
|
+
def define_accessor(name, definition)
|
186
|
+
ivar = definition[:ivar]
|
187
|
+
|
188
|
+
define_singleton_method(name) do |*args, **kwargs|
|
189
|
+
readonly = readonly_call?(args, kwargs) || definition[:readonly]
|
190
|
+
return apply_readonly(ivar, definition[:default]) if readonly
|
191
|
+
|
192
|
+
instance_variable_set(ivar, definition[:setter].call(*args, **kwargs))
|
193
|
+
end
|
194
|
+
|
195
|
+
return if definition[:readonly]
|
196
|
+
|
197
|
+
define_singleton_method("#{name}=") do |value|
|
198
|
+
instance_variable_set(ivar, definition[:setter].call(value))
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Defines an instance-level reader that delegates to the class-level method.
|
203
|
+
#
|
204
|
+
# @param name [Symbol]
|
205
|
+
# @return [void]
|
206
|
+
def define_instance_reader(name)
|
207
|
+
define_method(name) { self.class.__send__(name) }
|
208
|
+
end
|
209
|
+
|
210
|
+
# Applies the default value for a read-only call.
|
211
|
+
#
|
212
|
+
# @param ivar [Symbol]
|
213
|
+
# @param default [Proc]
|
214
|
+
# @return [Object]
|
215
|
+
def apply_readonly(ivar, default)
|
216
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
217
|
+
|
218
|
+
value = default.call
|
219
|
+
instance_variable_set(ivar, value)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Applies inherited attribute definitions to a subclass.
|
223
|
+
#
|
224
|
+
# @param subclass [Class]
|
225
|
+
# @param attributes [Hash]
|
226
|
+
# @param name [Symbol]
|
227
|
+
# @param definition [Hash]
|
228
|
+
# @return [void]
|
229
|
+
def apply_attribute!(subclass, attributes, name, definition)
|
230
|
+
value = instance_variable_get(definition[:ivar])
|
231
|
+
value = value.dup rescue value # rubocop:disable Style/RescueModifier
|
232
|
+
|
233
|
+
subclass.instance_variable_set(definition[:ivar], value)
|
234
|
+
attributes[name] = definition
|
235
|
+
end
|
236
|
+
|
237
|
+
# Determines if the method call should be treated as read-only access.
|
238
|
+
#
|
239
|
+
# @param args [Array]
|
240
|
+
# @param kwargs [Hash]
|
241
|
+
# @return [Boolean]
|
242
|
+
def readonly_call?(args, kwargs)
|
243
|
+
args.empty? && kwargs.empty?
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
data/lib/cattri/error.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cattri
|
4
|
+
# Internal support utilities for Cattri modules.
|
5
|
+
#
|
6
|
+
# Provides shared logic for safe default handling, attribute definition, and
|
7
|
+
# consistent reset behavior across class-level and instance-level attributes.
|
8
|
+
#
|
9
|
+
# This module is intended for internal use only and is included by both
|
10
|
+
# Cattri::ClassAttributes and Cattri::InstanceAttributes.
|
11
|
+
module Helpers
|
12
|
+
# A list of immutable Ruby types that are safe to reuse directly without duplication.
|
13
|
+
#
|
14
|
+
# These types are treated as-is when used as default values.
|
15
|
+
#
|
16
|
+
# @return [Array<Class>]
|
17
|
+
SAFE_VALUE_TYPES = [Numeric, Symbol, TrueClass, FalseClass, NilClass].freeze
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
# Defines an attribute structure used internally for accessors.
|
22
|
+
#
|
23
|
+
# Combines the caller's options with normalized default and setter logic,
|
24
|
+
# and attaches a consistent `@ivar` key for storage.
|
25
|
+
#
|
26
|
+
# @param name [Symbol, String] The attribute name
|
27
|
+
# @param options [Hash] The caller-provided options (e.g., `:default`, `:readonly`)
|
28
|
+
# @param block [Proc, nil] Optional setter block
|
29
|
+
# @param defaults [Hash] A set of fallback/default values to merge in
|
30
|
+
# @return [Array] Normalized name and attribute definition hash
|
31
|
+
def define_attribute(name, options, block, defaults)
|
32
|
+
options[:default] = normalize_default(options[:default])
|
33
|
+
options[:setter] = block || lambda { |*args, **kwargs|
|
34
|
+
return kwargs unless kwargs.empty?
|
35
|
+
return args.first if args.length == 1
|
36
|
+
|
37
|
+
args
|
38
|
+
}
|
39
|
+
|
40
|
+
name = name.to_sym
|
41
|
+
[name, defaults.merge(ivar: :"@#{name}", **options)]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Wraps static default values in lambdas to ensure safety.
|
45
|
+
#
|
46
|
+
# If the value is already callable, it is returned as-is.
|
47
|
+
# If the value is immutable, it is wrapped directly.
|
48
|
+
# Otherwise, it is wrapped with `.dup` for safe reuse.
|
49
|
+
#
|
50
|
+
# @param default [Object, Proc, nil] The user-provided default value
|
51
|
+
# @return [Proc] A proc that returns a safe default value
|
52
|
+
def normalize_default(default)
|
53
|
+
return default if default.respond_to?(:call)
|
54
|
+
return -> { default } if default.frozen? || SAFE_VALUE_TYPES.any? { |type| default.is_a?(type) }
|
55
|
+
|
56
|
+
-> { default.dup }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Resets a set of attribute definitions on a target object.
|
60
|
+
#
|
61
|
+
# Used to restore class or instance attributes to their configured default values.
|
62
|
+
#
|
63
|
+
# @param target [Object] The object or class whose instance variables will be reset
|
64
|
+
# @param attribute_definitions [Enumerable<Hash>] A list of attribute definition hashes
|
65
|
+
# @return [void]
|
66
|
+
def reset_attributes!(target, attribute_definitions)
|
67
|
+
attribute_definitions.each do |definition|
|
68
|
+
target.instance_variable_set(
|
69
|
+
definition[:ivar],
|
70
|
+
definition[:default].call
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|