u-attributes 2.0.0 → 2.3.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/.travis.yml +1 -2
- data/Gemfile +10 -1
- data/README.md +338 -137
- data/assets/u-attributes_logo_v1.png +0 -0
- data/lib/micro/attributes.rb +65 -20
- data/lib/micro/attributes/features/activemodel_validations.rb +2 -2
- data/lib/micro/attributes/features/initialize/strict.rb +6 -26
- data/lib/micro/attributes/macros.rb +40 -8
- data/lib/micro/attributes/utils.rb +43 -8
- data/lib/micro/attributes/version.rb +1 -1
- data/lib/micro/attributes/with.rb +1 -1
- data/u-attributes.gemspec +5 -2
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f90839cb67a0501e1ef44a5f06a324103063c87e929549377f885504bda5d8a
|
4
|
+
data.tar.gz: fd77058f7e514644a8363d460ad33ad76885648e32b5d9b02e22e592ffbf3ffe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27401e952c4f5611ec5443df546d4c2c67f0af7aa40606a8e0f128ae37dc167747d0ba4c94008d993d65c886d4e1d66c6ef3472556ecfa353903779f51b4fe23
|
7
|
+
data.tar.gz: d05025bd7442681e14fa8f970fb166898ec9af4a344b8912e10c57f559830833bebe05ccacea85332ef9c88437adabc60152967f8b0b38d01f38f1cc6a7c1257
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
+
gem 'u-case', '~> 4.0'
|
4
|
+
|
3
5
|
activemodel_version = ENV.fetch('ACTIVEMODEL_VERSION', '6.1')
|
4
6
|
|
5
7
|
activemodel = case activemodel_version
|
@@ -18,9 +20,16 @@ if activemodel_version < '6.1'
|
|
18
20
|
gem 'activesupport', activemodel, require: false
|
19
21
|
end
|
20
22
|
|
23
|
+
simplecov_version =
|
24
|
+
case RUBY_VERSION
|
25
|
+
when /\A2.[23]/ then '~> 0.17.1'
|
26
|
+
when /\A2.4/ then '~> 0.18.5'
|
27
|
+
else '~> 0.19'
|
28
|
+
end
|
29
|
+
|
21
30
|
group :test do
|
22
31
|
gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
|
23
|
-
gem 'simplecov', require: false
|
32
|
+
gem 'simplecov', simplecov_version, require: false
|
24
33
|
end
|
25
34
|
|
26
35
|
# Specify your gem's dependencies in u-attributes.gemspec
|
data/README.md
CHANGED
@@ -1,21 +1,41 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
[](https://travis-ci.com/serradura/u-attributes)
|
4
|
-
[](https://codeclimate.com/github/serradura/u-attributes/maintainability)
|
5
|
-
[](https://codeclimate.com/github/serradura/u-attributes/test_coverage)
|
1
|
+
<p align="center">
|
2
|
+
<img src="./assets/u-attributes_logo_v1.png" alt='Create "immutable" objects. No setters, just getters!'>
|
6
3
|
|
7
|
-
|
8
|
-
|
4
|
+
<p align="center"><i>Create "immutable" objects. No setters, just getters!</i></p>
|
5
|
+
<br>
|
6
|
+
</p>
|
9
7
|
|
10
|
-
|
8
|
+
<p align="center">
|
9
|
+
<img src="https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
<a href="https://rubygems.org/gems/u-attributes">
|
12
|
+
<img alt="Gem" src="https://img.shields.io/gem/v/u-attributes.svg?style=flat-square">
|
13
|
+
</a>
|
14
|
+
|
15
|
+
<a href="https://travis-ci.com/serradura/u-attributes">
|
16
|
+
<img alt="Build Status" src="https://travis-ci.com/serradura/u-attributes.svg?branch=main">
|
17
|
+
</a>
|
18
|
+
|
19
|
+
<a href="https://codeclimate.com/github/serradura/u-attributes/maintainability">
|
20
|
+
<img alt="Maintainability" src="https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/maintainability">
|
21
|
+
</a>
|
22
|
+
|
23
|
+
<a href="https://codeclimate.com/github/serradura/u-attributes/test_coverage">
|
24
|
+
<img alt="Test Coverage" src="https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/test_coverage">
|
25
|
+
</a>
|
26
|
+
</p>
|
27
|
+
|
28
|
+
This gem allows you to define "immutable" objects, and your objects will have only getters and no setters.
|
29
|
+
So, if you change [[1](#with_attribute)] [[2](#with_attributes)] some object attribute, you will have a new object instance. That is, you transform the object instead of modifying it.
|
30
|
+
|
31
|
+
# Table of contents <!-- omit in toc -->
|
14
32
|
- [Installation](#installation)
|
15
33
|
- [Compatibility](#compatibility)
|
16
34
|
- [Usage](#usage)
|
17
35
|
- [How to define attributes?](#how-to-define-attributes)
|
18
36
|
- [`Micro::Attributes#attributes=`](#microattributesattributes)
|
37
|
+
- [How to extract attributes from an object or hash?](#how-to-extract-attributes-from-an-object-or-hash)
|
38
|
+
- [Is it possible to define an attribute as required?](#is-it-possible-to-define-an-attribute-as-required)
|
19
39
|
- [`Micro::Attributes#attribute`](#microattributesattribute)
|
20
40
|
- [`Micro::Attributes#attribute!`](#microattributesattribute-1)
|
21
41
|
- [How to define multiple attributes?](#how-to-define-multiple-attributes)
|
@@ -23,26 +43,36 @@ This gem allows defining read-only attributes, that is, your objects will have o
|
|
23
43
|
- [`#with_attribute()`](#with_attribute)
|
24
44
|
- [`#with_attributes()`](#with_attributes)
|
25
45
|
- [Defining default values to the attributes](#defining-default-values-to-the-attributes)
|
26
|
-
- [
|
46
|
+
- [The strict initializer](#the-strict-initializer)
|
27
47
|
- [Is it possible to inherit the attributes?](#is-it-possible-to-inherit-the-attributes)
|
28
|
-
- [
|
48
|
+
- [`.attribute!()`](#attribute)
|
29
49
|
- [How to query the attributes?](#how-to-query-the-attributes)
|
50
|
+
- [`.attributes`](#attributes)
|
51
|
+
- [`.attribute?()`](#attribute-1)
|
52
|
+
- [`#attribute?()`](#attribute-2)
|
53
|
+
- [`#attributes()`](#attributes-1)
|
54
|
+
- [`#attributes(keys_as:)`](#attributeskeys_as)
|
55
|
+
- [`#attributes(*names)`](#attributesnames)
|
56
|
+
- [`#attributes([names])`](#attributesnames-1)
|
57
|
+
- [`#attributes(with:, without)`](#attributeswith-without)
|
58
|
+
- [`#defined_attributes`](#defined_attributes)
|
30
59
|
- [Built-in extensions](#built-in-extensions)
|
31
|
-
- [
|
32
|
-
- [
|
33
|
-
|
34
|
-
- [
|
35
|
-
- [
|
60
|
+
- [Picking specific features](#picking-specific-features)
|
61
|
+
- [`Micro::Attributes.with`](#microattributeswith)
|
62
|
+
- [`Micro::Attributes.without`](#microattributeswithout)
|
63
|
+
- [Picking all the features](#picking-all-the-features)
|
64
|
+
- [Extensions](#extensions)
|
65
|
+
- [`ActiveModel::Validation` extension](#activemodelvalidation-extension)
|
66
|
+
- [`.attribute()` options](#attribute-options)
|
67
|
+
- [Diff extension](#diff-extension)
|
68
|
+
- [Initialize extension](#initialize-extension)
|
69
|
+
- [Strict mode](#strict-mode)
|
36
70
|
- [Development](#development)
|
37
71
|
- [Contributing](#contributing)
|
38
72
|
- [License](#license)
|
39
73
|
- [Code of Conduct](#code-of-conduct)
|
40
74
|
|
41
|
-
|
42
|
-
|
43
|
-
> \>= 2.2.0
|
44
|
-
|
45
|
-
## Installation
|
75
|
+
# Installation
|
46
76
|
|
47
77
|
Add this line to your application's Gemfile and `bundle install`:
|
48
78
|
|
@@ -50,25 +80,29 @@ Add this line to your application's Gemfile and `bundle install`:
|
|
50
80
|
gem 'u-attributes'
|
51
81
|
```
|
52
82
|
|
53
|
-
|
83
|
+
# Compatibility
|
54
84
|
|
55
85
|
| u-attributes | branch | ruby | activemodel |
|
56
86
|
| -------------- | ------- | -------- | ------------- |
|
57
|
-
| 2.
|
87
|
+
| 2.3.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
58
88
|
| 1.2.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
59
89
|
|
60
|
-
|
90
|
+
> **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
|
61
91
|
|
62
|
-
|
92
|
+
[⬆️ Back to Top](#table-of-contents-)
|
63
93
|
|
64
|
-
|
65
|
-
|
94
|
+
# Usage
|
95
|
+
|
96
|
+
## How to define attributes?
|
66
97
|
|
98
|
+
By default, you must define the class constructor.
|
99
|
+
|
100
|
+
```ruby
|
67
101
|
class Person
|
68
102
|
include Micro::Attributes
|
69
103
|
|
70
|
-
attribute :name
|
71
104
|
attribute :age
|
105
|
+
attribute :name
|
72
106
|
|
73
107
|
def initialize(name: 'John Doe', age:)
|
74
108
|
@name, @age = name, age
|
@@ -77,8 +111,8 @@ end
|
|
77
111
|
|
78
112
|
person = Person.new(age: 21)
|
79
113
|
|
80
|
-
person.name # John Doe
|
81
114
|
person.age # 21
|
115
|
+
person.name # John Doe
|
82
116
|
|
83
117
|
# By design the attributes are always exposed as reader methods (getters).
|
84
118
|
# If you try to call a setter you will see a NoMethodError.
|
@@ -87,7 +121,9 @@ person.age # 21
|
|
87
121
|
# NoMethodError (undefined method `name=' for #<Person:0x0000... @name='John Doe', @age=21>)
|
88
122
|
```
|
89
123
|
|
90
|
-
|
124
|
+
[⬆️ Back to Top](#table-of-contents-)
|
125
|
+
|
126
|
+
### `Micro::Attributes#attributes=`
|
91
127
|
|
92
128
|
This is a protected method to make easier the assignment in a constructor. e.g.
|
93
129
|
|
@@ -95,8 +131,8 @@ This is a protected method to make easier the assignment in a constructor. e.g.
|
|
95
131
|
class Person
|
96
132
|
include Micro::Attributes
|
97
133
|
|
98
|
-
attribute :name, default: 'John Doe'
|
99
134
|
attribute :age
|
135
|
+
attribute :name, default: 'John Doe'
|
100
136
|
|
101
137
|
def initialize(options)
|
102
138
|
self.attributes = options
|
@@ -105,19 +141,81 @@ end
|
|
105
141
|
|
106
142
|
person = Person.new(age: 20)
|
107
143
|
|
144
|
+
person.age # 20
|
108
145
|
person.name # John Doe
|
146
|
+
```
|
147
|
+
|
148
|
+
#### How to extract attributes from an object or hash?
|
149
|
+
|
150
|
+
You can extract attributes using the `extract_attributes_from` method, it will try to fetch attributes from the
|
151
|
+
object using either the `object[attribute_key]` accessor or the reader method `object.attribute_key`.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
class Person
|
155
|
+
include Micro::Attributes
|
156
|
+
|
157
|
+
attribute :age
|
158
|
+
attribute :name, default: 'John Doe'
|
159
|
+
|
160
|
+
def initialize(user:)
|
161
|
+
self.attributes = extract_attributes_from(user)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# extracting from an object
|
166
|
+
|
167
|
+
class User
|
168
|
+
attr_accessor :age, :name
|
169
|
+
end
|
170
|
+
|
171
|
+
user = User.new
|
172
|
+
user.age = 20
|
173
|
+
|
174
|
+
person = Person.new(user: user)
|
175
|
+
|
109
176
|
person.age # 20
|
177
|
+
person.name # John Doe
|
178
|
+
|
179
|
+
# extracting from a hash
|
180
|
+
|
181
|
+
another_person = Person.new(user: { age: 55, name: 'Julia Not Roberts' })
|
182
|
+
|
183
|
+
another_person.age # 55
|
184
|
+
another_person.name # Julia Not Roberts
|
185
|
+
```
|
186
|
+
|
187
|
+
#### Is it possible to define an attribute as required?
|
188
|
+
|
189
|
+
You only need to use the `required: true` option.
|
190
|
+
|
191
|
+
But to this work, you need to assign the attributes using the [`#attributes=`](#microattributesattributes) method or the extensions: [initialize](#initialize-extension), [activemodel_validations](#activemodelvalidation-extension).
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class Person
|
195
|
+
include Micro::Attributes
|
196
|
+
|
197
|
+
attribute :age
|
198
|
+
attribute :name, required: true
|
199
|
+
|
200
|
+
def initialize(attributes)
|
201
|
+
self.attributes = attributes
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
Person.new(age: 32) # ArgumentError (missing keyword: :name)
|
110
206
|
```
|
111
207
|
|
112
|
-
|
208
|
+
[⬆️ Back to Top](#table-of-contents-)
|
209
|
+
|
210
|
+
### `Micro::Attributes#attribute`
|
113
211
|
|
114
212
|
Use this method with a valid attribute name to get its value.
|
115
213
|
|
116
214
|
```ruby
|
117
215
|
person = Person.new(age: 20)
|
118
216
|
|
119
|
-
person.attribute(:name) # John Doe
|
120
217
|
person.attribute('age') # 20
|
218
|
+
person.attribute(:name) # John Doe
|
121
219
|
person.attribute('foo') # nil
|
122
220
|
```
|
123
221
|
|
@@ -129,17 +227,21 @@ person.attribute('age') { |value| puts value } # 20
|
|
129
227
|
person.attribute('foo') { |value| puts value } # !! Nothing happened, because of the attribute doesn't exist.
|
130
228
|
```
|
131
229
|
|
132
|
-
|
230
|
+
[⬆️ Back to Top](#table-of-contents-)
|
231
|
+
|
232
|
+
### `Micro::Attributes#attribute!`
|
133
233
|
|
134
234
|
Works like the `#attribute` method, but it will raise an exception when the attribute doesn't exist.
|
135
235
|
|
136
236
|
```ruby
|
137
|
-
person.attribute!('foo')
|
237
|
+
person.attribute!('foo') # NameError (undefined attribute `foo)
|
138
238
|
|
139
239
|
person.attribute!('foo') { |value| value } # NameError (undefined attribute `foo)
|
140
240
|
```
|
141
241
|
|
142
|
-
|
242
|
+
[⬆️ Back to Top](#table-of-contents-)
|
243
|
+
|
244
|
+
## How to define multiple attributes?
|
143
245
|
|
144
246
|
Use `.attributes` with a list of attribute names.
|
145
247
|
|
@@ -162,7 +264,9 @@ person.age # 32
|
|
162
264
|
|
163
265
|
> **Note:** This method can't define default values. To do this, use the `#attribute()` method.
|
164
266
|
|
165
|
-
|
267
|
+
[⬆️ Back to Top](#table-of-contents-)
|
268
|
+
|
269
|
+
## `Micro::Attributes.with(:initialize)`
|
166
270
|
|
167
271
|
Use `Micro::Attributes.with(:initialize)` to define a constructor to assign the attributes. e.g.
|
168
272
|
|
@@ -170,47 +274,49 @@ Use `Micro::Attributes.with(:initialize)` to define a constructor to assign the
|
|
170
274
|
class Person
|
171
275
|
include Micro::Attributes.with(:initialize)
|
172
276
|
|
173
|
-
attribute :age
|
277
|
+
attribute :age, required: true
|
174
278
|
attribute :name, default: 'John Doe'
|
175
279
|
end
|
176
280
|
|
177
281
|
person = Person.new(age: 18)
|
178
282
|
|
179
|
-
person.name # John Doe
|
180
283
|
person.age # 18
|
284
|
+
person.name # John Doe
|
181
285
|
```
|
182
286
|
|
183
287
|
This extension enables two methods for your objects.
|
184
288
|
The `#with_attribute()` and `#with_attributes()`.
|
185
289
|
|
186
|
-
|
290
|
+
### `#with_attribute()`
|
187
291
|
|
188
292
|
```ruby
|
189
293
|
another_person = person.with_attribute(:age, 21)
|
190
294
|
|
191
|
-
another_person.name # John Doe
|
192
295
|
another_person.age # 21
|
296
|
+
another_person.name # John Doe
|
193
297
|
another_person.equal?(person) # false
|
194
298
|
```
|
195
299
|
|
196
|
-
|
300
|
+
### `#with_attributes()`
|
197
301
|
|
198
302
|
Use it to assign multiple attributes
|
199
303
|
```ruby
|
200
304
|
other_person = person.with_attributes(name: 'Serradura', age: 32)
|
201
305
|
|
202
|
-
other_person.name # Serradura
|
203
306
|
other_person.age # 32
|
307
|
+
other_person.name # Serradura
|
204
308
|
other_person.equal?(person) # false
|
205
309
|
```
|
206
310
|
|
207
311
|
If you pass a value different of a Hash, a Kind::Error will be raised.
|
208
312
|
|
209
313
|
```ruby
|
210
|
-
Person.new(1) # Kind::Error (1
|
314
|
+
Person.new(1) # Kind::Error (1 expected to be a kind of Hash)
|
211
315
|
```
|
212
316
|
|
213
|
-
|
317
|
+
[⬆️ Back to Top](#table-of-contents-)
|
318
|
+
|
319
|
+
## Defining default values to the attributes
|
214
320
|
|
215
321
|
To do this, you only need make use of the `default:` keyword. e.g.
|
216
322
|
|
@@ -223,10 +329,9 @@ class Person
|
|
223
329
|
end
|
224
330
|
```
|
225
331
|
|
226
|
-
There are
|
332
|
+
There are two different strategies to define default values.
|
227
333
|
1. Pass a regular object, like in the previous example.
|
228
334
|
2. Pass a `proc`/`lambda`, and if it has an argument you will receive the attribute value to do something before assign it.
|
229
|
-
3. Pass a **callable**, that is, a `class`, `module` or `instance` which responds to the `call` method. The behavior will be like the previous item (`proc`/`lambda`).
|
230
335
|
|
231
336
|
```ruby
|
232
337
|
class Person
|
@@ -237,9 +342,13 @@ class Person
|
|
237
342
|
end
|
238
343
|
```
|
239
344
|
|
240
|
-
|
345
|
+
[⬆️ Back to Top](#table-of-contents-)
|
346
|
+
|
347
|
+
## The strict initializer
|
348
|
+
|
349
|
+
Use `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords.
|
241
350
|
|
242
|
-
|
351
|
+
In other words, it is equivalent to you define all the attributes using the [`required: true` option](#is-it-possible-to-define-an-attribute-as-required).
|
243
352
|
|
244
353
|
```ruby
|
245
354
|
class StrictPerson
|
@@ -257,13 +366,15 @@ An attribute with a default value can be omitted.
|
|
257
366
|
``` ruby
|
258
367
|
person_without_age = StrictPerson.new(age: nil)
|
259
368
|
|
260
|
-
person_without_age.name # 'John Doe'
|
261
369
|
person_without_age.age # nil
|
370
|
+
person_without_age.name # 'John Doe'
|
262
371
|
```
|
263
372
|
|
264
373
|
> **Note:** Except for this validation the `.with(initialize: :strict)` method will works in the same ways of `.with(:initialize)`.
|
265
374
|
|
266
|
-
|
375
|
+
[⬆️ Back to Top](#table-of-contents-)
|
376
|
+
|
377
|
+
## Is it possible to inherit the attributes?
|
267
378
|
|
268
379
|
Yes. e.g.
|
269
380
|
|
@@ -286,7 +397,9 @@ instance.respond_to?(:age) # true
|
|
286
397
|
instance.respond_to?(:foo) # true
|
287
398
|
```
|
288
399
|
|
289
|
-
|
400
|
+
[⬆️ Back to Top](#table-of-contents-)
|
401
|
+
|
402
|
+
### `.attribute!()`
|
290
403
|
|
291
404
|
This method allows us to redefine the attributes default data that was defined in the parent class. e.g.
|
292
405
|
|
@@ -311,137 +424,217 @@ beta_person.name # 'Beta'
|
|
311
424
|
beta_person.age # 0
|
312
425
|
```
|
313
426
|
|
314
|
-
|
427
|
+
[⬆️ Back to Top](#table-of-contents-)
|
428
|
+
|
429
|
+
## How to query the attributes?
|
430
|
+
|
431
|
+
All of the methods that will be explained can be used with any of the built-in extensions.
|
432
|
+
|
433
|
+
**PS:** We will use the class below for all of the next examples.
|
315
434
|
|
316
435
|
```ruby
|
317
436
|
class Person
|
318
437
|
include Micro::Attributes
|
319
438
|
|
320
439
|
attribute :age
|
321
|
-
attribute :
|
440
|
+
attribute :first_name, default: 'John'
|
441
|
+
attribute :last_name, default: 'Doe'
|
322
442
|
|
323
443
|
def initialize(options)
|
324
444
|
self.attributes = options
|
325
445
|
end
|
446
|
+
|
447
|
+
def name
|
448
|
+
"#{first_name} #{last_name}"
|
449
|
+
end
|
326
450
|
end
|
451
|
+
```
|
452
|
+
|
453
|
+
### `.attributes`
|
454
|
+
|
455
|
+
Listing all the class attributes.
|
456
|
+
|
457
|
+
```ruby
|
458
|
+
Person.attributes # ["age", "first_name", "last_name"]
|
459
|
+
```
|
327
460
|
|
328
|
-
|
329
|
-
# .attributes() #
|
330
|
-
#---------------#
|
461
|
+
### `.attribute?()`
|
331
462
|
|
332
|
-
|
463
|
+
Checking the existence of some attribute.
|
333
464
|
|
334
|
-
|
335
|
-
|
336
|
-
|
465
|
+
```ruby
|
466
|
+
Person.attribute?(:first_name) # true
|
467
|
+
Person.attribute?('first_name') # true
|
337
468
|
|
338
|
-
Person.attribute?(:name) # true
|
339
|
-
Person.attribute?('name') # true
|
340
469
|
Person.attribute?('foo') # false
|
341
470
|
Person.attribute?(:foo) # false
|
471
|
+
```
|
342
472
|
|
343
|
-
|
473
|
+
### `#attribute?()`
|
344
474
|
|
345
|
-
|
475
|
+
Checking the existence of some attribute in an instance.
|
346
476
|
|
347
|
-
|
348
|
-
|
349
|
-
#---------------#
|
477
|
+
```ruby
|
478
|
+
person = Person.new(age: 20)
|
350
479
|
|
351
480
|
person.attribute?(:name) # true
|
352
481
|
person.attribute?('name') # true
|
482
|
+
|
353
483
|
person.attribute?('foo') # false
|
354
484
|
person.attribute?(:foo) # false
|
485
|
+
```
|
486
|
+
|
487
|
+
### `#attributes()`
|
488
|
+
|
489
|
+
Fetching all the attributes with their values.
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
person1 = Person.new(age: 20)
|
493
|
+
person1.attributes # {"age"=>20, "first_name"=>"John", "last_name"=>"Doe"}
|
494
|
+
|
495
|
+
person2 = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
|
496
|
+
person2.attributes # {"age"=>nil, "first_name"=>"Rodrigo", "last_name"=>"Rodrigues"}
|
497
|
+
```
|
498
|
+
|
499
|
+
#### `#attributes(keys_as:)`
|
500
|
+
|
501
|
+
Use the `keys_as:` option with `Symbol` or `String` to transform the attributes hash keys.
|
502
|
+
|
503
|
+
```ruby
|
504
|
+
person1 = Person.new(age: 20)
|
505
|
+
person1.attributes(keys_as: Symbol) # {:age=>20, :first_name=>"John", :last_name=>"Doe"}
|
506
|
+
|
507
|
+
person2 = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
|
508
|
+
person2.attributes(keys_as: String) # {"age"=>nil, "first_name"=>"Rodrigo", "last_name"=>"Rodrigues"}
|
509
|
+
```
|
510
|
+
|
511
|
+
#### `#attributes(*names)`
|
512
|
+
|
513
|
+
Slices the attributes to include only the given keys (in their types).
|
514
|
+
|
515
|
+
```ruby
|
516
|
+
person = Person.new(age: 20)
|
517
|
+
|
518
|
+
person.attributes(:age) # {:age => 20}
|
519
|
+
person.attributes(:age, :first_name) # {:age => 20, :first_name => "John"}
|
520
|
+
person.attributes('age', 'last_name') # {"age" => 20, "last_name" => "Doe"}
|
521
|
+
|
522
|
+
person.attributes(:age, 'last_name') # {:age => 20, "last_name" => "Doe"}
|
523
|
+
|
524
|
+
# You could also use the keys_as: option to ensure the same type for all of the hash keys.
|
525
|
+
|
526
|
+
person.attributes(:age, 'last_name', keys_as: Symbol) # {:age=>20, :last_name=>"Doe"}
|
527
|
+
```
|
528
|
+
|
529
|
+
#### `#attributes([names])`
|
530
|
+
|
531
|
+
As the previous example, this methods accepts a list of keys to slice the attributes.
|
532
|
+
|
533
|
+
```ruby
|
534
|
+
person = Person.new(age: 20)
|
535
|
+
|
536
|
+
person.attributes([:age]) # {:age => 20}
|
537
|
+
person.attributes([:age, :first_name]) # {:age => 20, :first_name => "John"}
|
538
|
+
person.attributes(['age', 'last_name']) # {"age" => 20, "last_name" => "Doe"}
|
539
|
+
|
540
|
+
person.attributes([:age, 'last_name']) # {:age => 20, "last_name" => "Doe"}
|
541
|
+
|
542
|
+
# You could also use the keys_as: option to ensure the same type for all of the hash keys.
|
543
|
+
|
544
|
+
person.attributes([:age, 'last_name'], keys_as: Symbol) # {:age=>20, :last_name=>"Doe"}
|
545
|
+
```
|
546
|
+
|
547
|
+
#### `#attributes(with:, without)`
|
548
|
+
|
549
|
+
Use the `with:` option to include any method value of the instance inside of the hash, and,
|
550
|
+
you can use the `without:` option to exclude one or more attribute keys from the final hash.
|
551
|
+
|
552
|
+
```ruby
|
553
|
+
person = Person.new(age: 20)
|
355
554
|
|
356
|
-
|
357
|
-
|
358
|
-
#---------------#
|
555
|
+
person.attributes(without: :age) # {"first_name"=>"John", "last_name"=>"Doe"}
|
556
|
+
person.attributes(without: [:age, :last_name]) # {"first_name"=>"John"}
|
359
557
|
|
360
|
-
person.attributes
|
361
|
-
Person.new(name: 'John').attributes # {'age'=>nil, 'name'=>'John'}
|
558
|
+
person.attributes(with: [:name], without: [:first_name, :last_name]) # {"age"=>20, "name"=>"John Doe"}
|
362
559
|
|
363
|
-
|
364
|
-
# #attributes(*names) #
|
365
|
-
#---------------------#
|
560
|
+
# To achieves the same output of the previous example, use the attribute names to slice only them.
|
366
561
|
|
367
|
-
|
368
|
-
# Returns a hash containing the given keys (in their types).
|
562
|
+
person.attributes(:age, with: [:name]) # {:age=>20, "name"=>"John Doe"}
|
369
563
|
|
370
|
-
|
371
|
-
|
372
|
-
person.attributes(
|
564
|
+
# You could also use the keys_as: option to ensure the same type for all of the hash keys.
|
565
|
+
|
566
|
+
person.attributes(:age, with: [:name], keys_as: Symbol) # {:age=>20, :name=>"John Doe"}
|
373
567
|
```
|
374
568
|
|
375
|
-
|
569
|
+
### `#defined_attributes`
|
570
|
+
|
571
|
+
Listing all the available attributes.
|
572
|
+
|
573
|
+
```ruby
|
574
|
+
person = Person.new(age: 20)
|
575
|
+
|
576
|
+
person.defined_attributes # ["age", "first_name", "last_name"]
|
577
|
+
```
|
578
|
+
|
579
|
+
[⬆️ Back to Top](#table-of-contents-)
|
580
|
+
|
581
|
+
# Built-in extensions
|
376
582
|
|
377
583
|
You can use the method `Micro::Attributes.with()` to combine and require only the features that better fit your needs.
|
378
584
|
|
379
585
|
But, if you desire except one or more features, use the `Micro::Attributes.without()` method.
|
380
586
|
|
587
|
+
## Picking specific features
|
588
|
+
|
589
|
+
### `Micro::Attributes.with`
|
590
|
+
|
381
591
|
```ruby
|
382
|
-
|
383
|
-
# Loading specific features #
|
384
|
-
#===========================#
|
592
|
+
Micro::Attributes.with(:initialize)
|
385
593
|
|
386
|
-
|
387
|
-
include Micro::Attributes.with(:diff)
|
594
|
+
Micro::Attributes.with(initialize: :strict)
|
388
595
|
|
389
|
-
|
390
|
-
attribute :state, default: 'sleeping'
|
596
|
+
Micro::Attributes.with(:diff, :initialize)
|
391
597
|
|
392
|
-
|
393
|
-
self.attributes = options
|
394
|
-
end
|
395
|
-
end
|
598
|
+
Micro::Attributes.with(:diff, initialize: :strict)
|
396
599
|
|
397
|
-
|
398
|
-
# Loading all features #
|
399
|
-
# --- #
|
400
|
-
#======================#
|
600
|
+
Micro::Attributes.with(:activemodel_validations)
|
401
601
|
|
402
|
-
|
403
|
-
include Micro::Attributes.with_all_features
|
602
|
+
Micro::Attributes.with(:activemodel_validations, :diff)
|
404
603
|
|
405
|
-
|
406
|
-
|
407
|
-
end
|
604
|
+
Micro::Attributes.with(:activemodel_validations, :diff, initialize: :strict)
|
605
|
+
```
|
408
606
|
|
409
|
-
|
410
|
-
# Using the .with() method alias and adding the strict initialize extension. #
|
411
|
-
#----------------------------------------------------------------------------#
|
412
|
-
class Job
|
413
|
-
include Micro::Attributes.with(:diff, initialize: :strict)
|
607
|
+
The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared.
|
414
608
|
|
415
|
-
|
416
|
-
|
609
|
+
```ruby
|
610
|
+
class Job
|
611
|
+
include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :activemodel_validations, :diff, :initialize)
|
417
612
|
end
|
613
|
+
```
|
418
614
|
|
419
|
-
|
420
|
-
# The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared.
|
421
|
-
#
|
422
|
-
# class Job
|
423
|
-
# include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: diff, initialize, activemodel_validations)
|
424
|
-
# end
|
615
|
+
### `Micro::Attributes.without`
|
425
616
|
|
426
|
-
|
427
|
-
# Loading except one or more features #
|
428
|
-
# ----- #
|
429
|
-
#=====================================#
|
617
|
+
Picking *except* one or more features
|
430
618
|
|
431
|
-
|
432
|
-
|
619
|
+
```ruby
|
620
|
+
Micro::Attributes.without(:diff) # will load :activemodel_validations and initialize: :strict
|
433
621
|
|
434
|
-
|
435
|
-
|
436
|
-
end
|
622
|
+
Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations and :diff
|
623
|
+
```
|
437
624
|
|
438
|
-
|
439
|
-
|
625
|
+
## Picking all the features
|
626
|
+
|
627
|
+
```ruby
|
628
|
+
Micro::Attributes.with_all_features
|
440
629
|
```
|
441
630
|
|
442
|
-
|
631
|
+
[⬆️ Back to Top](#table-of-contents-)
|
632
|
+
|
633
|
+
## Extensions
|
443
634
|
|
444
|
-
|
635
|
+
### `ActiveModel::Validation` extension
|
636
|
+
|
637
|
+
If your application uses ActiveModel as a dependency (like a regular Rails app). You will be enabled to use the `activemodel_validations` extension.
|
445
638
|
|
446
639
|
```ruby
|
447
640
|
class Job
|
@@ -461,7 +654,7 @@ job.id # 1
|
|
461
654
|
job.state # 'sleeping'
|
462
655
|
```
|
463
656
|
|
464
|
-
####
|
657
|
+
#### `.attribute()` options
|
465
658
|
|
466
659
|
You can use the `validate` or `validates` options to define your attributes. e.g.
|
467
660
|
|
@@ -472,7 +665,7 @@ class Job
|
|
472
665
|
attribute :id, validates: { presence: true }
|
473
666
|
attribute :state, validate: :must_be_a_filled_string
|
474
667
|
|
475
|
-
def
|
668
|
+
def must_be_a_filled_string
|
476
669
|
return if state.is_a?(String) && state.present?
|
477
670
|
|
478
671
|
errors.add(:state, 'must be a filled string')
|
@@ -480,6 +673,8 @@ class Job
|
|
480
673
|
end
|
481
674
|
```
|
482
675
|
|
676
|
+
[⬆️ Back to Top](#table-of-contents-)
|
677
|
+
|
483
678
|
### Diff extension
|
484
679
|
|
485
680
|
Provides a way to track changes in your object attributes.
|
@@ -529,6 +724,8 @@ job_changes.changed?(:state, from: 'sleeping', to: 'running') # true
|
|
529
724
|
job_changes.differences # {'state'=> {'from' => 'sleeping', 'to' => 'running'}}
|
530
725
|
```
|
531
726
|
|
727
|
+
[⬆️ Back to Top](#table-of-contents-)
|
728
|
+
|
532
729
|
### Initialize extension
|
533
730
|
|
534
731
|
1. Creates a constructor to assign the attributes.
|
@@ -578,7 +775,9 @@ other_job.state # killed
|
|
578
775
|
other_job.equal?(job) # false
|
579
776
|
```
|
580
777
|
|
581
|
-
|
778
|
+
[⬆️ Back to Top](#table-of-contents-)
|
779
|
+
|
780
|
+
#### Strict mode
|
582
781
|
|
583
782
|
1. Creates a constructor to assign the attributes.
|
584
783
|
2. Adds methods to build new instances when some data was assigned.
|
@@ -616,20 +815,22 @@ job.state # 'sleeping'
|
|
616
815
|
|
617
816
|
> **Note**: This extension works like the `initialize` extension. So, look at its section to understand all of the other features.
|
618
817
|
|
619
|
-
|
818
|
+
[⬆️ Back to Top](#table-of-contents-)
|
819
|
+
|
820
|
+
# Development
|
620
821
|
|
621
822
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
622
823
|
|
623
824
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
624
825
|
|
625
|
-
|
826
|
+
# Contributing
|
626
827
|
|
627
828
|
Bug reports and pull requests are welcome on GitHub at https://github.com/serradura/u-attributes. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
628
829
|
|
629
|
-
|
830
|
+
# License
|
630
831
|
|
631
832
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
632
833
|
|
633
|
-
|
834
|
+
# Code of Conduct
|
634
835
|
|
635
|
-
Everyone interacting in the Micro::Attributes project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/u-attributes/blob/
|
836
|
+
Everyone interacting in the Micro::Attributes project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/u-attributes/blob/main/CODE_OF_CONDUCT.md).
|
Binary file
|
data/lib/micro/attributes.rb
CHANGED
@@ -14,7 +14,8 @@ module Micro
|
|
14
14
|
base.extend(::Micro::Attributes.const_get(:Macros))
|
15
15
|
|
16
16
|
base.class_eval do
|
17
|
-
private_class_method :__attributes, :
|
17
|
+
private_class_method :__attributes, :__attribute_reader
|
18
|
+
private_class_method :__attribute_assign, :__attributes_data_to_assign
|
18
19
|
end
|
19
20
|
|
20
21
|
def base.inherited(subclass)
|
@@ -54,48 +55,92 @@ module Micro
|
|
54
55
|
raise NameError, "undefined attribute `#{name}"
|
55
56
|
end
|
56
57
|
|
58
|
+
def defined_attributes
|
59
|
+
@defined_attributes ||= self.class.attributes
|
60
|
+
end
|
61
|
+
|
57
62
|
def attributes(*names)
|
58
63
|
return __attributes if names.empty?
|
59
64
|
|
60
|
-
names.
|
61
|
-
|
65
|
+
options = names.last.is_a?(Hash) ? names.pop : Kind::Empty::HASH
|
66
|
+
|
67
|
+
names.flatten!
|
68
|
+
|
69
|
+
without_option = Array(options.fetch(:without, Kind::Empty::ARRAY))
|
70
|
+
|
71
|
+
keys = names.empty? ? defined_attributes - without_option.map(&:to_s) : names - without_option
|
72
|
+
|
73
|
+
data = keys.each_with_object({}) { |key, memo| memo[key] = attribute(key) if attribute?(key) }
|
74
|
+
|
75
|
+
with_option = Array(options.fetch(:with, Kind::Empty::ARRAY))
|
76
|
+
|
77
|
+
unless with_option.empty?
|
78
|
+
extra = with_option.each_with_object({}) { |key, memo| memo[key.to_s] = public_send(key) }
|
79
|
+
|
80
|
+
data.merge!(extra)
|
62
81
|
end
|
82
|
+
|
83
|
+
Utils::Hashes.keys_as(options[:keys_as], data)
|
63
84
|
end
|
64
85
|
|
65
86
|
protected
|
66
87
|
|
67
88
|
def attributes=(arg)
|
68
|
-
hash = Utils.
|
89
|
+
hash = Utils::Hashes.stringify_keys(arg)
|
90
|
+
|
91
|
+
__attributes_missing!(hash)
|
69
92
|
|
70
|
-
|
93
|
+
__attributes_assign(hash)
|
71
94
|
end
|
72
95
|
|
73
96
|
private
|
74
97
|
|
98
|
+
def extract_attributes_from(other)
|
99
|
+
Utils::ExtractAttribute.from(other, keys: defined_attributes)
|
100
|
+
end
|
101
|
+
|
75
102
|
def __attributes
|
76
103
|
@__attributes ||= {}
|
77
104
|
end
|
78
105
|
|
79
|
-
|
80
|
-
|
106
|
+
FetchValueToAssign = -> (value, default) do
|
107
|
+
if default.is_a?(Proc)
|
108
|
+
default.arity > 0 ? default.call(value) : default.call
|
109
|
+
else
|
110
|
+
value.nil? ? default : value
|
111
|
+
end
|
81
112
|
end
|
82
113
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
final_value =
|
88
|
-
if default.respond_to?(:call)
|
89
|
-
callable = default.is_a?(Proc) ? default : default.method(:call)
|
90
|
-
callable.arity > 0 ? callable.call(value) : callable.call
|
91
|
-
else
|
92
|
-
value || default
|
93
|
-
end
|
94
|
-
|
95
|
-
__attribute_set(key, final_value)
|
114
|
+
def __attributes_assign(hash)
|
115
|
+
self.class.__attributes_data__.each do |name, default|
|
116
|
+
__attribute_assign(name, FetchValueToAssign.(hash[name], default)) if attribute?(name)
|
96
117
|
end
|
97
118
|
|
98
119
|
__attributes.freeze
|
99
120
|
end
|
121
|
+
|
122
|
+
def __attribute_assign(name, value)
|
123
|
+
__attributes[name] = instance_variable_set("@#{name}", value)
|
124
|
+
end
|
125
|
+
|
126
|
+
MISSING_KEYWORD = 'missing keyword'.freeze
|
127
|
+
MISSING_KEYWORDS = 'missing keywords'.freeze
|
128
|
+
|
129
|
+
def __attributes_missing!(hash)
|
130
|
+
required_keys = self.class.__attributes_required__
|
131
|
+
|
132
|
+
return if required_keys.empty?
|
133
|
+
|
134
|
+
missing_keys = required_keys.map { |name| ":#{name}" if !hash.key?(name) }
|
135
|
+
missing_keys.compact!
|
136
|
+
|
137
|
+
return if missing_keys.empty?
|
138
|
+
|
139
|
+
label = missing_keys.size == 1 ? MISSING_KEYWORD : MISSING_KEYWORDS
|
140
|
+
|
141
|
+
raise ArgumentError, "#{label}: #{missing_keys.join(', ')}"
|
142
|
+
end
|
143
|
+
|
144
|
+
private_constant :FetchValueToAssign, :MISSING_KEYWORD, :MISSING_KEYWORDS
|
100
145
|
end
|
101
146
|
end
|
@@ -14,11 +14,11 @@ module Micro::Attributes
|
|
14
14
|
end
|
15
15
|
|
16
16
|
module ClassMethods
|
17
|
-
def
|
17
|
+
def __call_after_attribute_assign__(attr_name, options)
|
18
18
|
validate, validates = options.values_at(:validate, :validates)
|
19
19
|
|
20
20
|
self.validate(validate) if validate
|
21
|
-
self.validates(attr_name, validates) if validates
|
21
|
+
self.validates(attr_name, validates.dup) if validates
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -4,35 +4,15 @@ module Micro::Attributes
|
|
4
4
|
module Features
|
5
5
|
module Initialize
|
6
6
|
module Strict
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
protected def attributes=(arg)
|
11
|
-
arg_hash = Utils.stringify_hash_keys(arg)
|
12
|
-
att_data = self.class.__attributes_data__
|
13
|
-
|
14
|
-
attributes_missing!(ref: att_data, arg: arg_hash)
|
15
|
-
|
16
|
-
__attributes_set(arg_hash, att_data)
|
17
|
-
end
|
18
|
-
|
19
|
-
private def attributes_missing!(ref:, arg:)
|
20
|
-
missing_keys = attributes_missing(ref, arg)
|
21
|
-
|
22
|
-
return if missing_keys.empty?
|
23
|
-
|
24
|
-
label = missing_keys.size == 1 ? MISSING_KEYWORD : MISSING_KEYWORDS
|
25
|
-
|
26
|
-
raise ArgumentError, "#{label}: #{missing_keys.join(', ')}"
|
27
|
-
end
|
28
|
-
|
29
|
-
private def attributes_missing(ref, arg)
|
30
|
-
ref.each_with_object([]) do |(key, val), memo|
|
31
|
-
memo << ":#{key}" if val.nil? && !arg.has_key?(key)
|
7
|
+
module ClassMethods
|
8
|
+
def attributes_are_all_required?
|
9
|
+
true
|
32
10
|
end
|
33
11
|
end
|
34
12
|
|
35
|
-
|
13
|
+
def self.included(base)
|
14
|
+
base.send(:extend, ClassMethods)
|
15
|
+
end
|
36
16
|
end
|
37
17
|
end
|
38
18
|
end
|
@@ -3,10 +3,33 @@
|
|
3
3
|
module Micro
|
4
4
|
module Attributes
|
5
5
|
module Macros
|
6
|
+
def attributes_are_all_required?
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
# NOTE: can't be renamed! It is used by u-case v4.
|
6
11
|
def __attributes_data__
|
7
12
|
@__attributes_data__ ||= {}
|
8
13
|
end
|
9
14
|
|
15
|
+
def __attributes_required__
|
16
|
+
@__attributes_required__ ||= Set.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def __attributes_required_add(name, is_required, hasnt_default)
|
20
|
+
if is_required || (attributes_are_all_required? && hasnt_default)
|
21
|
+
__attributes_required__.add(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def __attributes_data_to_assign(name, options)
|
28
|
+
hasnt_default = !options.key?(:default)
|
29
|
+
|
30
|
+
hasnt_default ? __attributes_required_add(name, options[:required], hasnt_default) : options[:default]
|
31
|
+
end
|
32
|
+
|
10
33
|
def __attributes
|
11
34
|
@__attributes ||= Set.new
|
12
35
|
end
|
@@ -17,20 +40,24 @@ module Micro
|
|
17
40
|
attr_reader(name)
|
18
41
|
end
|
19
42
|
|
20
|
-
def
|
43
|
+
def __attribute_assign(key, can_overwrite, options)
|
21
44
|
name = key.to_s
|
22
45
|
has_attribute = attribute?(name)
|
23
46
|
|
24
47
|
__attribute_reader(name) unless has_attribute
|
25
|
-
__attributes_data__[name] = options[:default] if can_overwrite || !has_attribute
|
26
48
|
|
27
|
-
|
49
|
+
__attributes_data__[name] = __attributes_data_to_assign(name, options) if can_overwrite || !has_attribute
|
50
|
+
|
51
|
+
__call_after_attribute_assign__(name, options)
|
28
52
|
end
|
29
53
|
|
30
|
-
def
|
54
|
+
def __call_after_attribute_assign__(attr_name, options); end
|
31
55
|
|
56
|
+
# NOTE: can't be renamed! It is used by u-case v4.
|
32
57
|
def __attributes_set_after_inherit__(arg)
|
33
|
-
arg.each
|
58
|
+
arg.each do |key, val|
|
59
|
+
__attribute_assign(key, true, val ? { default: val } : {})
|
60
|
+
end
|
34
61
|
end
|
35
62
|
|
36
63
|
def attribute?(name)
|
@@ -38,27 +65,32 @@ module Micro
|
|
38
65
|
end
|
39
66
|
|
40
67
|
def attribute(name, options = Kind::Empty::HASH)
|
41
|
-
|
68
|
+
__attribute_assign(name, false, options)
|
42
69
|
end
|
43
70
|
|
44
71
|
def attributes(*args)
|
45
72
|
return __attributes.to_a if args.empty?
|
46
73
|
|
47
74
|
args.flatten!
|
75
|
+
|
76
|
+
options =
|
77
|
+
args.size > 1 && args.last.is_a?(::Hash) ? args.pop : Kind::Empty::HASH
|
78
|
+
|
48
79
|
args.each do |arg|
|
49
80
|
if arg.is_a?(String) || arg.is_a?(Symbol)
|
50
|
-
|
81
|
+
__attribute_assign(arg, false, options)
|
51
82
|
else
|
52
83
|
raise Kind::Error.new('String/Symbol'.freeze, arg)
|
53
84
|
end
|
54
85
|
end
|
55
86
|
end
|
56
87
|
|
88
|
+
# NOTE: can't be renamed! It is used by u-case v4.
|
57
89
|
module ForSubclasses
|
58
90
|
WRONG_NUMBER_OF_ARGS = 'wrong number of arguments (given 0, expected 1 or more)'.freeze
|
59
91
|
|
60
92
|
def attribute!(name, options = Kind::Empty::HASH)
|
61
|
-
|
93
|
+
__attribute_assign(name, true, options)
|
62
94
|
end
|
63
95
|
|
64
96
|
private_constant :WRONG_NUMBER_OF_ARGS
|
@@ -1,17 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Micro
|
4
|
-
module
|
5
|
-
module
|
6
|
-
def self.
|
3
|
+
module Micro::Attributes
|
4
|
+
module Utils
|
5
|
+
module Hashes
|
6
|
+
def self.stringify_keys(arg)
|
7
7
|
hash = Kind::Of.(::Hash, arg)
|
8
8
|
|
9
9
|
return hash if hash.empty?
|
10
|
+
return hash.transform_keys(&:to_s) if hash.respond_to?(:transform_keys)
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
hash.each_with_object({}) { |(key, val), memo| memo[key.to_s] = val }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.symbolize_keys(arg)
|
16
|
+
hash = Kind::Of.(::Hash, arg)
|
17
|
+
|
18
|
+
return hash if hash.empty?
|
19
|
+
return hash.transform_keys(&:to_sym) if hash.respond_to?(:transform_keys)
|
20
|
+
|
21
|
+
hash.each_with_object({}) { |(key, val), memo| memo[key.to_sym] = val }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.keys_as(type, hash)
|
25
|
+
return Kind::Of.(::Hash, hash) unless type
|
26
|
+
|
27
|
+
return symbolize_keys(hash) if type == Symbol
|
28
|
+
return stringify_keys(hash) if type == String
|
29
|
+
|
30
|
+
raise ArgumentError, 'first argument must be the class String or Symbol'.freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.get(hash, key)
|
34
|
+
value = hash[key.to_s]
|
35
|
+
|
36
|
+
value.nil? ? hash[key.to_sym] : value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module ExtractAttribute
|
41
|
+
def self.call(object, key:)
|
42
|
+
return object.public_send(key) if object.respond_to?(key)
|
43
|
+
|
44
|
+
Hashes.get(object, key) if object.respond_to?(:[])
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.from(object, keys:)
|
48
|
+
Kind::Of.(::Array, keys).each_with_object({}) do |key, memo|
|
49
|
+
memo[key] = call(object, key: key)
|
15
50
|
end
|
16
51
|
end
|
17
52
|
end
|
data/u-attributes.gemspec
CHANGED
@@ -9,8 +9,11 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ['Rodrigo Serradura']
|
10
10
|
spec.email = ['rodrigo.serradura@gmail.com']
|
11
11
|
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.description =
|
12
|
+
spec.summary = %q{Create "immutable" objects. No setters, just getters!}
|
13
|
+
spec.description =
|
14
|
+
"This gem allows you to define \"immutable\" objects, and your objects will have only getters and no setters. "\
|
15
|
+
"So, if you change some object attribute, you will have a new object instance. " \
|
16
|
+
"That is, you transform the object instead of modifying it."
|
14
17
|
spec.homepage = 'https://github.com/serradura/u-attributes'
|
15
18
|
spec.license = 'MIT'
|
16
19
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: u-attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kind
|
@@ -58,8 +58,10 @@ dependencies:
|
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '13.0'
|
61
|
-
description: This gem allows
|
62
|
-
|
61
|
+
description: This gem allows you to define "immutable" objects, and your objects will
|
62
|
+
have only getters and no setters. So, if you change some object attribute, you will
|
63
|
+
have a new object instance. That is, you transform the object instead of modifying
|
64
|
+
it.
|
63
65
|
email:
|
64
66
|
- rodrigo.serradura@gmail.com
|
65
67
|
executables: []
|
@@ -74,6 +76,7 @@ files:
|
|
74
76
|
- LICENSE.txt
|
75
77
|
- README.md
|
76
78
|
- Rakefile
|
79
|
+
- assets/u-attributes_logo_v1.png
|
77
80
|
- bin/console
|
78
81
|
- bin/setup
|
79
82
|
- lib/micro/attributes.rb
|
@@ -112,5 +115,5 @@ requirements: []
|
|
112
115
|
rubygems_version: 3.0.6
|
113
116
|
signing_key:
|
114
117
|
specification_version: 4
|
115
|
-
summary:
|
118
|
+
summary: Create "immutable" objects. No setters, just getters!
|
116
119
|
test_files: []
|