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