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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97dfa238f3a4bbeea816dd6965e9261de6cff2af2c14a5934c9d2ffdee03b2a4
4
- data.tar.gz: a21fb9c6247d7d8b6969c0f60539dfe9e5bec6fb502c945dea64ec8534a86265
3
+ metadata.gz: 31b9f3d0195fc8b0f865279a79a54bd9beb8b0644d3f1b4371b6aa4163264024
4
+ data.tar.gz: '048e8c549d655980eee398e8ae445a7eb6900143f98c8e04449d5e2c82f0c9e6'
5
5
  SHA512:
6
- metadata.gz: a99ea97d70785cfa6f90a3a638ca4fe68bffc2ee05d4b3d9f5a3b9b31de2c3076607f1c64753c1c3850df6150d105985a5336b2d66e0560dab77ceea35a12b28
7
- data.tar.gz: daaee31a1f10ec4c74ffc59131196f040ce33eee787ea0381643737ade568615d1b52c78964a392f8e41a476de940d5b0a215102537514783273dd79da0329f1
6
+ metadata.gz: 3a776e02b0708d566156ce8adc7d0ad5813a7c752f8f19615eeea734b46ee34995d69a8cab263ed1e1ffbba64140eee4a630ff18fdcb6cf54cd5fb0a404d6fe8
7
+ data.tar.gz: 4e7869905c50b2f0ea288f404900c3c2b645ca3c8cad6404c2d8aa8913a0ad87874b43288c8f408e1a23252d9ab114fa8664a6a21914f71c2ffbf4ee04bb3d68
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
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
- ACTIVEMODEL_VERSION='5.2' bundle update
24
- ACTIVEMODEL_VERSION='5.2' bundle exec rake test
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
@@ -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
- - "./cc-test-reporter before-build"
23
+ - './cc-test-reporter before-build'
24
24
 
25
- script: "./.travis.sh"
25
+ script: './.travis.sh'
26
26
 
27
27
  after_success:
28
- - "./cc-test-reporter after-build -t simplecov"
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.rc1'
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
@@ -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 "Software"), to deal
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 "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
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
- [![Gem](https://img.shields.io/gem/v/u-attributes.svg?style=flat-square)](https://rubygems.org/gems/u-attributes)
2
- [![Build Status](https://travis-ci.com/serradura/u-attributes.svg?branch=master)](https://travis-ci.com/serradura/u-attributes)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/maintainability)](https://codeclimate.com/github/serradura/u-attributes/maintainability)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/test_coverage)](https://codeclimate.com/github/serradura/u-attributes/test_coverage)
5
-
6
- μ-attributes (Micro::Attributes)
7
- ================================
8
-
9
- This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
10
-
11
- ## Table of contents
12
- - [μ-attributes (Micro::Attributes)](#%ce%bc-attributes-microattributes)
13
- - [Table of contents](#table-of-contents)
14
- - [Required Ruby version](#required-ruby-version)
15
- - [Installation](#installation)
16
- - [Usage](#usage)
17
- - [How to require?](#how-to-require)
18
- - [How to define attributes?](#how-to-define-attributes)
19
- - [How to define multiple attributes?](#how-to-define-multiple-attributes)
20
- - [How to define attributes with a constructor to assign them?](#how-to-define-attributes-with-a-constructor-to-assign-them)
21
- - [How to inherit the attributes?](#how-to-inherit-the-attributes)
22
- - [How to query the attributes?](#how-to-query-the-attributes)
23
- - [Built-in extensions](#built-in-extensions)
24
- - [ActiveModel::Validations extension](#activemodelvalidations-extension)
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
- - [Strict initialize extension](#strict-initialize-extension)
28
- - [Development](#development)
29
- - [Contributing](#contributing)
30
- - [License](#license)
31
- - [Code of Conduct](#code-of-conduct)
60
+ - [Strict mode](#strict-mode)
61
+ - [Development](#development)
62
+ - [Contributing](#contributing)
63
+ - [License](#license)
64
+ - [Code of Conduct](#code-of-conduct)
32
65
 
33
- ## Required Ruby version
66
+ # Installation
34
67
 
35
- > \>= 2.2.0
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
- And then execute:
46
-
47
- $ bundle
74
+ # Compatibility
48
75
 
49
- Or install it yourself as:
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
- $ gem install u-attributes
81
+ > **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
52
82
 
53
- ## Usage
83
+ [⬆️ Back to Top](#table-of-contents-)
54
84
 
55
- ### How to require?
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
- # or
87
+ ## How to define attributes?
63
88
 
64
- require 'u-attributes'
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
- puts person.name # John Doe
86
- puts person.age # 21
105
+ person.age # 21
106
+ person.name # John Doe
87
107
 
88
- # By design, the attributes expose only reader methods (getters).
89
- # If you try to call a setter, you will see a NoMethodError.
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="John Doe", @age=21>)
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
- # This protected method is added to make easier the assignment in a constructor.
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
- puts person.name # John Doe
114
- puts person.age # 20
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
- puts person.attribute(:name) # John Doe
123
- puts person.attribute('age') # 20
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
- # If you pass a block, it will be executed only if the attribute is valid.
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 not exists.
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
- puts person.attribute!('foo') # NameError (undefined attribute `foo)
140
- person.attribute!('foo') { |value| puts value } # NameError (undefined attribute `foo)
141
- ```
223
+ ### `Micro::Attributes#attribute!`
142
224
 
143
- ### How to define multiple attributes?
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
- # Use .attributes with a list of attribute names.
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: 'John Doe' # Use a hash to define attributes with default values
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
- puts person.name # 'John Doe'
162
- puts person.age # 32
252
+ person.name # nil
253
+ person.age # 32
163
254
  ```
164
255
 
165
- ### How to define attributes with a constructor to assign them?
166
- A: Use `Micro::Attributes.to_initialize`
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.to_initialize
266
+ include Micro::Attributes.with(:initialize)
171
267
 
172
- attributes :age, name: 'John Doe'
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
- puts person.name # John Doe
178
- puts person.age # 18
274
+ person.age # 18
275
+ person.name # John Doe
276
+ ```
179
277
 
180
- ##############################################
181
- # Assigning new values to get a new instance #
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
- puts another_person.name # John Doe
191
- puts another_person.age # 21
192
- puts another_person.equal?(person) # false
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
- puts other_person.name # Serradura
203
- puts other_person.age # 32
204
- puts other_person.equal?(person) # false
297
+ other_person.age # 32
298
+ other_person.name # Serradura
299
+ other_person.equal?(person) # false
300
+ ```
205
301
 
206
- # If you pass a value different of a Hash, an ArgumentError will be raised.
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
- # Strict initializer #
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
- # Use .to_initialize! to forbids an instantiation without all keywords.
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.to_initialize!
346
+ include Micro::Attributes.with(initialize: :strict)
219
347
 
220
- attributes :age, name: 'John Doe'
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
- # The code above will raise:
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
- p person_without_age.name # "John Doe"
231
- p person_without_age.age # nil
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
- ### How to inherit the attributes?
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.to_initialize
374
+ include Micro::Attributes.with(:initialize)
242
375
 
243
- attributes :age, name: 'John Doe'
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
- puts instance.name # John Doe
253
- puts instance.respond_to?(:age) # true
254
- puts instance.respond_to?(:foo) # true
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
- # The methods above allow redefining the attributes default data
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
- p alfa_person.name # "Alfa"
269
- p alfa_person.age # nil
404
+ alfa_person.name # 'Alfa'
405
+ alfa_person.age # nil
270
406
 
271
407
  class SubSubclass < Subclass
272
- attributes! name: 'Beta', age: 0
408
+ attribute! :age, default: 0
409
+ attribute! :name, default: 'Beta'
273
410
  end
274
411
 
275
412
  beta_person = SubSubclass.new({})
276
413
 
277
- p beta_person.name # "Beta"
278
- p beta_person.age # 0
414
+ beta_person.name # 'Beta'
415
+ beta_person.age # 0
279
416
  ```
280
417
 
281
- ### How to query the attributes?
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
- attributes :age, name: 'John Doe'
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
- p Person.attributes # ["name", "age"]
438
+ Person.attributes # ['name', 'age']
299
439
 
300
440
  #---------------#
301
441
  # .attribute?() #
302
442
  #---------------#
303
443
 
304
- puts Person.attribute?(:name) # true
305
- puts Person.attribute?('name') # true
306
- puts Person.attribute?('foo') # false
307
- puts Person.attribute?(:foo) # false
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
- puts person.attribute?(:name) # true
318
- puts person.attribute?('name') # true
319
- puts person.attribute?('foo') # false
320
- puts person.attribute?(:foo) # false
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
- p person.attributes # {"age"=>20, "name"=>"John Doe"}
327
- p Person.new(name: 'John').attributes # {"age"=>nil, "name"=>"John"}
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
- p person.attributes(:age) # {age: 20}
337
- p person.attributes(:age, :name) # {age: 20, name: "John Doe"}
338
- p person.attributes('age', 'name') # {"age"=>20, "name"=>"John Doe"}
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
- ## Built-in extensions
487
+ [⬆️ Back to Top](#table-of-contents-)
342
488
 
343
- You can use the method `Micro::Attributes.features()` or `Micro::Attributes.with()` to combine and require only the features that better fit your needs.
489
+ # Built-in extensions
344
490
 
345
- But, if you desire...
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
- ```ruby
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
- attribute :id
358
- attribute :state, 'sleeping'
495
+ ## Picking specific features
359
496
 
360
- def initialize(options)
361
- self.attributes = options
362
- end
363
- end
497
+ ### `Micro::Attributes.with`
364
498
 
365
- #======================#
366
- # Loading all features #
367
- # --- #
368
- #======================#
499
+ ```ruby
500
+ Micro::Attributes.with(:initialize)
369
501
 
370
- class Job
371
- include Micro::Attributes.features
502
+ Micro::Attributes.with(initialize: :strict)
372
503
 
373
- attributes :id, state: 'sleeping'
374
- end
504
+ Micro::Attributes.with(:diff, :initialize)
375
505
 
376
- # Note:
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
- attributes :id, state: 'sleeping'
386
- end
510
+ Micro::Attributes.with(:activemodel_validations, :diff)
387
511
 
388
- # Note:
389
- # The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared.
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.to_initialize(diff: true, activemodel_validations: true)
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
- # Same of `include Micro::Attributes.with(:strict_initialize, :activemodel_validations)`
415
- end
525
+ Picking *except* one or more features
416
526
 
417
- #=====================================#
418
- # Loading except one or more features #
419
- # ----- #
420
- #=====================================#
527
+ ```ruby
528
+ Micro::Attributes.without(:diff) # will load :activemodel_validations and initialize: :strict
421
529
 
422
- class Job
423
- include Micro::Attributes.without(:diff)
530
+ Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations and :diff
531
+ ```
424
532
 
425
- attributes :id, state: 'sleeping'
426
- end
533
+ ## Picking all the features
427
534
 
428
- # Note:
429
- # The method `Micro::Attributes.without()` returns `Micro::Attributes` if all features extensions were used.
535
+ ```ruby
536
+ Micro::Attributes.with_all_features
430
537
  ```
431
538
 
432
- ### ActiveModel::Validations extension
539
+ [⬆️ Back to Top](#table-of-contents-)
433
540
 
434
- If your application uses ActiveModel as a dependency (like a regular Rails app). You will be enabled to use the `actimodel_validations` extension.
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
- # include Micro::Attributes.with(:initialize, :activemodel_validations)
439
- # include Micro::Attributes.features(:initialize, :activemodel_validations)
440
- include Micro::Attributes.to_initialize(activemodel_validations: true)
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
- p job.id # 1
451
- p job.state # "sleeping"
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
- # include Micro::Attributes.with(:initialize, :diff)
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
- attributes :id, state: 'sleeping'
596
+ attribute :id
597
+ attribute :state, default: 'sleeping'
467
598
  end
468
599
 
469
600
  job = Job.new(id: SecureRandom.uuid())
470
601
 
471
- p job.id # A random UUID generated from SecureRandom.uuid(). e.g: "e68bcc74-b91c-45c2-a904-12f1298cc60e"
472
- p job.state # "sleeping"
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
- p job_running.state # "running"
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
- p job_changes.present? # true
485
- p job_changes.blank? # false
486
- p job_changes.empty? # false
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
- p job_changes.changed? # true
622
+ job_changes.changed? # true
492
623
 
493
- p job_changes.changed?(:id) # false
624
+ job_changes.changed?(:id) # false
494
625
 
495
- p job_changes.changed?(:state) # true
496
- p job_changes.changed?(:state, from: 'sleeping', to: 'running') # true
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
- p job_changes.differences # {"state"=> {"from" => "sleeping", "to" => "running"}}
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. Adds methods to build new instances when some data was assigned.
640
+ 2. Add methods to build new instances when some data was assigned.
508
641
 
509
642
  ```ruby
510
643
  class Job
511
- # include Micro::Attributes.with(:initialize)
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
- p job.id # nil
522
- p job.state # nil
651
+ job.id # nil
652
+ job.state # nil
523
653
 
524
654
  job = Job.new(id: 1, state: 'sleeping')
525
655
 
526
- p job.id # 1
527
- p job.state # "sleeping"
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
- puts new_job.id # 1
540
- puts new_job.state # running
541
- puts new_job.equal?(job) # false
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
- puts other_job.id # 2
552
- puts other_job.state # killed
553
- puts other_job.equal?(job) # false
681
+ other_job.id # 2
682
+ other_job.state # killed
683
+ other_job.equal?(job) # false
554
684
  ```
555
685
 
556
- ### Strict initialize extension
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
- # include Micro::Attributes.with(:strict_initialize)
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 strict_initialize extension will require all the keys when initialize. #
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
- p job.id # nil
587
- p job.state # nil
715
+ job.id # nil
716
+ job.state # nil
588
717
 
589
718
  job = Job.new(id: 1, state: 'sleeping')
590
719
 
591
- p job.id # 1
592
- p job.state # "sleeping"
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
- # Note:
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
- ## Development
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
- ## Contributing
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
- ## License
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
- ## Code of Conduct
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/master/CODE_OF_CONDUCT.md).
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).