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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d105f8057ad000b717972d8b2b5624d3f5d7608256f82402878ae0929c6462c7
4
- data.tar.gz: 20f7b0132c445194fb3189b81c7c29c482a2fbeffdb1574444f833ede5619308
3
+ metadata.gz: 01dbce30afd7d116e15ea5985e5012d86f2bd110b017394151086df322e9b7ed
4
+ data.tar.gz: 4004e27e835cc216807b2fb2781a15911c16f2ba20e509b7c36188a8b6021f95
5
5
  SHA512:
6
- metadata.gz: 7d5e1d415a6b94dfa9c90aad9f8700829696d1e1b2fa054d6bf62c9eac477f618604c59695b55c3a18dd2405b28c4af63c886057f2ef740cba747f19b2273706
7
- data.tar.gz: 4493325bbeed30c82c13690ad35d87926a9913b3b33d18cc0ef2dedae8eca2e976fc43d2485bb65715b2cb6c51bc383dfd8b0c59b50885bd5099624b568455d6
6
+ metadata.gz: ca177ca900f63a07901366612f5ef545512db4e8f9b48bea6482403af6346324784910fa9dca963e681449a1d3b583b173317890b76e8a2e03fd46b58af32006
7
+ data.tar.gz: 44372485364d637906d1dad476fc58a98cf228c33e252012af320e7e1da36b82db3943eb635ed34dff211908088f6fc8fe3c2de8a1b9a418f62f8e8b3e1596ed
@@ -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/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.1.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
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 3 different strategies to define default values.
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 :name, default: 'John Doe'
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
- Person.attributes # ['name', 'age']
456
+ Listing all the class attributes.
440
457
 
441
- #---------------#
442
- # .attribute?() #
443
- #---------------#
458
+ ```ruby
459
+ Person.attributes # ["age", "first_name", "last_name"]
460
+ ```
444
461
 
445
- Person.attribute?(:name) # true
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
- person = Person.new(age: 20)
466
+ ```ruby
467
+ Person.attribute?(:first_name) # true
468
+ Person.attribute?('first_name') # true
453
469
 
454
- #---------------------#
455
- # #defined_attributes #
456
- #---------------------#
470
+ Person.attribute?('foo') # false
471
+ Person.attribute?(:foo) # false
472
+ ```
457
473
 
458
- person.defined_attributes # ['name', 'age']
474
+ ### `#attribute?()`
459
475
 
460
- #---------------#
461
- # #attribute?() #
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
- # #attributes() #
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 # {'age'=>20, 'name'=>'John Doe'}
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
- # Slices the attributes to include only the given keys.
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
- 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'}
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: :strict)
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 :diff
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.
@@ -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.each_with_object({}) do |name, memo|
62
- memo[name] = attribute(name) if attribute?(name)
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
- def defined_attributes
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 = Utils.stringify_hash_keys(arg)
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
- ExtractAttribute = -> (other, key) {
83
- return Utils::HashAccess.(other, key) if other.respond_to?(:[])
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
- defined_attributes.each_with_object({}) do |key, memo|
90
- memo[key] = ExtractAttribute.(other, key)
91
- end
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.respond_to?(:call)
100
- callable = default.is_a?(Proc) ? default : default.method(:call)
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, default|
110
- __attribute_assign(name, FetchValueToAssign.(hash[name], default)) if attribute?(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, value)
117
- __attributes[name] = instance_variable_set("@#{name}", value)
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