u-attributes 2.0.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc53b00949ac8d0d8d8572443936ba537a637569be77e0b105a0b98f2d1ae9d0
4
- data.tar.gz: 94f42f6da3c2cba68f6565e1bb413b5692bd700baa077555cd421e4aeb82e2ff
3
+ metadata.gz: 62c6ad9683f0824be6a32648028f25a97ba88a10c7fbdd0b9a2ccfbfe275961b
4
+ data.tar.gz: d110a1b4220af76a7f14753cb1ca5067428f06db026466e095d54a22ee46f86f
5
5
  SHA512:
6
- metadata.gz: e5617a5fd34458de945dae2874bed24013e682871f19b70063e734156f2528e8b249e615632f90c978c203abf876560de9717940f9b81e48271f84a956456199
7
- data.tar.gz: 50b19483c9e8f88169101306be67a34e4e8c6a26358b187fd119b41ca26e83d461619d6a149bfc898ab7b1208c6af6791b58acf039a3d92d0e8f912e007232b9
6
+ metadata.gz: 24703dcd81aa785fe00b77a741434ad84600243f4bb132d2bbbce2b5a310d4f2b931c2b6ea41e311a2bcf07876296bd03d8e6c39a92905bc9a4f745fdb30a094
7
+ data.tar.gz: 8172811ddaf44a3099e66f63cea5aed99d5c1a63a77944dc1e98c16fa0378603498fbdfe0c77f7220244c8d7b977801a75bd01facc875fb05f0bd1aed0171cc5
@@ -1,7 +1,5 @@
1
1
  language: ruby
2
2
 
3
- sudo: false
4
-
5
3
  rvm:
6
4
  - 2.2.2
7
5
  - 2.3.0
@@ -9,6 +7,7 @@ rvm:
9
7
  - 2.5.0
10
8
  - 2.6.0
11
9
  - 2.7.0
10
+ - truffleruby-head
12
11
 
13
12
  cache: bundler
14
13
 
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
@@ -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/README.md CHANGED
@@ -1,21 +1,41 @@
1
- ![Ruby](https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066)
2
- [![Gem](https://img.shields.io/gem/v/u-attributes.svg?style=flat-square)](https://rubygems.org/gems/u-attributes)
3
- [![Build Status](https://travis-ci.com/serradura/u-attributes.svg?branch=main)](https://travis-ci.com/serradura/u-attributes)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/maintainability)](https://codeclimate.com/github/serradura/u-attributes/maintainability)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/b562e6b877a9edf4dbf6/test_coverage)](https://codeclimate.com/github/serradura/u-attributes/test_coverage)
1
+ <p align="center">
2
+ <img src="./assets/u-attributes_logo_v1.png" alt='Create "immutable" objects. No setters, just getters!'>
6
3
 
7
- μ-attributes (Micro::Attributes) <!-- omit in toc -->
8
- ================================
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>
9
27
 
10
28
  This gem allows you to define "immutable" objects, and your objects will have only getters and no setters.
11
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.
12
30
 
13
- ## Table of contents <!-- omit in toc -->
31
+ # Table of contents <!-- omit in toc -->
14
32
  - [Installation](#installation)
15
33
  - [Compatibility](#compatibility)
16
34
  - [Usage](#usage)
17
35
  - [How to define attributes?](#how-to-define-attributes)
18
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)
19
39
  - [`Micro::Attributes#attribute`](#microattributesattribute)
20
40
  - [`Micro::Attributes#attribute!`](#microattributesattribute-1)
21
41
  - [How to define multiple attributes?](#how-to-define-multiple-attributes)
@@ -27,6 +47,15 @@ So, if you change [[1](#with_attribute)] [[2](#with_attributes)] some object att
27
47
  - [Is it possible to inherit the attributes?](#is-it-possible-to-inherit-the-attributes)
28
48
  - [`.attribute!()`](#attribute)
29
49
  - [How to query the attributes?](#how-to-query-the-attributes)
50
+ - [`.attributes`](#attributes)
51
+ - [`.attribute?()`](#attribute-1)
52
+ - [`#attribute?()`](#attribute-2)
53
+ - [`#attributes()`](#attributes-1)
54
+ - [`#attributes(keys_as:)`](#attributeskeys_as)
55
+ - [`#attributes(*names)`](#attributesnames)
56
+ - [`#attributes([names])`](#attributesnames-1)
57
+ - [`#attributes(with:, without:)`](#attributeswith-without)
58
+ - [`#defined_attributes`](#defined_attributes)
30
59
  - [Built-in extensions](#built-in-extensions)
31
60
  - [Picking specific features](#picking-specific-features)
32
61
  - [`Micro::Attributes.with`](#microattributeswith)
@@ -38,6 +67,7 @@ So, if you change [[1](#with_attribute)] [[2](#with_attributes)] some object att
38
67
  - [Diff extension](#diff-extension)
39
68
  - [Initialize extension](#initialize-extension)
40
69
  - [Strict mode](#strict-mode)
70
+ - [Keys as symbol extension](#keys-as-symbol-extension)
41
71
  - [Development](#development)
42
72
  - [Contributing](#contributing)
43
73
  - [License](#license)
@@ -55,7 +85,7 @@ gem 'u-attributes'
55
85
 
56
86
  | u-attributes | branch | ruby | activemodel |
57
87
  | -------------- | ------- | -------- | ------------- |
58
- | 2.0.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
88
+ | 2.4.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
59
89
  | 1.2.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
60
90
 
61
91
  > **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
@@ -66,9 +96,9 @@ gem 'u-attributes'
66
96
 
67
97
  ## How to define attributes?
68
98
 
69
- ```ruby
70
- # By default you must to define the class constructor.
99
+ By default, you must define the class constructor.
71
100
 
101
+ ```ruby
72
102
  class Person
73
103
  include Micro::Attributes
74
104
 
@@ -116,6 +146,66 @@ person.age # 20
116
146
  person.name # John Doe
117
147
  ```
118
148
 
149
+ #### How to extract attributes from an object or hash?
150
+
151
+ You can extract attributes using the `extract_attributes_from` method, it will try to fetch attributes from the
152
+ object using either the `object[attribute_key]` accessor or the reader method `object.attribute_key`.
153
+
154
+ ```ruby
155
+ class Person
156
+ include Micro::Attributes
157
+
158
+ attribute :age
159
+ attribute :name, default: 'John Doe'
160
+
161
+ def initialize(user:)
162
+ self.attributes = extract_attributes_from(user)
163
+ end
164
+ end
165
+
166
+ # extracting from an object
167
+
168
+ class User
169
+ attr_accessor :age, :name
170
+ end
171
+
172
+ user = User.new
173
+ user.age = 20
174
+
175
+ person = Person.new(user: user)
176
+
177
+ person.age # 20
178
+ person.name # John Doe
179
+
180
+ # extracting from a hash
181
+
182
+ another_person = Person.new(user: { age: 55, name: 'Julia Not Roberts' })
183
+
184
+ another_person.age # 55
185
+ another_person.name # Julia Not Roberts
186
+ ```
187
+
188
+ #### Is it possible to define an attribute as required?
189
+
190
+ You only need to use the `required: true` option.
191
+
192
+ 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).
193
+
194
+ ```ruby
195
+ class Person
196
+ include Micro::Attributes
197
+
198
+ attribute :age
199
+ attribute :name, required: true
200
+
201
+ def initialize(attributes)
202
+ self.attributes = attributes
203
+ end
204
+ end
205
+
206
+ Person.new(age: 32) # ArgumentError (missing keyword: :name)
207
+ ```
208
+
119
209
  [⬆️ Back to Top](#table-of-contents-)
120
210
 
121
211
  ### `Micro::Attributes#attribute`
@@ -185,7 +275,7 @@ Use `Micro::Attributes.with(:initialize)` to define a constructor to assign the
185
275
  class Person
186
276
  include Micro::Attributes.with(:initialize)
187
277
 
188
- attribute :age
278
+ attribute :age, required: true
189
279
  attribute :name, default: 'John Doe'
190
280
  end
191
281
 
@@ -240,10 +330,9 @@ class Person
240
330
  end
241
331
  ```
242
332
 
243
- There are 3 different strategies to define default values.
333
+ There are two different strategies to define default values.
244
334
  1. Pass a regular object, like in the previous example.
245
335
  2. Pass a `proc`/`lambda`, and if it has an argument you will receive the attribute value to do something before assign it.
246
- 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`).
247
336
 
248
337
  ```ruby
249
338
  class Person
@@ -258,7 +347,9 @@ end
258
347
 
259
348
  ## The strict initializer
260
349
 
261
- Use `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords. e.g.
350
+ Use `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords.
351
+
352
+ 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).
262
353
 
263
354
  ```ruby
264
355
  class StrictPerson
@@ -338,63 +429,155 @@ beta_person.age # 0
338
429
 
339
430
  ## How to query the attributes?
340
431
 
432
+ All of the methods that will be explained can be used with any of the built-in extensions.
433
+
434
+ **PS:** We will use the class below for all of the next examples.
435
+
341
436
  ```ruby
342
437
  class Person
343
438
  include Micro::Attributes
344
439
 
345
440
  attribute :age
346
- attribute :name, default: 'John Doe'
441
+ attribute :first_name, default: 'John'
442
+ attribute :last_name, default: 'Doe'
347
443
 
348
444
  def initialize(options)
349
445
  self.attributes = options
350
446
  end
447
+
448
+ def name
449
+ "#{first_name} #{last_name}"
450
+ end
351
451
  end
452
+ ```
453
+
454
+ ### `.attributes`
455
+
456
+ Listing all the class attributes.
352
457
 
353
- #---------------#
354
- # .attributes() #
355
- #---------------#
458
+ ```ruby
459
+ Person.attributes # ["age", "first_name", "last_name"]
460
+ ```
356
461
 
357
- Person.attributes # ['name', 'age']
462
+ ### `.attribute?()`
358
463
 
359
- #---------------#
360
- # .attribute?() #
361
- #---------------#
464
+ Checking the existence of some attribute.
465
+
466
+ ```ruby
467
+ Person.attribute?(:first_name) # true
468
+ Person.attribute?('first_name') # true
362
469
 
363
- Person.attribute?(:name) # true
364
- Person.attribute?('name') # true
365
470
  Person.attribute?('foo') # false
366
471
  Person.attribute?(:foo) # false
472
+ ```
367
473
 
368
- # ---
474
+ ### `#attribute?()`
369
475
 
370
- person = Person.new(age: 20)
476
+ Checking the existence of some attribute in an instance.
371
477
 
372
- #---------------#
373
- # #attribute?() #
374
- #---------------#
478
+ ```ruby
479
+ person = Person.new(age: 20)
375
480
 
376
481
  person.attribute?(:name) # true
377
482
  person.attribute?('name') # true
483
+
378
484
  person.attribute?('foo') # false
379
485
  person.attribute?(:foo) # false
486
+ ```
380
487
 
381
- #---------------#
382
- # #attributes() #
383
- #---------------#
488
+ ### `#attributes()`
384
489
 
385
- person.attributes # {'age'=>20, 'name'=>'John Doe'}
386
- Person.new(name: 'John').attributes # {'age'=>nil, 'name'=>'John'}
490
+ Fetching all the attributes with their values.
491
+
492
+ ```ruby
493
+ person1 = Person.new(age: 20)
494
+ person1.attributes # {"age"=>20, "first_name"=>"John", "last_name"=>"Doe"}
495
+
496
+ person2 = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
497
+ person2.attributes # {"age"=>nil, "first_name"=>"Rodrigo", "last_name"=>"Rodrigues"}
498
+ ```
387
499
 
388
- #---------------------#
389
- # #attributes(*names) #
390
- #---------------------#
500
+ #### `#attributes(keys_as:)`
391
501
 
392
- # Slices the attributes to include only the given keys.
393
- # Returns a hash containing the given keys (in their types).
502
+ Use the `keys_as:` option with `Symbol`/`:symbol` or `String`/`:string` to transform the attributes hash keys.
394
503
 
395
- person.attributes(:age) # {age: 20}
396
- person.attributes(:age, :name) # {age: 20, name: 'John Doe'}
397
- person.attributes('age', 'name') # {'age'=>20, 'name'=>'John Doe'}
504
+ ```ruby
505
+ person1 = Person.new(age: 20)
506
+ person2 = Person.new(first_name: 'Rodrigo', last_name: 'Rodrigues')
507
+
508
+ person1.attributes(keys_as: Symbol) # {:age=>20, :first_name=>"John", :last_name=>"Doe"}
509
+ person2.attributes(keys_as: String) # {"age"=>nil, "first_name"=>"Rodrigo", "last_name"=>"Rodrigues"}
510
+
511
+ person1.attributes(keys_as: :symbol) # {:age=>20, :first_name=>"John", :last_name=>"Doe"}
512
+ person2.attributes(keys_as: :string) # {"age"=>nil, "first_name"=>"Rodrigo", "last_name"=>"Rodrigues"}
513
+ ```
514
+
515
+ #### `#attributes(*names)`
516
+
517
+ Slices the attributes to include only the given keys (in their types).
518
+
519
+ ```ruby
520
+ person = Person.new(age: 20)
521
+
522
+ person.attributes(:age) # {:age => 20}
523
+ person.attributes(:age, :first_name) # {:age => 20, :first_name => "John"}
524
+ person.attributes('age', 'last_name') # {"age" => 20, "last_name" => "Doe"}
525
+
526
+ person.attributes(:age, 'last_name') # {:age => 20, "last_name" => "Doe"}
527
+
528
+ # You could also use the keys_as: option to ensure the same type for all of the hash keys.
529
+
530
+ person.attributes(:age, 'last_name', keys_as: Symbol) # {:age=>20, :last_name=>"Doe"}
531
+ ```
532
+
533
+ #### `#attributes([names])`
534
+
535
+ As the previous example, this methods accepts a list of keys to slice the attributes.
536
+
537
+ ```ruby
538
+ person = Person.new(age: 20)
539
+
540
+ person.attributes([:age]) # {:age => 20}
541
+ person.attributes([:age, :first_name]) # {:age => 20, :first_name => "John"}
542
+ person.attributes(['age', 'last_name']) # {"age" => 20, "last_name" => "Doe"}
543
+
544
+ person.attributes([:age, 'last_name']) # {:age => 20, "last_name" => "Doe"}
545
+
546
+ # You could also use the keys_as: option to ensure the same type for all of the hash keys.
547
+
548
+ person.attributes([:age, 'last_name'], keys_as: Symbol) # {:age=>20, :last_name=>"Doe"}
549
+ ```
550
+
551
+ #### `#attributes(with:, without:)`
552
+
553
+ Use the `with:` option to include any method value of the instance inside of the hash, and,
554
+ you can use the `without:` option to exclude one or more attribute keys from the final hash.
555
+
556
+ ```ruby
557
+ person = Person.new(age: 20)
558
+
559
+ person.attributes(without: :age) # {"first_name"=>"John", "last_name"=>"Doe"}
560
+ person.attributes(without: [:age, :last_name]) # {"first_name"=>"John"}
561
+
562
+ person.attributes(with: [:name], without: [:first_name, :last_name]) # {"age"=>20, "name"=>"John Doe"}
563
+
564
+ # To achieves the same output of the previous example, use the attribute names to slice only them.
565
+
566
+ person.attributes(:age, with: [:name]) # {:age=>20, "name"=>"John Doe"}
567
+
568
+ # You could also use the keys_as: option to ensure the same type for all of the hash keys.
569
+
570
+ person.attributes(:age, with: [:name], keys_as: Symbol) # {:age=>20, :name=>"John Doe"}
571
+ ```
572
+
573
+ ### `#defined_attributes`
574
+
575
+ Listing all the available attributes.
576
+
577
+ ```ruby
578
+ person = Person.new(age: 20)
579
+
580
+ person.defined_attributes # ["age", "first_name", "last_name"]
398
581
  ```
399
582
 
400
583
  [⬆️ Back to Top](#table-of-contents-)
@@ -412,24 +595,30 @@ But, if you desire except one or more features, use the `Micro::Attributes.witho
412
595
  ```ruby
413
596
  Micro::Attributes.with(:initialize)
414
597
 
415
- Micro::Attributes.with(initialize: :strict)
598
+ Micro::Attributes.with(:initialize, :keys_as_symbol)
599
+
600
+ Micro::Attributes.with(:keys_as_symbol, initialize: :strict)
416
601
 
417
602
  Micro::Attributes.with(:diff, :initialize)
418
603
 
419
604
  Micro::Attributes.with(:diff, initialize: :strict)
420
605
 
606
+ Micro::Attributes.with(:diff, :keys_as_symbol, initialize: :strict)
607
+
421
608
  Micro::Attributes.with(:activemodel_validations)
422
609
 
423
610
  Micro::Attributes.with(:activemodel_validations, :diff)
424
611
 
425
612
  Micro::Attributes.with(:activemodel_validations, :diff, initialize: :strict)
613
+
614
+ Micro::Attributes.with(:activemodel_validations, :diff, :keys_as_symbol, initialize: :strict)
426
615
  ```
427
616
 
428
617
  The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared.
429
618
 
430
619
  ```ruby
431
620
  class Job
432
- include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :activemodel_validations, :diff, :initialize)
621
+ include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :activemodel_validations, :diff, :initialize, :keys_as_symbol)
433
622
  end
434
623
  ```
435
624
 
@@ -438,15 +627,19 @@ end
438
627
  Picking *except* one or more features
439
628
 
440
629
  ```ruby
441
- Micro::Attributes.without(:diff) # will load :activemodel_validations and initialize: :strict
630
+ Micro::Attributes.without(:diff) # will load :activemodel_validations, :keys_as_symbol and initialize: :strict
442
631
 
443
- Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations and :diff
632
+ Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations, :diff and :keys_as_symbol
444
633
  ```
445
634
 
446
635
  ## Picking all the features
447
636
 
448
637
  ```ruby
449
638
  Micro::Attributes.with_all_features
639
+
640
+ # This method returns the same of:
641
+
642
+ Micro::Attributes.with(:activemodel_validations, :diff, :keys_as_symbol, initialize: :strict)
450
643
  ```
451
644
 
452
645
  [⬆️ Back to Top](#table-of-contents-)
@@ -638,6 +831,40 @@ job.state # 'sleeping'
638
831
 
639
832
  [⬆️ Back to Top](#table-of-contents-)
640
833
 
834
+ ### Keys as symbol extension
835
+
836
+ Disables the indifferent access requiring the declaration/usage of the attributes as symbols.
837
+
838
+ The advantage of this extension over the default behavior is because it avoids an unnecessary allocation in memory of strings. All the keys are transformed into strings in the indifferent access mode, but, with this extension, this typecasting will be avoided. So, it has a better performance and reduces the usage of memory/Garbage collector, but gives for you the responsibility to always use symbols to set/access the attributes.
839
+
840
+ ```ruby
841
+ class Job
842
+ include Micro::Attributes.with(:initialize, :keys_as_symbol)
843
+
844
+ attribute :id
845
+ attribute :state, default: 'sleeping'
846
+ end
847
+
848
+ job = Job.new(id: 1)
849
+
850
+ job.attributes # {:id => 1, :state => "sleeping"}
851
+
852
+ job.attribute?(:id) # true
853
+ job.attribute?('id') # false
854
+
855
+ job.attribute(:id) # 1
856
+ job.attribute('id') # nil
857
+
858
+ job.attribute!(:id) # 1
859
+ job.attribute!('id') # NameError (undefined attribute `id)
860
+ ```
861
+
862
+ As you could see in the previous example only symbols will work to do something with the attributes.
863
+
864
+ This extension also changes the `diff extension` making everything (arguments, outputs) working only with symbols.
865
+
866
+ [⬆️ Back to Top](#table-of-contents-)
867
+
641
868
  # Development
642
869
 
643
870
  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.