u-attributes 1.2.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.sh +9 -2
- data/.travis.yml +5 -5
- data/Gemfile +11 -2
- data/LICENSE.txt +2 -2
- data/README.md +418 -290
- data/Rakefile +5 -5
- data/assets/u-attributes_logo_v1.png +0 -0
- data/bin/console +4 -4
- data/lib/micro/attributes.rb +80 -41
- data/lib/micro/attributes/diff.rb +52 -0
- data/lib/micro/attributes/features.rb +41 -33
- data/lib/micro/attributes/features/activemodel_validations.rb +17 -23
- data/lib/micro/attributes/features/diff.rb +2 -50
- data/lib/micro/attributes/features/initialize.rb +5 -0
- data/lib/micro/attributes/features/initialize/strict.rb +19 -0
- data/lib/micro/attributes/macros.rb +57 -23
- data/lib/micro/attributes/utils.rb +36 -0
- data/lib/micro/attributes/version.rb +1 -1
- data/lib/micro/attributes/with.rb +6 -6
- data/u-attributes.gemspec +19 -12
- metadata +48 -11
- data/Gemfile.lock +0 -29
- data/lib/micro/attributes/attributes_utils.rb +0 -21
- data/lib/micro/attributes/features/strict_initialize.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31b9f3d0195fc8b0f865279a79a54bd9beb8b0644d3f1b4371b6aa4163264024
|
4
|
+
data.tar.gz: '048e8c549d655980eee398e8ae445a7eb6900143f98c8e04449d5e2c82f0c9e6'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a776e02b0708d566156ce8adc7d0ad5813a7c752f8f19615eeea734b46ee34995d69a8cab263ed1e1ffbba64140eee4a630ff18fdcb6cf54cd5fb0a404d6fe8
|
7
|
+
data.tar.gz: 4e7869905c50b2f0ea288f404900c3c2b645ca3c8cad6404c2d8aa8913a0ad87874b43288c8f408e1a23252d9ab114fa8664a6a21914f71c2ffbf4ee04bb3d68
|
data/.gitignore
CHANGED
data/.travis.sh
CHANGED
@@ -20,5 +20,12 @@ ACTIVEMODEL_VERSION='5.0' bundle exec rake test
|
|
20
20
|
ACTIVEMODEL_VERSION='5.1' bundle update
|
21
21
|
ACTIVEMODEL_VERSION='5.1' bundle exec rake test
|
22
22
|
|
23
|
-
|
24
|
-
ACTIVEMODEL_VERSION='5.2' bundle
|
23
|
+
if [[ ! $ruby_v =~ '2.2.0' ]]; then
|
24
|
+
ACTIVEMODEL_VERSION='5.2' bundle update
|
25
|
+
ACTIVEMODEL_VERSION='5.2' bundle exec rake test
|
26
|
+
fi
|
27
|
+
|
28
|
+
if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]; then
|
29
|
+
ACTIVEMODEL_VERSION='6.0' bundle update
|
30
|
+
ACTIVEMODEL_VERSION='6.0' bundle exec rake test
|
31
|
+
fi
|
data/.travis.yml
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
|
-
sudo: false
|
4
|
-
|
5
3
|
rvm:
|
6
4
|
- 2.2.2
|
7
5
|
- 2.3.0
|
8
6
|
- 2.4.0
|
9
7
|
- 2.5.0
|
10
8
|
- 2.6.0
|
9
|
+
- 2.7.0
|
10
|
+
- truffleruby-head
|
11
11
|
|
12
12
|
cache: bundler
|
13
13
|
|
@@ -20,9 +20,9 @@ install: bundle install --jobs=3 --retry=3
|
|
20
20
|
before_script:
|
21
21
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
22
22
|
- chmod +x ./cc-test-reporter
|
23
|
-
-
|
23
|
+
- './cc-test-reporter before-build'
|
24
24
|
|
25
|
-
script:
|
25
|
+
script: './.travis.sh'
|
26
26
|
|
27
27
|
after_success:
|
28
|
-
-
|
28
|
+
- './cc-test-reporter after-build -t simplecov'
|
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
|
@@ -10,7 +12,7 @@ activemodel = case activemodel_version
|
|
10
12
|
when '5.0' then '5.0.7'
|
11
13
|
when '5.1' then '5.1.7'
|
12
14
|
when '5.2' then '5.2.3'
|
13
|
-
when '6.0' then '6.0.0
|
15
|
+
when '6.0' then '6.0.0'
|
14
16
|
end
|
15
17
|
|
16
18
|
if activemodel_version < '6.1'
|
@@ -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/LICENSE.txt
CHANGED
@@ -3,7 +3,7 @@ The MIT License (MIT)
|
|
3
3
|
Copyright (c) 2019 Rodrigo Serradura
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the
|
6
|
+
of this software and associated documentation files (the 'Software'), to deal
|
7
7
|
in the Software without restriction, including without limitation the rights
|
8
8
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
9
|
copies of the Software, and to permit persons to whom the Software is
|
@@ -12,7 +12,7 @@ furnished to do so, subject to the following conditions:
|
|
12
12
|
The above copyright notice and this permission notice shall be included in
|
13
13
|
all copies or substantial portions of the Software.
|
14
14
|
|
15
|
-
THE SOFTWARE IS PROVIDED
|
15
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
17
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
data/README.md
CHANGED
@@ -1,79 +1,99 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
1
|
+
<p align="center">
|
2
|
+
<img src="./assets/u-attributes_logo_v1.png" alt='Create "immutable" objects. No setters, just getters!'>
|
3
|
+
|
4
|
+
<p align="center"><i>Create "immutable" objects. No setters, just getters!</i></p>
|
5
|
+
<br>
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<p align="center">
|
9
|
+
<img src="https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
|
10
|
+
|
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 -->
|
32
|
+
- [Installation](#installation)
|
33
|
+
- [Compatibility](#compatibility)
|
34
|
+
- [Usage](#usage)
|
35
|
+
- [How to define attributes?](#how-to-define-attributes)
|
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)
|
39
|
+
- [`Micro::Attributes#attribute`](#microattributesattribute)
|
40
|
+
- [`Micro::Attributes#attribute!`](#microattributesattribute-1)
|
41
|
+
- [How to define multiple attributes?](#how-to-define-multiple-attributes)
|
42
|
+
- [`Micro::Attributes.with(:initialize)`](#microattributeswithinitialize)
|
43
|
+
- [`#with_attribute()`](#with_attribute)
|
44
|
+
- [`#with_attributes()`](#with_attributes)
|
45
|
+
- [Defining default values to the attributes](#defining-default-values-to-the-attributes)
|
46
|
+
- [The strict initializer](#the-strict-initializer)
|
47
|
+
- [Is it possible to inherit the attributes?](#is-it-possible-to-inherit-the-attributes)
|
48
|
+
- [`.attribute!()`](#attribute)
|
49
|
+
- [How to query the attributes?](#how-to-query-the-attributes)
|
50
|
+
- [Built-in extensions](#built-in-extensions)
|
51
|
+
- [Picking specific features](#picking-specific-features)
|
52
|
+
- [`Micro::Attributes.with`](#microattributeswith)
|
53
|
+
- [`Micro::Attributes.without`](#microattributeswithout)
|
54
|
+
- [Picking all the features](#picking-all-the-features)
|
55
|
+
- [Extensions](#extensions)
|
56
|
+
- [`ActiveModel::Validation` extension](#activemodelvalidation-extension)
|
57
|
+
- [`.attribute()` options](#attribute-options)
|
25
58
|
- [Diff extension](#diff-extension)
|
26
59
|
- [Initialize extension](#initialize-extension)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
60
|
+
- [Strict mode](#strict-mode)
|
61
|
+
- [Development](#development)
|
62
|
+
- [Contributing](#contributing)
|
63
|
+
- [License](#license)
|
64
|
+
- [Code of Conduct](#code-of-conduct)
|
32
65
|
|
33
|
-
|
66
|
+
# Installation
|
34
67
|
|
35
|
-
|
36
|
-
|
37
|
-
## Installation
|
38
|
-
|
39
|
-
Add this line to your application's Gemfile:
|
68
|
+
Add this line to your application's Gemfile and `bundle install`:
|
40
69
|
|
41
70
|
```ruby
|
42
71
|
gem 'u-attributes'
|
43
72
|
```
|
44
73
|
|
45
|
-
|
46
|
-
|
47
|
-
$ bundle
|
74
|
+
# Compatibility
|
48
75
|
|
49
|
-
|
76
|
+
| u-attributes | branch | ruby | activemodel |
|
77
|
+
| -------------- | ------- | -------- | ------------- |
|
78
|
+
| 2.2.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
79
|
+
| 1.2.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
50
80
|
|
51
|
-
|
81
|
+
> **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
|
52
82
|
|
53
|
-
|
83
|
+
[⬆️ Back to Top](#table-of-contents-)
|
54
84
|
|
55
|
-
|
56
|
-
```ruby
|
57
|
-
# Bundler will do it automatically, but if you desire to do a manual require.
|
58
|
-
# Use one of the following options:
|
59
|
-
|
60
|
-
require 'micro/attributes'
|
85
|
+
# Usage
|
61
86
|
|
62
|
-
|
87
|
+
## How to define attributes?
|
63
88
|
|
64
|
-
|
65
|
-
```
|
89
|
+
By default, you must define the class constructor.
|
66
90
|
|
67
|
-
### How to define attributes?
|
68
91
|
```ruby
|
69
|
-
|
70
|
-
# By default you must to define the class constructor.
|
71
|
-
|
72
92
|
class Person
|
73
93
|
include Micro::Attributes
|
74
94
|
|
75
|
-
attribute :name
|
76
95
|
attribute :age
|
96
|
+
attribute :name
|
77
97
|
|
78
98
|
def initialize(name: 'John Doe', age:)
|
79
99
|
@name, @age = name, age
|
@@ -82,26 +102,28 @@ end
|
|
82
102
|
|
83
103
|
person = Person.new(age: 21)
|
84
104
|
|
85
|
-
|
86
|
-
|
105
|
+
person.age # 21
|
106
|
+
person.name # John Doe
|
87
107
|
|
88
|
-
# By design
|
89
|
-
# If you try to call a setter
|
108
|
+
# By design the attributes are always exposed as reader methods (getters).
|
109
|
+
# If you try to call a setter you will see a NoMethodError.
|
90
110
|
#
|
91
111
|
# person.name = 'Rodrigo'
|
92
|
-
# NoMethodError (undefined method `name=' for #<Person:0x0000... @name=
|
112
|
+
# NoMethodError (undefined method `name=' for #<Person:0x0000... @name='John Doe', @age=21>)
|
113
|
+
```
|
114
|
+
|
115
|
+
[⬆️ Back to Top](#table-of-contents-)
|
93
116
|
|
94
|
-
|
95
|
-
# self.attributes= #
|
96
|
-
#------------------#
|
117
|
+
### `Micro::Attributes#attributes=`
|
97
118
|
|
98
|
-
|
119
|
+
This is a protected method to make easier the assignment in a constructor. e.g.
|
99
120
|
|
121
|
+
```ruby
|
100
122
|
class Person
|
101
123
|
include Micro::Attributes
|
102
124
|
|
103
|
-
attribute :name, 'John Doe' # .attribute() accepts a second arg as its default value
|
104
125
|
attribute :age
|
126
|
+
attribute :name, default: 'John Doe'
|
105
127
|
|
106
128
|
def initialize(options)
|
107
129
|
self.attributes = options
|
@@ -110,46 +132,115 @@ end
|
|
110
132
|
|
111
133
|
person = Person.new(age: 20)
|
112
134
|
|
113
|
-
|
114
|
-
|
135
|
+
person.age # 20
|
136
|
+
person.name # John Doe
|
137
|
+
```
|
115
138
|
|
116
|
-
|
117
|
-
# #attribute() #
|
118
|
-
#--------------#
|
119
|
-
#
|
120
|
-
# Use the #attribute() method with a valid attribute name to get its value
|
139
|
+
#### How to extract attributes from an object or hash?
|
121
140
|
|
122
|
-
|
123
|
-
|
124
|
-
puts person.attribute('foo') # nil
|
141
|
+
You can extract attributes using the `extract_attributes_from` method, it will try to fetch attributes from the
|
142
|
+
object using either the `object[attribute_key]` accessor or the reader method `object.attribute_key`.
|
125
143
|
|
126
|
-
|
127
|
-
|
144
|
+
```ruby
|
145
|
+
class Person
|
146
|
+
include Micro::Attributes
|
147
|
+
|
148
|
+
attribute :age
|
149
|
+
attribute :name, default: 'John Doe'
|
128
150
|
|
151
|
+
def initialize(user:)
|
152
|
+
self.attributes = extract_attributes_from(user)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# extracting from an object
|
157
|
+
|
158
|
+
class User
|
159
|
+
attr_accessor :age, :name
|
160
|
+
end
|
161
|
+
|
162
|
+
user = User.new
|
163
|
+
user.age = 20
|
164
|
+
|
165
|
+
person = Person.new(user: user)
|
166
|
+
|
167
|
+
person.age # 20
|
168
|
+
person.name # John Doe
|
169
|
+
|
170
|
+
# extracting from a hash
|
171
|
+
|
172
|
+
another_person = Person.new(user: { age: 55, name: 'Julia Not Roberts' })
|
173
|
+
|
174
|
+
another_person.age # 55
|
175
|
+
another_person.name # Julia Not Roberts
|
176
|
+
```
|
177
|
+
|
178
|
+
#### Is it possible to define an attribute as required?
|
179
|
+
|
180
|
+
You only need to use the `required: true` option.
|
181
|
+
|
182
|
+
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).
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
class Person
|
186
|
+
include Micro::Attributes
|
187
|
+
|
188
|
+
attribute :age
|
189
|
+
attribute :name, required: true
|
190
|
+
|
191
|
+
def initialize(attributes)
|
192
|
+
self.attributes = attributes
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
Person.new(age: 32) # ArgumentError (missing keyword: :name)
|
197
|
+
```
|
198
|
+
|
199
|
+
[⬆️ Back to Top](#table-of-contents-)
|
200
|
+
|
201
|
+
### `Micro::Attributes#attribute`
|
202
|
+
|
203
|
+
Use this method with a valid attribute name to get its value.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
person = Person.new(age: 20)
|
207
|
+
|
208
|
+
person.attribute('age') # 20
|
209
|
+
person.attribute(:name) # John Doe
|
210
|
+
person.attribute('foo') # nil
|
211
|
+
```
|
212
|
+
|
213
|
+
If you pass a block, it will be executed only if the attribute was valid.
|
214
|
+
|
215
|
+
```ruby
|
129
216
|
person.attribute(:name) { |value| puts value } # John Doe
|
130
217
|
person.attribute('age') { |value| puts value } # 20
|
131
|
-
person.attribute('foo') { |value| puts value } # !! Nothing happened, because of the attribute
|
218
|
+
person.attribute('foo') { |value| puts value } # !! Nothing happened, because of the attribute doesn't exist.
|
219
|
+
```
|
132
220
|
|
133
|
-
|
134
|
-
# #attribute!() #
|
135
|
-
#---------------#
|
136
|
-
#
|
137
|
-
# Works like the #attribute() method, but will raise an exception when the attribute not exist.
|
221
|
+
[⬆️ Back to Top](#table-of-contents-)
|
138
222
|
|
139
|
-
|
140
|
-
person.attribute!('foo') { |value| puts value } # NameError (undefined attribute `foo)
|
141
|
-
```
|
223
|
+
### `Micro::Attributes#attribute!`
|
142
224
|
|
143
|
-
|
225
|
+
Works like the `#attribute` method, but it will raise an exception when the attribute doesn't exist.
|
144
226
|
|
145
227
|
```ruby
|
228
|
+
person.attribute!('foo') # NameError (undefined attribute `foo)
|
229
|
+
|
230
|
+
person.attribute!('foo') { |value| value } # NameError (undefined attribute `foo)
|
231
|
+
```
|
232
|
+
|
233
|
+
[⬆️ Back to Top](#table-of-contents-)
|
234
|
+
|
235
|
+
## How to define multiple attributes?
|
146
236
|
|
147
|
-
|
237
|
+
Use `.attributes` with a list of attribute names.
|
148
238
|
|
239
|
+
```ruby
|
149
240
|
class Person
|
150
241
|
include Micro::Attributes
|
151
242
|
|
152
|
-
attributes :age, name
|
243
|
+
attributes :age, :name
|
153
244
|
|
154
245
|
def initialize(options)
|
155
246
|
self.attributes = options
|
@@ -158,89 +249,132 @@ end
|
|
158
249
|
|
159
250
|
person = Person.new(age: 32)
|
160
251
|
|
161
|
-
|
162
|
-
|
252
|
+
person.name # nil
|
253
|
+
person.age # 32
|
163
254
|
```
|
164
255
|
|
165
|
-
|
166
|
-
|
256
|
+
> **Note:** This method can't define default values. To do this, use the `#attribute()` method.
|
257
|
+
|
258
|
+
[⬆️ Back to Top](#table-of-contents-)
|
259
|
+
|
260
|
+
## `Micro::Attributes.with(:initialize)`
|
261
|
+
|
262
|
+
Use `Micro::Attributes.with(:initialize)` to define a constructor to assign the attributes. e.g.
|
167
263
|
|
168
264
|
```ruby
|
169
265
|
class Person
|
170
|
-
include Micro::Attributes.
|
266
|
+
include Micro::Attributes.with(:initialize)
|
171
267
|
|
172
|
-
|
268
|
+
attribute :age, required: true
|
269
|
+
attribute :name, default: 'John Doe'
|
173
270
|
end
|
174
271
|
|
175
272
|
person = Person.new(age: 18)
|
176
273
|
|
177
|
-
|
178
|
-
|
274
|
+
person.age # 18
|
275
|
+
person.name # John Doe
|
276
|
+
```
|
179
277
|
|
180
|
-
|
181
|
-
|
182
|
-
##############################################
|
278
|
+
This extension enables two methods for your objects.
|
279
|
+
The `#with_attribute()` and `#with_attributes()`.
|
183
280
|
|
184
|
-
|
185
|
-
# #with_attribute() #
|
186
|
-
#-------------------#
|
281
|
+
### `#with_attribute()`
|
187
282
|
|
283
|
+
```ruby
|
188
284
|
another_person = person.with_attribute(:age, 21)
|
189
285
|
|
190
|
-
|
191
|
-
|
192
|
-
|
286
|
+
another_person.age # 21
|
287
|
+
another_person.name # John Doe
|
288
|
+
another_person.equal?(person) # false
|
289
|
+
```
|
193
290
|
|
194
|
-
|
195
|
-
# #with_attributes() #
|
196
|
-
#--------------------#
|
197
|
-
#
|
198
|
-
# Use it to assign multiple attributes
|
291
|
+
### `#with_attributes()`
|
199
292
|
|
293
|
+
Use it to assign multiple attributes
|
294
|
+
```ruby
|
200
295
|
other_person = person.with_attributes(name: 'Serradura', age: 32)
|
201
296
|
|
202
|
-
|
203
|
-
|
204
|
-
|
297
|
+
other_person.age # 32
|
298
|
+
other_person.name # Serradura
|
299
|
+
other_person.equal?(person) # false
|
300
|
+
```
|
205
301
|
|
206
|
-
|
207
|
-
#
|
208
|
-
# Person.new(1)
|
209
|
-
# ArgumentError (argument must be a Hash)
|
302
|
+
If you pass a value different of a Hash, a Kind::Error will be raised.
|
210
303
|
|
211
|
-
|
212
|
-
#
|
213
|
-
|
304
|
+
```ruby
|
305
|
+
Person.new(1) # Kind::Error (1 expected to be a kind of Hash)
|
306
|
+
```
|
307
|
+
|
308
|
+
[⬆️ Back to Top](#table-of-contents-)
|
309
|
+
|
310
|
+
## Defining default values to the attributes
|
311
|
+
|
312
|
+
To do this, you only need make use of the `default:` keyword. e.g.
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
class Person
|
316
|
+
include Micro::Attributes.with(:initialize)
|
317
|
+
|
318
|
+
attribute :age
|
319
|
+
attribute :name, default: 'John Doe'
|
320
|
+
end
|
321
|
+
```
|
322
|
+
|
323
|
+
There are two different strategies to define default values.
|
324
|
+
1. Pass a regular object, like in the previous example.
|
325
|
+
2. Pass a `proc`/`lambda`, and if it has an argument you will receive the attribute value to do something before assign it.
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
class Person
|
329
|
+
include Micro::Attributes.with(:initialize)
|
330
|
+
|
331
|
+
attribute :age, default: -> age { age&.to_i }
|
332
|
+
attribute :name, default: -> name { String(name || 'John Doe').strip }
|
333
|
+
end
|
334
|
+
```
|
335
|
+
|
336
|
+
[⬆️ Back to Top](#table-of-contents-)
|
337
|
+
|
338
|
+
## The strict initializer
|
214
339
|
|
215
|
-
|
340
|
+
Use `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords.
|
216
341
|
|
342
|
+
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).
|
343
|
+
|
344
|
+
```ruby
|
217
345
|
class StrictPerson
|
218
|
-
include Micro::Attributes.
|
346
|
+
include Micro::Attributes.with(initialize: :strict)
|
219
347
|
|
220
|
-
|
348
|
+
attribute :age
|
349
|
+
attribute :name, default: 'John Doe'
|
221
350
|
end
|
222
351
|
|
223
|
-
StrictPerson.new({})
|
352
|
+
StrictPerson.new({}) # ArgumentError (missing keyword: :age)
|
353
|
+
```
|
224
354
|
|
225
|
-
|
226
|
-
# ArgumentError (missing keyword: :age)
|
355
|
+
An attribute with a default value can be omitted.
|
227
356
|
|
357
|
+
``` ruby
|
228
358
|
person_without_age = StrictPerson.new(age: nil)
|
229
359
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
# Except for this validation when initializing,
|
234
|
-
# the `to_initialize!` method will works in the same ways of `to_initialize`.
|
360
|
+
person_without_age.age # nil
|
361
|
+
person_without_age.name # 'John Doe'
|
235
362
|
```
|
236
363
|
|
237
|
-
|
364
|
+
> **Note:** Except for this validation the `.with(initialize: :strict)` method will works in the same ways of `.with(:initialize)`.
|
365
|
+
|
366
|
+
[⬆️ Back to Top](#table-of-contents-)
|
367
|
+
|
368
|
+
## Is it possible to inherit the attributes?
|
369
|
+
|
370
|
+
Yes. e.g.
|
238
371
|
|
239
372
|
```ruby
|
240
373
|
class Person
|
241
|
-
include Micro::Attributes.
|
374
|
+
include Micro::Attributes.with(:initialize)
|
242
375
|
|
243
|
-
|
376
|
+
attribute :age
|
377
|
+
attribute :name, default: 'John Doe'
|
244
378
|
end
|
245
379
|
|
246
380
|
class Subclass < Person # Will preserve the parent class attributes
|
@@ -249,42 +383,48 @@ end
|
|
249
383
|
|
250
384
|
instance = Subclass.new({})
|
251
385
|
|
252
|
-
|
253
|
-
|
254
|
-
|
386
|
+
instance.name # John Doe
|
387
|
+
instance.respond_to?(:age) # true
|
388
|
+
instance.respond_to?(:foo) # true
|
389
|
+
```
|
390
|
+
|
391
|
+
[⬆️ Back to Top](#table-of-contents-)
|
255
392
|
|
256
|
-
|
257
|
-
# .attribute!() or .attributes!() #
|
258
|
-
#---------------------------------#
|
393
|
+
### `.attribute!()`
|
259
394
|
|
260
|
-
|
395
|
+
This method allows us to redefine the attributes default data that was defined in the parent class. e.g.
|
261
396
|
|
397
|
+
```ruby
|
262
398
|
class AnotherSubclass < Person
|
263
|
-
attribute! :name, 'Alfa'
|
399
|
+
attribute! :name, default: 'Alfa'
|
264
400
|
end
|
265
401
|
|
266
402
|
alfa_person = AnotherSubclass.new({})
|
267
403
|
|
268
|
-
|
269
|
-
|
404
|
+
alfa_person.name # 'Alfa'
|
405
|
+
alfa_person.age # nil
|
270
406
|
|
271
407
|
class SubSubclass < Subclass
|
272
|
-
|
408
|
+
attribute! :age, default: 0
|
409
|
+
attribute! :name, default: 'Beta'
|
273
410
|
end
|
274
411
|
|
275
412
|
beta_person = SubSubclass.new({})
|
276
413
|
|
277
|
-
|
278
|
-
|
414
|
+
beta_person.name # 'Beta'
|
415
|
+
beta_person.age # 0
|
279
416
|
```
|
280
417
|
|
281
|
-
|
418
|
+
[⬆️ Back to Top](#table-of-contents-)
|
419
|
+
|
420
|
+
## How to query the attributes?
|
282
421
|
|
283
422
|
```ruby
|
284
423
|
class Person
|
285
424
|
include Micro::Attributes
|
286
425
|
|
287
|
-
|
426
|
+
attribute :age
|
427
|
+
attribute :name, default: 'John Doe'
|
288
428
|
|
289
429
|
def initialize(options)
|
290
430
|
self.attributes = options
|
@@ -295,36 +435,42 @@ end
|
|
295
435
|
# .attributes() #
|
296
436
|
#---------------#
|
297
437
|
|
298
|
-
|
438
|
+
Person.attributes # ['name', 'age']
|
299
439
|
|
300
440
|
#---------------#
|
301
441
|
# .attribute?() #
|
302
442
|
#---------------#
|
303
443
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
444
|
+
Person.attribute?(:name) # true
|
445
|
+
Person.attribute?('name') # true
|
446
|
+
Person.attribute?('foo') # false
|
447
|
+
Person.attribute?(:foo) # false
|
308
448
|
|
309
449
|
# ---
|
310
450
|
|
311
451
|
person = Person.new(age: 20)
|
312
452
|
|
453
|
+
#---------------------#
|
454
|
+
# #defined_attributes #
|
455
|
+
#---------------------#
|
456
|
+
|
457
|
+
person.defined_attributes # ['name', 'age']
|
458
|
+
|
313
459
|
#---------------#
|
314
460
|
# #attribute?() #
|
315
461
|
#---------------#
|
316
462
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
463
|
+
person.attribute?(:name) # true
|
464
|
+
person.attribute?('name') # true
|
465
|
+
person.attribute?('foo') # false
|
466
|
+
person.attribute?(:foo) # false
|
321
467
|
|
322
468
|
#---------------#
|
323
469
|
# #attributes() #
|
324
470
|
#---------------#
|
325
471
|
|
326
|
-
|
327
|
-
|
472
|
+
person.attributes # {'age'=>20, 'name'=>'John Doe'}
|
473
|
+
Person.new(name: 'John').attributes # {'age'=>nil, 'name'=>'John'}
|
328
474
|
|
329
475
|
#---------------------#
|
330
476
|
# #attributes(*names) #
|
@@ -333,113 +479,78 @@ p Person.new(name: 'John').attributes # {"age"=>nil, "name"=>"John"}
|
|
333
479
|
# Slices the attributes to include only the given keys.
|
334
480
|
# Returns a hash containing the given keys (in their types).
|
335
481
|
|
336
|
-
|
337
|
-
|
338
|
-
|
482
|
+
person.attributes(:age) # {age: 20}
|
483
|
+
person.attributes(:age, :name) # {age: 20, name: 'John Doe'}
|
484
|
+
person.attributes('age', 'name') # {'age'=>20, 'name'=>'John Doe'}
|
339
485
|
```
|
340
486
|
|
341
|
-
|
487
|
+
[⬆️ Back to Top](#table-of-contents-)
|
342
488
|
|
343
|
-
|
489
|
+
# Built-in extensions
|
344
490
|
|
345
|
-
|
346
|
-
1. only one feature, use the `Micro::Attributes.feature()` method.
|
347
|
-
2. except one or more features, use the `Micro::Attributes.without()` method.
|
491
|
+
You can use the method `Micro::Attributes.with()` to combine and require only the features that better fit your needs.
|
348
492
|
|
349
|
-
|
350
|
-
#===========================#
|
351
|
-
# Loading specific features #
|
352
|
-
#===========================#
|
353
|
-
|
354
|
-
class Job
|
355
|
-
include Micro::Attributes.feature(:diff)
|
493
|
+
But, if you desire except one or more features, use the `Micro::Attributes.without()` method.
|
356
494
|
|
357
|
-
|
358
|
-
attribute :state, 'sleeping'
|
495
|
+
## Picking specific features
|
359
496
|
|
360
|
-
|
361
|
-
self.attributes = options
|
362
|
-
end
|
363
|
-
end
|
497
|
+
### `Micro::Attributes.with`
|
364
498
|
|
365
|
-
|
366
|
-
|
367
|
-
# --- #
|
368
|
-
#======================#
|
499
|
+
```ruby
|
500
|
+
Micro::Attributes.with(:initialize)
|
369
501
|
|
370
|
-
|
371
|
-
include Micro::Attributes.features
|
502
|
+
Micro::Attributes.with(initialize: :strict)
|
372
503
|
|
373
|
-
|
374
|
-
end
|
504
|
+
Micro::Attributes.with(:diff, :initialize)
|
375
505
|
|
376
|
-
|
377
|
-
# If `Micro::Attributes.features()` be invoked without arguments, a module with all features will be returned.
|
506
|
+
Micro::Attributes.with(:diff, initialize: :strict)
|
378
507
|
|
379
|
-
|
380
|
-
# Using the .with() method alias and adding the strict initialize extension. #
|
381
|
-
#----------------------------------------------------------------------------#
|
382
|
-
class Job
|
383
|
-
include Micro::Attributes.with(:strict_initialize, :diff)
|
508
|
+
Micro::Attributes.with(:activemodel_validations)
|
384
509
|
|
385
|
-
|
386
|
-
end
|
510
|
+
Micro::Attributes.with(:activemodel_validations, :diff)
|
387
511
|
|
388
|
-
|
389
|
-
|
390
|
-
#
|
391
|
-
# class Job
|
392
|
-
# include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: diff, initialize, activemodel_validations)
|
393
|
-
# end
|
512
|
+
Micro::Attributes.with(:activemodel_validations, :diff, initialize: :strict)
|
513
|
+
```
|
394
514
|
|
395
|
-
|
396
|
-
# Alternatives to the methods above #
|
397
|
-
#===================================#
|
515
|
+
The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared.
|
398
516
|
|
399
|
-
|
400
|
-
# Via Micro::Attributes.to_initialize() #
|
401
|
-
#---------------------------------------#
|
517
|
+
```ruby
|
402
518
|
class Job
|
403
|
-
include Micro::Attributes.
|
404
|
-
|
405
|
-
# Same of `include Micro::Attributes.with(:initialize, :diff, :activemodel_validations)`
|
519
|
+
include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :activemodel_validations, :diff, :initialize)
|
406
520
|
end
|
521
|
+
```
|
407
522
|
|
408
|
-
|
409
|
-
# Via Micro::Attributes.to_initialize!() #
|
410
|
-
#----------------------------------------#
|
411
|
-
class Job
|
412
|
-
include Micro::Attributes.to_initialize!(diff: false, activemodel_validations: true)
|
523
|
+
### `Micro::Attributes.without`
|
413
524
|
|
414
|
-
|
415
|
-
end
|
525
|
+
Picking *except* one or more features
|
416
526
|
|
417
|
-
|
418
|
-
#
|
419
|
-
# ----- #
|
420
|
-
#=====================================#
|
527
|
+
```ruby
|
528
|
+
Micro::Attributes.without(:diff) # will load :activemodel_validations and initialize: :strict
|
421
529
|
|
422
|
-
|
423
|
-
|
530
|
+
Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations and :diff
|
531
|
+
```
|
424
532
|
|
425
|
-
|
426
|
-
end
|
533
|
+
## Picking all the features
|
427
534
|
|
428
|
-
|
429
|
-
|
535
|
+
```ruby
|
536
|
+
Micro::Attributes.with_all_features
|
430
537
|
```
|
431
538
|
|
432
|
-
|
539
|
+
[⬆️ Back to Top](#table-of-contents-)
|
433
540
|
|
434
|
-
|
541
|
+
## Extensions
|
542
|
+
|
543
|
+
### `ActiveModel::Validation` extension
|
544
|
+
|
545
|
+
If your application uses ActiveModel as a dependency (like a regular Rails app). You will be enabled to use the `activemodel_validations` extension.
|
435
546
|
|
436
547
|
```ruby
|
437
548
|
class Job
|
438
|
-
|
439
|
-
|
440
|
-
|
549
|
+
include Micro::Attributes.with(:activemodel_validations)
|
550
|
+
|
551
|
+
attribute :id
|
552
|
+
attribute :state, default: 'sleeping'
|
441
553
|
|
442
|
-
attributes :id, state: 'sleeping'
|
443
554
|
validates! :id, :state, presence: true
|
444
555
|
end
|
445
556
|
|
@@ -447,10 +558,31 @@ Job.new({}) # ActiveModel::StrictValidationFailed (Id can't be blank)
|
|
447
558
|
|
448
559
|
job = Job.new(id: 1)
|
449
560
|
|
450
|
-
|
451
|
-
|
561
|
+
job.id # 1
|
562
|
+
job.state # 'sleeping'
|
563
|
+
```
|
564
|
+
|
565
|
+
#### `.attribute()` options
|
566
|
+
|
567
|
+
You can use the `validate` or `validates` options to define your attributes. e.g.
|
568
|
+
|
569
|
+
```ruby
|
570
|
+
class Job
|
571
|
+
include Micro::Attributes.with(:activemodel_validations)
|
572
|
+
|
573
|
+
attribute :id, validates: { presence: true }
|
574
|
+
attribute :state, validate: :must_be_a_filled_string
|
575
|
+
|
576
|
+
def must_be_a_filled_string
|
577
|
+
return if state.is_a?(String) && state.present?
|
578
|
+
|
579
|
+
errors.add(:state, 'must be a filled string')
|
580
|
+
end
|
581
|
+
end
|
452
582
|
```
|
453
583
|
|
584
|
+
[⬆️ Back to Top](#table-of-contents-)
|
585
|
+
|
454
586
|
### Diff extension
|
455
587
|
|
456
588
|
Provides a way to track changes in your object attributes.
|
@@ -459,21 +591,20 @@ Provides a way to track changes in your object attributes.
|
|
459
591
|
require 'securerandom'
|
460
592
|
|
461
593
|
class Job
|
462
|
-
|
463
|
-
# include Micro::Attributes.to_initialize(diff: true)
|
464
|
-
include Micro::Attributes.features(:initialize, :diff)
|
594
|
+
include Micro::Attributes.with(:initialize, :diff)
|
465
595
|
|
466
|
-
|
596
|
+
attribute :id
|
597
|
+
attribute :state, default: 'sleeping'
|
467
598
|
end
|
468
599
|
|
469
600
|
job = Job.new(id: SecureRandom.uuid())
|
470
601
|
|
471
|
-
|
472
|
-
|
602
|
+
job.id # A random UUID generated from SecureRandom.uuid(). e.g: 'e68bcc74-b91c-45c2-a904-12f1298cc60e'
|
603
|
+
job.state # 'sleeping'
|
473
604
|
|
474
605
|
job_running = job.with_attribute(:state, 'running')
|
475
606
|
|
476
|
-
|
607
|
+
job_running.state # 'running'
|
477
608
|
|
478
609
|
job_changes = job.diff_attributes(job_running)
|
479
610
|
|
@@ -481,50 +612,49 @@ job_changes = job.diff_attributes(job_running)
|
|
481
612
|
# #present?, #blank?, #empty? #
|
482
613
|
#-----------------------------#
|
483
614
|
|
484
|
-
|
485
|
-
|
486
|
-
|
615
|
+
job_changes.present? # true
|
616
|
+
job_changes.blank? # false
|
617
|
+
job_changes.empty? # false
|
487
618
|
|
488
619
|
#-----------#
|
489
620
|
# #changed? #
|
490
621
|
#-----------#
|
491
|
-
|
622
|
+
job_changes.changed? # true
|
492
623
|
|
493
|
-
|
624
|
+
job_changes.changed?(:id) # false
|
494
625
|
|
495
|
-
|
496
|
-
|
626
|
+
job_changes.changed?(:state) # true
|
627
|
+
job_changes.changed?(:state, from: 'sleeping', to: 'running') # true
|
497
628
|
|
498
629
|
#----------------#
|
499
630
|
# #differences() #
|
500
631
|
#----------------#
|
501
|
-
|
632
|
+
job_changes.differences # {'state'=> {'from' => 'sleeping', 'to' => 'running'}}
|
502
633
|
```
|
503
634
|
|
635
|
+
[⬆️ Back to Top](#table-of-contents-)
|
636
|
+
|
504
637
|
### Initialize extension
|
505
638
|
|
506
639
|
1. Creates a constructor to assign the attributes.
|
507
|
-
2.
|
640
|
+
2. Add methods to build new instances when some data was assigned.
|
508
641
|
|
509
642
|
```ruby
|
510
643
|
class Job
|
511
|
-
|
512
|
-
# include Micro::Attributes.feature(:initialize)
|
513
|
-
# include Micro::Attributes.features(:initialize)
|
514
|
-
include Micro::Attributes.to_initialize
|
644
|
+
include Micro::Attributes.with(:initialize)
|
515
645
|
|
516
646
|
attributes :id, :state
|
517
647
|
end
|
518
648
|
|
519
649
|
job_null = Job.new({})
|
520
650
|
|
521
|
-
|
522
|
-
|
651
|
+
job.id # nil
|
652
|
+
job.state # nil
|
523
653
|
|
524
654
|
job = Job.new(id: 1, state: 'sleeping')
|
525
655
|
|
526
|
-
|
527
|
-
|
656
|
+
job.id # 1
|
657
|
+
job.state # 'sleeping'
|
528
658
|
|
529
659
|
##############################################
|
530
660
|
# Assigning new values to get a new instance #
|
@@ -536,9 +666,9 @@ p job.state # "sleeping"
|
|
536
666
|
|
537
667
|
new_job = job.with_attribute(:state, 'running')
|
538
668
|
|
539
|
-
|
540
|
-
|
541
|
-
|
669
|
+
new_job.id # 1
|
670
|
+
new_job.state # running
|
671
|
+
new_job.equal?(job) # false
|
542
672
|
|
543
673
|
#--------------------#
|
544
674
|
# #with_attributes() #
|
@@ -548,12 +678,14 @@ puts new_job.equal?(job) # false
|
|
548
678
|
|
549
679
|
other_job = job.with_attributes(id: 2, state: 'killed')
|
550
680
|
|
551
|
-
|
552
|
-
|
553
|
-
|
681
|
+
other_job.id # 2
|
682
|
+
other_job.state # killed
|
683
|
+
other_job.equal?(job) # false
|
554
684
|
```
|
555
685
|
|
556
|
-
|
686
|
+
[⬆️ Back to Top](#table-of-contents-)
|
687
|
+
|
688
|
+
#### Strict mode
|
557
689
|
|
558
690
|
1. Creates a constructor to assign the attributes.
|
559
691
|
2. Adds methods to build new instances when some data was assigned.
|
@@ -561,16 +693,13 @@ puts other_job.equal?(job) # false
|
|
561
693
|
|
562
694
|
```ruby
|
563
695
|
class Job
|
564
|
-
|
565
|
-
# include Micro::Attributes.feature(:strict_initialize)
|
566
|
-
# include Micro::Attributes.features(:strict_initialize)
|
567
|
-
include Micro::Attributes.to_initialize!
|
696
|
+
include Micro::Attributes.with(initialize: :strict)
|
568
697
|
|
569
698
|
attributes :id, :state
|
570
699
|
end
|
571
|
-
|
572
|
-
# The
|
573
|
-
|
700
|
+
#-----------------------------------------------------------------------#
|
701
|
+
# The strict initialize mode will require all the keys when initialize. #
|
702
|
+
#-----------------------------------------------------------------------#
|
574
703
|
|
575
704
|
Job.new({})
|
576
705
|
|
@@ -583,34 +712,33 @@ Job.new({})
|
|
583
712
|
|
584
713
|
job_null = Job.new(id: nil, state: nil)
|
585
714
|
|
586
|
-
|
587
|
-
|
715
|
+
job.id # nil
|
716
|
+
job.state # nil
|
588
717
|
|
589
718
|
job = Job.new(id: 1, state: 'sleeping')
|
590
719
|
|
591
|
-
|
592
|
-
|
720
|
+
job.id # 1
|
721
|
+
job.state # 'sleeping'
|
722
|
+
```
|
593
723
|
|
724
|
+
> **Note**: This extension works like the `initialize` extension. So, look at its section to understand all of the other features.
|
594
725
|
|
595
|
-
#
|
596
|
-
# This extension works like the `initialize` extension.
|
597
|
-
# So, look at its section to understand all the other features.
|
598
|
-
```
|
726
|
+
[⬆️ Back to Top](#table-of-contents-)
|
599
727
|
|
600
|
-
|
728
|
+
# Development
|
601
729
|
|
602
730
|
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.
|
603
731
|
|
604
732
|
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).
|
605
733
|
|
606
|
-
|
734
|
+
# Contributing
|
607
735
|
|
608
736
|
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.
|
609
737
|
|
610
|
-
|
738
|
+
# License
|
611
739
|
|
612
740
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
613
741
|
|
614
|
-
|
742
|
+
# Code of Conduct
|
615
743
|
|
616
|
-
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/
|
744
|
+
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).
|