u-attributes 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![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
|
-
|
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
|