u-attributes 2.0.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.com/serradura/u-attributes.svg?branch=master)](https://travis-ci.com/serradura/u-attributes)
|
4
|
-
[![Maintainability](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/maintainability)](https://codeclimate.com/github/serradura/u-attributes/maintainability)
|
5
|
-
[![Test Coverage](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/test_coverage)](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: []
|