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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 854cdfcbae1f568640947a0bbfdf5fa26493a1e47ebf29ce91d7d287f6388050
4
- data.tar.gz: df775cef424add3e18672d03b6522d5e625b6e211b6891078b7bfb31e2ba996a
3
+ metadata.gz: 7939bac74159eb5cd1e4307a80805ee0712fe5233148f550bd40c3ed99773d5d
4
+ data.tar.gz: ac33edcb92e41acf77e823d61e22c2b2b6abe3b4c74036dfbf82d741ee208724
5
5
  SHA512:
6
- metadata.gz: 6541f9295ae0cc88fec1fcc05d8307feb05769de3aadfb597710517aa76e80dacaef7fd2d165491319ef2b0e701c20ebd025459befa2e5c92f07da43f5df6f3b
7
- data.tar.gz: 04e200e16d8c6258e819baa9b3f0ef00c86fc1715d6a866c6dc05fcefde54ceb7fd4746fa050e4e7b50ae7360dcd4639a2397ffe837593107368e19bfcef0dad
6
+ metadata.gz: '0796ae31ef53f91844d02950c00f52fe263794d13af076b5814292bd63e73ff36939f5141888f9c0db9f7c5ff7e3a739be11d1b04e89b8e76ee0eff21068ab05'
7
+ data.tar.gz: 16423f86a33677355c35c4aa5184d2356739a16d2506faf94cc45c50b033139c81ff11c261d9e7383228bd500d583caafd1596e89d411c2d21238c540dc63608
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,74 +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
- - [Installation](#installation)
15
- - [Usage](#usage)
16
- - [How to require?](#how-to-require)
17
- - [How to define attributes?](#how-to-define-attributes)
18
- - [How to define multiple attributes?](#how-to-define-multiple-attributes)
19
- - [How to define attributes with a constructor to assign them?](#how-to-define-attributes-with-a-constructor-to-assign-them)
20
- - [How to inherit the attributes?](#how-to-inherit-the-attributes)
21
- - [How to query the attributes?](#how-to-query-the-attributes)
22
- - [Built-in extensions](#built-in-extensions)
23
- - [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)
24
58
  - [Diff extension](#diff-extension)
25
59
  - [Initialize extension](#initialize-extension)
26
- - [Strict initialize extension](#strict-initialize-extension)
27
- - [Development](#development)
28
- - [Contributing](#contributing)
29
- - [License](#license)
30
- - [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)
31
65
 
32
- ## Installation
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
- And then execute:
74
+ # Compatibility
41
75
 
42
- $ bundle
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
- Or install it yourself as:
81
+ > **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
45
82
 
46
- $ gem install u-attributes
83
+ [⬆️ Back to Top](#table-of-contents-)
47
84
 
48
- ## Usage
85
+ # Usage
49
86
 
50
- ### How to require?
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
- require 'u-attributes'
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
- puts person.name # John Doe
81
- puts person.age # 21
105
+ person.age # 21
106
+ person.name # John Doe
82
107
 
83
- # By design, the attributes expose only reader methods (getters).
84
- # 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.
85
110
  #
86
111
  # person.name = 'Rodrigo'
87
- # 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
+ ```
88
114
 
89
- #------------------#
90
- # self.attributes= #
91
- #------------------#
115
+ [⬆️ Back to Top](#table-of-contents-)
92
116
 
93
- # This protected method is added to make easier the assignment in a constructor.
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
- puts person.name # John Doe
109
- puts person.age # 20
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
- puts person.attribute(:name) # John Doe
118
- puts person.attribute('age') # 20
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
- # 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'
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 not exists.
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
- puts person.attribute!('foo') # NameError (undefined attribute `foo)
135
- person.attribute!('foo') { |value| puts value } # NameError (undefined attribute `foo)
136
- ```
223
+ ### `Micro::Attributes#attribute!`
137
224
 
138
- ### How to define multiple attributes?
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
- # Use .attributes with a list of attribute names.
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: 'John Doe' # Use a hash to define attributes with default values
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
- puts person.name # 'John Doe'
157
- puts person.age # 32
252
+ person.name # nil
253
+ person.age # 32
158
254
  ```
159
255
 
160
- ### How to define attributes with a constructor to assign them?
161
- 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.
162
263
 
163
264
  ```ruby
164
265
  class Person
165
- include Micro::Attributes.to_initialize
266
+ include Micro::Attributes.with(:initialize)
166
267
 
167
- attributes :age, name: 'John Doe'
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
- puts person.name # John Doe
173
- puts person.age # 18
274
+ person.age # 18
275
+ person.name # John Doe
276
+ ```
174
277
 
175
- ##############################################
176
- # Assigning new values to get a new instance #
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
- puts another_person.name # John Doe
186
- puts another_person.age # 21
187
- puts another_person.equal?(person) # false
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
- puts other_person.name # Serradura
198
- puts other_person.age # 32
199
- puts other_person.equal?(person) # false
297
+ other_person.age # 32
298
+ other_person.name # Serradura
299
+ other_person.equal?(person) # false
300
+ ```
200
301
 
201
- # If you pass a value different of a Hash, an ArgumentError will be raised.
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
- # Strict initializer #
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
- # Use .to_initialize! to forbids an instantiation without all keywords.
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.to_initialize!
347
+ include Micro::Attributes.with(initialize: :strict)
214
348
 
215
- attributes :age, name: 'John Doe'
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
- # The code above will raise:
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
- p person_without_age.name # "John Doe"
226
- p person_without_age.age # nil
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
- ### How to inherit the attributes?
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.to_initialize
375
+ include Micro::Attributes.with(:initialize)
237
376
 
238
- attributes :age, name: 'John Doe'
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
- puts instance.name # John Doe
248
- puts instance.respond_to?(:age) # true
249
- puts instance.respond_to?(:foo) # true
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
- # The methods above allow redefining the attributes default data
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
- p alfa_person.name # "Alfa"
264
- p alfa_person.age # nil
405
+ alfa_person.name # 'Alfa'
406
+ alfa_person.age # nil
265
407
 
266
408
  class SubSubclass < Subclass
267
- attributes! name: 'Beta', age: 0
409
+ attribute! :age, default: 0
410
+ attribute! :name, default: 'Beta'
268
411
  end
269
412
 
270
413
  beta_person = SubSubclass.new({})
271
414
 
272
- p beta_person.name # "Beta"
273
- p beta_person.age # 0
415
+ beta_person.name # 'Beta'
416
+ beta_person.age # 0
274
417
  ```
275
418
 
276
- ### How to query the attributes?
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
- attributes :age, name: 'John Doe'
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
- p Person.attributes # ["name", "age"]
439
+ Person.attributes # ['name', 'age']
294
440
 
295
441
  #---------------#
296
442
  # .attribute?() #
297
443
  #---------------#
298
444
 
299
- puts Person.attribute?(:name) # true
300
- puts Person.attribute?('name') # true
301
- puts Person.attribute?('foo') # false
302
- puts Person.attribute?(:foo) # false
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
- puts person.attribute?(:name) # true
313
- puts person.attribute?('name') # true
314
- puts person.attribute?('foo') # false
315
- puts person.attribute?(:foo) # false
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
- p person.attributes # {"age"=>20, "name"=>"John Doe"}
322
- p Person.new(name: 'John').attributes # {"age"=>nil, "name"=>"John"}
323
- ```
473
+ person.attributes # {'age'=>20, 'name'=>'John Doe'}
474
+ Person.new(name: 'John').attributes # {'age'=>nil, 'name'=>'John'}
324
475
 
325
- ## Built-in extensions
476
+ #---------------------#
477
+ # #attributes(*names) #
478
+ #---------------------#
326
479
 
327
- You can use the method `Micro::Attributes.features()` or `Micro::Attributes.with()` to combine and require only the features that better fit your needs.
480
+ # Slices the attributes to include only the given keys.
481
+ # Returns a hash containing the given keys (in their types).
328
482
 
329
- But, if you desire...
330
- 1. only one feature, use the `Micro::Attributes.feature()` method.
331
- 2. except one or more features, use the `Micro::Attributes.without()` method.
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
- ```ruby
334
- #===========================#
335
- # Loading specific features #
336
- #===========================#
488
+ [⬆️ Back to Top](#table-of-contents-)
337
489
 
338
- class Job
339
- include Micro::Attributes.feature(:diff)
490
+ # Built-in extensions
340
491
 
341
- attribute :id
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
- def initialize(options)
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
- class Job
355
- include Micro::Attributes.features
498
+ ### `Micro::Attributes.with`
356
499
 
357
- attributes :id, state: 'sleeping'
358
- end
500
+ ```ruby
501
+ Micro::Attributes.with(:initialize)
359
502
 
360
- # Note:
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
- attributes :id, state: 'sleeping'
370
- end
507
+ Micro::Attributes.with(:diff, initialize: :strict)
371
508
 
372
- # Note:
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
- # Via Micro::Attributes.to_initialize() #
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
- # Same of `include Micro::Attributes.with(:initialize, :diff, :activemodel_validations)`
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.to_initialize!(diff: false, activemodel_validations: true)
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
- class Job
407
- include Micro::Attributes.without(:diff)
526
+ Picking *except* one or more features
408
527
 
409
- attributes :id, state: 'sleeping'
410
- end
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
- # Note:
413
- # The method `Micro::Attributes.without()` returns `Micro::Attributes` if all features extensions were used.
534
+ ## Picking all the features
535
+
536
+ ```ruby
537
+ Micro::Attributes.with_all_features
414
538
  ```
415
539
 
416
- ### ActiveModel::Validations extension
540
+ [⬆️ Back to Top](#table-of-contents-)
417
541
 
418
- If your application uses ActiveModel as a dependency (like a regular Rails app). You will be enabled to use the `actimodel_validations` extension.
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
- # include Micro::Attributes.with(:initialize, :activemodel_validations)
423
- # include Micro::Attributes.features(:initialize, :activemodel_validations)
424
- include Micro::Attributes.to_initialize(activemodel_validations: true)
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
- p job.id # 1
435
- p job.state # "sleeping"
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
- # include Micro::Attributes.with(:initialize, :diff)
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
- attributes :id, state: 'sleeping'
597
+ attribute :id
598
+ attribute :state, default: 'sleeping'
451
599
  end
452
600
 
453
601
  job = Job.new(id: SecureRandom.uuid())
454
602
 
455
- p job.id # A random UUID generated from SecureRandom.uuid(). e.g: "e68bcc74-b91c-45c2-a904-12f1298cc60e"
456
- p job.state # "sleeping"
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
- p job_running.state # "running"
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
- p job_changes.present? # true
469
- p job_changes.blank? # false
470
- p job_changes.empty? # false
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
- p job_changes.changed? # true
623
+ job_changes.changed? # true
476
624
 
477
- p job_changes.changed?(:id) # false
625
+ job_changes.changed?(:id) # false
478
626
 
479
- p job_changes.changed?(:state) # true
480
- p job_changes.changed?(:state, from: 'sleeping', to: 'running') # true
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
- p job_changes.differences # {"state"=> {"from" => "sleeping", "to" => "running"}}
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. Adds methods to build new instances when some data was assigned.
641
+ 2. Add methods to build new instances when some data was assigned.
492
642
 
493
643
  ```ruby
494
644
  class Job
495
- # include Micro::Attributes.with(:initialize)
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
- p job.id # nil
506
- p job.state # nil
652
+ job.id # nil
653
+ job.state # nil
507
654
 
508
655
  job = Job.new(id: 1, state: 'sleeping')
509
656
 
510
- p job.id # 1
511
- p job.state # "sleeping"
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
- puts new_job.id # 1
524
- puts new_job.state # running
525
- puts new_job.equal?(job) # false
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
- puts other_job.id # 2
536
- puts other_job.state # killed
537
- puts other_job.equal?(job) # false
682
+ other_job.id # 2
683
+ other_job.state # killed
684
+ other_job.equal?(job) # false
538
685
  ```
539
686
 
540
- ### Strict initialize extension
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
- # include Micro::Attributes.with(:strict_initialize)
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 strict_initialize extension will require all the keys when initialize. #
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
- p job.id # nil
571
- p job.state # nil
716
+ job.id # nil
717
+ job.state # nil
572
718
 
573
719
  job = Job.new(id: 1, state: 'sleeping')
574
720
 
575
- p job.id # 1
576
- p job.state # "sleeping"
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
- # Note:
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
- ## Development
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
- ## Contributing
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
- ## License
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
- ## Code of Conduct
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/master/CODE_OF_CONDUCT.md).
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).