u-attributes 1.2.0 → 2.2.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/.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).
|