u-attributes 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +10 -1
- data/README.md +101 -13
- data/assets/u-attributes_logo_v1.png +0 -0
- data/lib/micro/attributes.rb +56 -17
- data/lib/micro/attributes/features/activemodel_validations.rb +1 -1
- data/lib/micro/attributes/features/initialize/strict.rb +6 -26
- data/lib/micro/attributes/macros.rb +36 -8
- data/lib/micro/attributes/utils.rb +6 -0
- data/lib/micro/attributes/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d105f8057ad000b717972d8b2b5624d3f5d7608256f82402878ae0929c6462c7
|
4
|
+
data.tar.gz: 20f7b0132c445194fb3189b81c7c29c482a2fbeffdb1574444f833ede5619308
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d5e1d415a6b94dfa9c90aad9f8700829696d1e1b2fa054d6bf62c9eac477f618604c59695b55c3a18dd2405b28c4af63c886057f2ef740cba747f19b2273706
|
7
|
+
data.tar.gz: 4493325bbeed30c82c13690ad35d87926a9913b3b33d18cc0ef2dedae8eca2e976fc43d2485bb65715b2cb6c51bc383dfd8b0c59b50885bd5099624b568455d6
|
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
|
-
|
2
|
-
|
3
|
-
[](https://travis-ci.com/serradura/u-attributes)
|
4
|
-
[](https://codeclimate.com/github/serradura/u-attributes/maintainability)
|
5
|
-
[](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
|
-
|
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
|
-
|
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)
|
@@ -55,7 +75,7 @@ gem 'u-attributes'
|
|
55
75
|
|
56
76
|
| u-attributes | branch | ruby | activemodel |
|
57
77
|
| -------------- | ------- | -------- | ------------- |
|
58
|
-
| 2.
|
78
|
+
| 2.1.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
59
79
|
| 1.2.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
60
80
|
|
61
81
|
> **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
|
@@ -66,9 +86,9 @@ gem 'u-attributes'
|
|
66
86
|
|
67
87
|
## How to define attributes?
|
68
88
|
|
69
|
-
|
70
|
-
# By default you must to define the class constructor.
|
89
|
+
By default, you must define the class constructor.
|
71
90
|
|
91
|
+
```ruby
|
72
92
|
class Person
|
73
93
|
include Micro::Attributes
|
74
94
|
|
@@ -116,6 +136,66 @@ person.age # 20
|
|
116
136
|
person.name # John Doe
|
117
137
|
```
|
118
138
|
|
139
|
+
#### How to extract attributes from an object or hash?
|
140
|
+
|
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`.
|
143
|
+
|
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
|
195
|
+
|
196
|
+
Person.new(age: 32) # ArgumentError (missing keyword: :name)
|
197
|
+
```
|
198
|
+
|
119
199
|
[⬆️ Back to Top](#table-of-contents-)
|
120
200
|
|
121
201
|
### `Micro::Attributes#attribute`
|
@@ -185,7 +265,7 @@ Use `Micro::Attributes.with(:initialize)` to define a constructor to assign the
|
|
185
265
|
class Person
|
186
266
|
include Micro::Attributes.with(:initialize)
|
187
267
|
|
188
|
-
attribute :age
|
268
|
+
attribute :age, required: true
|
189
269
|
attribute :name, default: 'John Doe'
|
190
270
|
end
|
191
271
|
|
@@ -258,7 +338,9 @@ end
|
|
258
338
|
|
259
339
|
## The strict initializer
|
260
340
|
|
261
|
-
Use `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords.
|
341
|
+
Use `.with(initialize: :strict)` to forbids an instantiation without all the attribute keywords.
|
342
|
+
|
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).
|
262
344
|
|
263
345
|
```ruby
|
264
346
|
class StrictPerson
|
@@ -369,6 +451,12 @@ Person.attribute?(:foo) # false
|
|
369
451
|
|
370
452
|
person = Person.new(age: 20)
|
371
453
|
|
454
|
+
#---------------------#
|
455
|
+
# #defined_attributes #
|
456
|
+
#---------------------#
|
457
|
+
|
458
|
+
person.defined_attributes # ['name', 'age']
|
459
|
+
|
372
460
|
#---------------#
|
373
461
|
# #attribute?() #
|
374
462
|
#---------------#
|
Binary file
|
data/lib/micro/attributes.rb
CHANGED
@@ -14,7 +14,8 @@ module Micro
|
|
14
14
|
base.extend(::Micro::Attributes.const_get(:Macros))
|
15
15
|
|
16
16
|
base.class_eval do
|
17
|
-
private_class_method :__attributes, :
|
17
|
+
private_class_method :__attributes, :__attribute_reader
|
18
|
+
private_class_method :__attribute_assign, :__attributes_data_to_assign
|
18
19
|
end
|
19
20
|
|
20
21
|
def base.inherited(subclass)
|
@@ -62,40 +63,78 @@ module Micro
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
66
|
+
def defined_attributes
|
67
|
+
@defined_attributes ||= self.class.attributes
|
68
|
+
end
|
69
|
+
|
65
70
|
protected
|
66
71
|
|
67
72
|
def attributes=(arg)
|
68
73
|
hash = Utils.stringify_hash_keys(arg)
|
69
74
|
|
70
|
-
|
75
|
+
__attributes_missing!(hash)
|
76
|
+
|
77
|
+
__attributes_assign(hash)
|
71
78
|
end
|
72
79
|
|
73
80
|
private
|
74
81
|
|
75
|
-
|
76
|
-
|
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
|
+
}
|
87
|
+
|
88
|
+
def extract_attributes_from(other)
|
89
|
+
defined_attributes.each_with_object({}) do |key, memo|
|
90
|
+
memo[key] = ExtractAttribute.(other, key)
|
91
|
+
end
|
77
92
|
end
|
78
93
|
|
79
|
-
def
|
80
|
-
__attributes
|
94
|
+
def __attributes
|
95
|
+
@__attributes ||= {}
|
81
96
|
end
|
82
97
|
|
83
|
-
|
84
|
-
|
85
|
-
|
98
|
+
FetchValueToAssign = -> (value, default) do
|
99
|
+
if default.respond_to?(:call)
|
100
|
+
callable = default.is_a?(Proc) ? default : default.method(:call)
|
86
101
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
value.nil? ? default : value
|
93
|
-
end
|
102
|
+
callable.arity > 0 ? callable.call(value) : callable.call
|
103
|
+
else
|
104
|
+
value.nil? ? default : value
|
105
|
+
end
|
106
|
+
end
|
94
107
|
|
95
|
-
|
108
|
+
def __attributes_assign(hash)
|
109
|
+
self.class.__attributes_data__.each do |name, default|
|
110
|
+
__attribute_assign(name, FetchValueToAssign.(hash[name], default)) if attribute?(name)
|
96
111
|
end
|
97
112
|
|
98
113
|
__attributes.freeze
|
99
114
|
end
|
115
|
+
|
116
|
+
def __attribute_assign(name, value)
|
117
|
+
__attributes[name] = instance_variable_set("@#{name}", value)
|
118
|
+
end
|
119
|
+
|
120
|
+
MISSING_KEYWORD = 'missing keyword'.freeze
|
121
|
+
MISSING_KEYWORDS = 'missing keywords'.freeze
|
122
|
+
|
123
|
+
def __attributes_missing!(hash)
|
124
|
+
required_keys = self.class.__attributes_required__
|
125
|
+
|
126
|
+
return if required_keys.empty?
|
127
|
+
|
128
|
+
missing_keys = required_keys.map { |name| ":#{name}" if !hash.key?(name) }
|
129
|
+
missing_keys.compact!
|
130
|
+
|
131
|
+
return if missing_keys.empty?
|
132
|
+
|
133
|
+
label = missing_keys.size == 1 ? MISSING_KEYWORD : MISSING_KEYWORDS
|
134
|
+
|
135
|
+
raise ArgumentError, "#{label}: #{missing_keys.join(', ')}"
|
136
|
+
end
|
137
|
+
|
138
|
+
private_constant :FetchValueToAssign, :MISSING_KEYWORD, :MISSING_KEYWORDS
|
100
139
|
end
|
101
140
|
end
|
@@ -14,7 +14,7 @@ module Micro::Attributes
|
|
14
14
|
end
|
15
15
|
|
16
16
|
module ClassMethods
|
17
|
-
def
|
17
|
+
def __call_after_attribute_assign__(attr_name, options)
|
18
18
|
validate, validates = options.values_at(:validate, :validates)
|
19
19
|
|
20
20
|
self.validate(validate) if validate
|
@@ -4,35 +4,15 @@ module Micro::Attributes
|
|
4
4
|
module Features
|
5
5
|
module Initialize
|
6
6
|
module Strict
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
protected def attributes=(arg)
|
11
|
-
arg_hash = Utils.stringify_hash_keys(arg)
|
12
|
-
att_data = self.class.__attributes_data__
|
13
|
-
|
14
|
-
attributes_missing!(ref: att_data, arg: arg_hash)
|
15
|
-
|
16
|
-
__attributes_set(arg_hash, att_data)
|
17
|
-
end
|
18
|
-
|
19
|
-
private def attributes_missing!(ref:, arg:)
|
20
|
-
missing_keys = attributes_missing(ref, arg)
|
21
|
-
|
22
|
-
return if missing_keys.empty?
|
23
|
-
|
24
|
-
label = missing_keys.size == 1 ? MISSING_KEYWORD : MISSING_KEYWORDS
|
25
|
-
|
26
|
-
raise ArgumentError, "#{label}: #{missing_keys.join(', ')}"
|
27
|
-
end
|
28
|
-
|
29
|
-
private def attributes_missing(ref, arg)
|
30
|
-
ref.each_with_object([]) do |(key, val), memo|
|
31
|
-
memo << ":#{key}" if val.nil? && !arg.has_key?(key)
|
7
|
+
module ClassMethods
|
8
|
+
def attributes_are_all_required?
|
9
|
+
true
|
32
10
|
end
|
33
11
|
end
|
34
12
|
|
35
|
-
|
13
|
+
def self.included(base)
|
14
|
+
base.send(:extend, ClassMethods)
|
15
|
+
end
|
36
16
|
end
|
37
17
|
end
|
38
18
|
end
|
@@ -3,10 +3,33 @@
|
|
3
3
|
module Micro
|
4
4
|
module Attributes
|
5
5
|
module Macros
|
6
|
+
def attributes_are_all_required?
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
# NOTE: can't be renamed! It is used by u-case v4.
|
6
11
|
def __attributes_data__
|
7
12
|
@__attributes_data__ ||= {}
|
8
13
|
end
|
9
14
|
|
15
|
+
def __attributes_required__
|
16
|
+
@__attributes_required__ ||= Set.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def __attributes_required_add(name, is_required, hasnt_default)
|
20
|
+
if is_required || (attributes_are_all_required? && hasnt_default)
|
21
|
+
__attributes_required__.add(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def __attributes_data_to_assign(name, options)
|
28
|
+
hasnt_default = !options.key?(:default)
|
29
|
+
|
30
|
+
hasnt_default ? __attributes_required_add(name, options[:required], hasnt_default) : options[:default]
|
31
|
+
end
|
32
|
+
|
10
33
|
def __attributes
|
11
34
|
@__attributes ||= Set.new
|
12
35
|
end
|
@@ -17,20 +40,24 @@ module Micro
|
|
17
40
|
attr_reader(name)
|
18
41
|
end
|
19
42
|
|
20
|
-
def
|
43
|
+
def __attribute_assign(key, can_overwrite, options)
|
21
44
|
name = key.to_s
|
22
45
|
has_attribute = attribute?(name)
|
23
46
|
|
24
47
|
__attribute_reader(name) unless has_attribute
|
25
|
-
__attributes_data__[name] = options[:default] if can_overwrite || !has_attribute
|
26
48
|
|
27
|
-
|
49
|
+
__attributes_data__[name] = __attributes_data_to_assign(name, options) if can_overwrite || !has_attribute
|
50
|
+
|
51
|
+
__call_after_attribute_assign__(name, options)
|
28
52
|
end
|
29
53
|
|
30
|
-
def
|
54
|
+
def __call_after_attribute_assign__(attr_name, options); end
|
31
55
|
|
56
|
+
# NOTE: can't be renamed! It is used by u-case v4.
|
32
57
|
def __attributes_set_after_inherit__(arg)
|
33
|
-
arg.each
|
58
|
+
arg.each do |key, val|
|
59
|
+
__attribute_assign(key, true, val ? { default: val } : {})
|
60
|
+
end
|
34
61
|
end
|
35
62
|
|
36
63
|
def attribute?(name)
|
@@ -38,7 +65,7 @@ module Micro
|
|
38
65
|
end
|
39
66
|
|
40
67
|
def attribute(name, options = Kind::Empty::HASH)
|
41
|
-
|
68
|
+
__attribute_assign(name, false, options)
|
42
69
|
end
|
43
70
|
|
44
71
|
def attributes(*args)
|
@@ -47,18 +74,19 @@ module Micro
|
|
47
74
|
args.flatten!
|
48
75
|
args.each do |arg|
|
49
76
|
if arg.is_a?(String) || arg.is_a?(Symbol)
|
50
|
-
|
77
|
+
__attribute_assign(arg, false, Kind::Empty::HASH)
|
51
78
|
else
|
52
79
|
raise Kind::Error.new('String/Symbol'.freeze, arg)
|
53
80
|
end
|
54
81
|
end
|
55
82
|
end
|
56
83
|
|
84
|
+
# NOTE: can't be renamed! It is used by u-case v4.
|
57
85
|
module ForSubclasses
|
58
86
|
WRONG_NUMBER_OF_ARGS = 'wrong number of arguments (given 0, expected 1 or more)'.freeze
|
59
87
|
|
60
88
|
def attribute!(name, options = Kind::Empty::HASH)
|
61
|
-
|
89
|
+
__attribute_assign(name, true, options)
|
62
90
|
end
|
63
91
|
|
64
92
|
private_constant :WRONG_NUMBER_OF_ARGS
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: u-attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kind
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- LICENSE.txt
|
77
77
|
- README.md
|
78
78
|
- Rakefile
|
79
|
+
- assets/u-attributes_logo_v1.png
|
79
80
|
- bin/console
|
80
81
|
- bin/setup
|
81
82
|
- lib/micro/attributes.rb
|