u-attributes 2.1.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/README.md +180 -41
- data/lib/micro/attributes.rb +43 -23
- data/lib/micro/attributes/diff.rb +26 -14
- data/lib/micro/attributes/features.rb +128 -75
- data/lib/micro/attributes/features/accept.rb +132 -0
- data/lib/micro/attributes/features/accept/strict.rb +26 -0
- data/lib/micro/attributes/features/activemodel_validations.rb +54 -10
- data/lib/micro/attributes/features/initialize.rb +3 -8
- data/lib/micro/attributes/features/keys_as_symbol.rb +31 -0
- data/lib/micro/attributes/macros.rb +80 -13
- data/lib/micro/attributes/utils.rb +47 -14
- data/lib/micro/attributes/version.rb +1 -1
- data/u-attributes.gemspec +1 -1
- metadata +5 -4
- data/assets/u-attributes_logo_v1.png +0 -0
- data/lib/micro/attributes/with.rb +0 -100
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01dbce30afd7d116e15ea5985e5012d86f2bd110b017394151086df322e9b7ed
|
4
|
+
data.tar.gz: 4004e27e835cc216807b2fb2781a15911c16f2ba20e509b7c36188a8b6021f95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca177ca900f63a07901366612f5ef545512db4e8f9b48bea6482403af6346324784910fa9dca963e681449a1d3b583b173317890b76e8a2e03fd46b58af32006
|
7
|
+
data.tar.gz: 44372485364d637906d1dad476fc58a98cf228c33e252012af320e7e1da36b82db3943eb635ed34dff211908088f6fc8fe3c2de8a1b9a418f62f8e8b3e1596ed
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -47,6 +47,15 @@ So, if you change [[1](#with_attribute)] [[2](#with_attributes)] some object att
|
|
47
47
|
- [Is it possible to inherit the attributes?](#is-it-possible-to-inherit-the-attributes)
|
48
48
|
- [`.attribute!()`](#attribute)
|
49
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)
|
50
59
|
- [Built-in extensions](#built-in-extensions)
|
51
60
|
- [Picking specific features](#picking-specific-features)
|
52
61
|
- [`Micro::Attributes.with`](#microattributeswith)
|
@@ -58,6 +67,7 @@ So, if you change [[1](#with_attribute)] [[2](#with_attributes)] some object att
|
|
58
67
|
- [Diff extension](#diff-extension)
|
59
68
|
- [Initialize extension](#initialize-extension)
|
60
69
|
- [Strict mode](#strict-mode)
|
70
|
+
- [Keys as symbol extension](#keys-as-symbol-extension)
|
61
71
|
- [Development](#development)
|
62
72
|
- [Contributing](#contributing)
|
63
73
|
- [License](#license)
|
@@ -75,7 +85,7 @@ gem 'u-attributes'
|
|
75
85
|
|
76
86
|
| u-attributes | branch | ruby | activemodel |
|
77
87
|
| -------------- | ------- | -------- | ------------- |
|
78
|
-
| 2.
|
88
|
+
| 2.5.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
79
89
|
| 1.2.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
80
90
|
|
81
91
|
> **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
|
@@ -320,10 +330,9 @@ class Person
|
|
320
330
|
end
|
321
331
|
```
|
322
332
|
|
323
|
-
There are
|
333
|
+
There are two different strategies to define default values.
|
324
334
|
1. Pass a regular object, like in the previous example.
|
325
335
|
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
336
|
|
328
337
|
```ruby
|
329
338
|
class Person
|
@@ -420,69 +429,155 @@ beta_person.age # 0
|
|
420
429
|
|
421
430
|
## How to query the attributes?
|
422
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
|
+
|
423
436
|
```ruby
|
424
437
|
class Person
|
425
438
|
include Micro::Attributes
|
426
439
|
|
427
440
|
attribute :age
|
428
|
-
attribute :
|
441
|
+
attribute :first_name, default: 'John'
|
442
|
+
attribute :last_name, default: 'Doe'
|
429
443
|
|
430
444
|
def initialize(options)
|
431
445
|
self.attributes = options
|
432
446
|
end
|
447
|
+
|
448
|
+
def name
|
449
|
+
"#{first_name} #{last_name}"
|
450
|
+
end
|
433
451
|
end
|
452
|
+
```
|
434
453
|
|
435
|
-
|
436
|
-
# .attributes() #
|
437
|
-
#---------------#
|
454
|
+
### `.attributes`
|
438
455
|
|
439
|
-
|
456
|
+
Listing all the class attributes.
|
440
457
|
|
441
|
-
|
442
|
-
#
|
443
|
-
|
458
|
+
```ruby
|
459
|
+
Person.attributes # ["age", "first_name", "last_name"]
|
460
|
+
```
|
444
461
|
|
445
|
-
|
446
|
-
Person.attribute?('name') # true
|
447
|
-
Person.attribute?('foo') # false
|
448
|
-
Person.attribute?(:foo) # false
|
462
|
+
### `.attribute?()`
|
449
463
|
|
450
|
-
|
464
|
+
Checking the existence of some attribute.
|
451
465
|
|
452
|
-
|
466
|
+
```ruby
|
467
|
+
Person.attribute?(:first_name) # true
|
468
|
+
Person.attribute?('first_name') # true
|
453
469
|
|
454
|
-
|
455
|
-
#
|
456
|
-
|
470
|
+
Person.attribute?('foo') # false
|
471
|
+
Person.attribute?(:foo) # false
|
472
|
+
```
|
457
473
|
|
458
|
-
|
474
|
+
### `#attribute?()`
|
459
475
|
|
460
|
-
|
461
|
-
|
462
|
-
|
476
|
+
Checking the existence of some attribute in an instance.
|
477
|
+
|
478
|
+
```ruby
|
479
|
+
person = Person.new(age: 20)
|
463
480
|
|
464
481
|
person.attribute?(:name) # true
|
465
482
|
person.attribute?('name') # true
|
483
|
+
|
466
484
|
person.attribute?('foo') # false
|
467
485
|
person.attribute?(:foo) # false
|
486
|
+
```
|
487
|
+
|
488
|
+
### `#attributes()`
|
489
|
+
|
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
|
+
```
|
499
|
+
|
500
|
+
#### `#attributes(keys_as:)`
|
501
|
+
|
502
|
+
Use the `keys_as:` option with `Symbol`/`:symbol` or `String`/`:string` to transform the attributes hash keys.
|
503
|
+
|
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)
|
468
558
|
|
469
|
-
|
470
|
-
|
471
|
-
#---------------#
|
559
|
+
person.attributes(without: :age) # {"first_name"=>"John", "last_name"=>"Doe"}
|
560
|
+
person.attributes(without: [:age, :last_name]) # {"first_name"=>"John"}
|
472
561
|
|
473
|
-
person.attributes
|
474
|
-
Person.new(name: 'John').attributes # {'age'=>nil, 'name'=>'John'}
|
562
|
+
person.attributes(with: [:name], without: [:first_name, :last_name]) # {"age"=>20, "name"=>"John Doe"}
|
475
563
|
|
476
|
-
|
477
|
-
# #attributes(*names) #
|
478
|
-
#---------------------#
|
564
|
+
# To achieves the same output of the previous example, use the attribute names to slice only them.
|
479
565
|
|
480
|
-
|
481
|
-
# Returns a hash containing the given keys (in their types).
|
566
|
+
person.attributes(:age, with: [:name]) # {:age=>20, "name"=>"John Doe"}
|
482
567
|
|
483
|
-
|
484
|
-
|
485
|
-
person.attributes(
|
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"]
|
486
581
|
```
|
487
582
|
|
488
583
|
[⬆️ Back to Top](#table-of-contents-)
|
@@ -500,24 +595,30 @@ But, if you desire except one or more features, use the `Micro::Attributes.witho
|
|
500
595
|
```ruby
|
501
596
|
Micro::Attributes.with(:initialize)
|
502
597
|
|
503
|
-
Micro::Attributes.with(initialize
|
598
|
+
Micro::Attributes.with(:initialize, :keys_as_symbol)
|
599
|
+
|
600
|
+
Micro::Attributes.with(:keys_as_symbol, initialize: :strict)
|
504
601
|
|
505
602
|
Micro::Attributes.with(:diff, :initialize)
|
506
603
|
|
507
604
|
Micro::Attributes.with(:diff, initialize: :strict)
|
508
605
|
|
606
|
+
Micro::Attributes.with(:diff, :keys_as_symbol, initialize: :strict)
|
607
|
+
|
509
608
|
Micro::Attributes.with(:activemodel_validations)
|
510
609
|
|
511
610
|
Micro::Attributes.with(:activemodel_validations, :diff)
|
512
611
|
|
513
612
|
Micro::Attributes.with(:activemodel_validations, :diff, initialize: :strict)
|
613
|
+
|
614
|
+
Micro::Attributes.with(:activemodel_validations, :diff, :keys_as_symbol, initialize: :strict)
|
514
615
|
```
|
515
616
|
|
516
617
|
The method `Micro::Attributes.with()` will raise an exception if no arguments/features were declared.
|
517
618
|
|
518
619
|
```ruby
|
519
620
|
class Job
|
520
|
-
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: :accept, :activemodel_validations, :diff, :initialize, :keys_as_symbol)
|
521
622
|
end
|
522
623
|
```
|
523
624
|
|
@@ -526,15 +627,19 @@ end
|
|
526
627
|
Picking *except* one or more features
|
527
628
|
|
528
629
|
```ruby
|
529
|
-
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
|
530
631
|
|
531
|
-
Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations and :
|
632
|
+
Micro::Attributes.without(initialize: :strict) # will load :activemodel_validations, :diff and :keys_as_symbol
|
532
633
|
```
|
533
634
|
|
534
635
|
## Picking all the features
|
535
636
|
|
536
637
|
```ruby
|
537
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)
|
538
643
|
```
|
539
644
|
|
540
645
|
[⬆️ Back to Top](#table-of-contents-)
|
@@ -726,6 +831,40 @@ job.state # 'sleeping'
|
|
726
831
|
|
727
832
|
[⬆️ Back to Top](#table-of-contents-)
|
728
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
|
+
|
729
868
|
# Development
|
730
869
|
|
731
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.
|
data/lib/micro/attributes.rb
CHANGED
@@ -16,6 +16,7 @@ module Micro
|
|
16
16
|
base.class_eval do
|
17
17
|
private_class_method :__attributes, :__attribute_reader
|
18
18
|
private_class_method :__attribute_assign, :__attributes_data_to_assign
|
19
|
+
private_class_method :__attributes_required_add, :__attributes_data_to_assign
|
19
20
|
end
|
20
21
|
|
21
22
|
def base.inherited(subclass)
|
@@ -55,66 +56,85 @@ module Micro
|
|
55
56
|
raise NameError, "undefined attribute `#{name}"
|
56
57
|
end
|
57
58
|
|
59
|
+
def defined_attributes
|
60
|
+
@defined_attributes ||= self.class.attributes
|
61
|
+
end
|
62
|
+
|
58
63
|
def attributes(*names)
|
59
64
|
return __attributes if names.empty?
|
60
65
|
|
61
|
-
names.
|
62
|
-
|
66
|
+
options = names.last.is_a?(Hash) ? names.pop : Kind::Empty::HASH
|
67
|
+
|
68
|
+
names.flatten!
|
69
|
+
|
70
|
+
without_option = Array(options.fetch(:without, Kind::Empty::ARRAY))
|
71
|
+
|
72
|
+
keys = names.empty? ? defined_attributes - without_option.map { |value| __attribute_key(value) } : names - without_option
|
73
|
+
|
74
|
+
data = keys.each_with_object({}) { |key, memo| memo[key] = attribute(key) if attribute?(key) }
|
75
|
+
|
76
|
+
with_option = Array(options.fetch(:with, Kind::Empty::ARRAY))
|
77
|
+
|
78
|
+
unless with_option.empty?
|
79
|
+
extra = with_option.each_with_object({}) { |key, memo| memo[__attribute_key(key)] = public_send(key) }
|
80
|
+
|
81
|
+
data.merge!(extra)
|
63
82
|
end
|
64
|
-
end
|
65
83
|
|
66
|
-
|
67
|
-
@defined_attributes ||= self.class.attributes
|
84
|
+
Utils::Hashes.keys_as(options[:keys_as], data)
|
68
85
|
end
|
69
86
|
|
70
87
|
protected
|
71
88
|
|
72
89
|
def attributes=(arg)
|
73
|
-
hash =
|
90
|
+
hash = self.class.__attributes_keys_transform__(arg)
|
74
91
|
|
75
92
|
__attributes_missing!(hash)
|
76
93
|
|
94
|
+
__call_before_attributes_assign
|
77
95
|
__attributes_assign(hash)
|
96
|
+
__call_after_attributes_assign
|
97
|
+
|
98
|
+
__attributes
|
78
99
|
end
|
79
100
|
|
80
101
|
private
|
81
102
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
other.public_send(key) if other.respond_to?(key)
|
86
|
-
}
|
103
|
+
def __call_before_attributes_assign; end
|
104
|
+
def __call_after_attributes_assign; end
|
87
105
|
|
88
106
|
def extract_attributes_from(other)
|
89
|
-
|
90
|
-
|
91
|
-
|
107
|
+
Utils::ExtractAttribute.from(other, keys: defined_attributes)
|
108
|
+
end
|
109
|
+
|
110
|
+
def __attribute_key(value)
|
111
|
+
self.class.__attribute_key_transform__(value)
|
92
112
|
end
|
93
113
|
|
94
114
|
def __attributes
|
95
115
|
@__attributes ||= {}
|
96
116
|
end
|
97
117
|
|
98
|
-
FetchValueToAssign = -> (value, default) do
|
99
|
-
if default.
|
100
|
-
|
101
|
-
|
102
|
-
callable.arity > 0 ? callable.call(value) : callable.call
|
118
|
+
FetchValueToAssign = -> (value, default, keep_proc = false) do
|
119
|
+
if default.is_a?(Proc) && !keep_proc
|
120
|
+
default.arity > 0 ? default.call(value) : default.call
|
103
121
|
else
|
104
122
|
value.nil? ? default : value
|
105
123
|
end
|
106
124
|
end
|
107
125
|
|
108
126
|
def __attributes_assign(hash)
|
109
|
-
self.class.__attributes_data__.each do |name,
|
110
|
-
__attribute_assign(name,
|
127
|
+
self.class.__attributes_data__.each do |name, attribute_data|
|
128
|
+
__attribute_assign(name, hash[name], attribute_data) if attribute?(name)
|
111
129
|
end
|
112
130
|
|
113
131
|
__attributes.freeze
|
114
132
|
end
|
115
133
|
|
116
|
-
def __attribute_assign(name,
|
117
|
-
|
134
|
+
def __attribute_assign(name, initialize_value, attribute_data)
|
135
|
+
value_to_assign = FetchValueToAssign.(initialize_value, attribute_data[0])
|
136
|
+
|
137
|
+
__attributes[name] = instance_variable_set("@#{name}", value_to_assign)
|
118
138
|
end
|
119
139
|
|
120
140
|
MISSING_KEYWORD = 'missing keyword'.freeze
|