u-attributes 2.4.0 → 2.8.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/.github/workflows/ci.yml +27 -0
- data/.vscode/settings.json +8 -0
- data/Gemfile +17 -12
- data/README.md +17 -8
- data/bin/prepare_coverage +27 -0
- data/bin/test +48 -0
- data/lib/micro/attributes/diff.rb +5 -5
- data/lib/micro/attributes/features/accept/strict.rb +26 -0
- data/lib/micro/attributes/features/accept.rb +132 -0
- data/lib/micro/attributes/features/activemodel_validations.rb +53 -9
- data/lib/micro/attributes/features/initialize.rb +3 -8
- data/lib/micro/attributes/features/keys_as_symbol.rb +5 -5
- data/lib/micro/attributes/features.rb +85 -27
- data/lib/micro/attributes/macros.rb +143 -30
- data/lib/micro/attributes/utils.rb +35 -37
- data/lib/micro/attributes/version.rb +1 -1
- data/lib/micro/attributes.rb +51 -17
- data/u-attributes.gemspec +4 -5
- metadata +20 -19
- data/.travis.sh +0 -31
- data/.travis.yml +0 -28
- data/lib/micro/attributes/with.rb +0 -182
- data/test.sh +0 -7
@@ -7,22 +7,17 @@ module Micro::Attributes
|
|
7
7
|
base.class_eval(<<-RUBY)
|
8
8
|
def initialize(arg)
|
9
9
|
self.attributes = arg
|
10
|
-
__call_after_micro_attribute
|
11
10
|
end
|
12
11
|
RUBY
|
13
12
|
end
|
14
13
|
|
15
|
-
def with_attribute(key, val)
|
16
|
-
self.class.new(attributes.merge(key => val))
|
17
|
-
end
|
18
|
-
|
19
14
|
def with_attributes(arg)
|
20
15
|
self.class.new(attributes.merge(arg))
|
21
16
|
end
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
def with_attribute(key, val)
|
19
|
+
with_attributes(key => val)
|
20
|
+
end
|
26
21
|
end
|
27
22
|
end
|
28
23
|
end
|
@@ -9,16 +9,16 @@ module Micro::Attributes
|
|
9
9
|
:symbol
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
Kind::
|
12
|
+
def __attribute_key_check__(value)
|
13
|
+
Kind::Symbol[value]
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def __attribute_key_transform__(value)
|
17
17
|
value
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
20
|
+
def __attributes_keys_transform__(hash)
|
21
|
+
Kind::Hash[hash]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -1,15 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'micro/attributes/with'
|
4
|
-
|
5
3
|
module Micro
|
6
4
|
module Attributes
|
5
|
+
module With
|
6
|
+
end
|
7
|
+
|
7
8
|
module Features
|
9
|
+
require 'micro/attributes/features/diff'
|
10
|
+
require 'micro/attributes/features/accept'
|
11
|
+
require 'micro/attributes/features/accept/strict'
|
12
|
+
require 'micro/attributes/features/initialize'
|
13
|
+
require 'micro/attributes/features/initialize/strict'
|
14
|
+
require 'micro/attributes/features/keys_as_symbol'
|
15
|
+
require 'micro/attributes/features/activemodel_validations'
|
16
|
+
|
8
17
|
extend self
|
9
18
|
|
10
19
|
module Name
|
11
20
|
ALL = [
|
12
21
|
DIFF = 'diff'.freeze,
|
22
|
+
ACCEPT = 'accept'.freeze,
|
13
23
|
INITIALIZE = 'initialize'.freeze,
|
14
24
|
KEYS_AS_SYMBOL = 'keys_as_symbol'.freeze,
|
15
25
|
ACTIVEMODEL_VALIDATIONS = 'activemodel_validations'.freeze
|
@@ -19,36 +29,74 @@ module Micro
|
|
19
29
|
module Options
|
20
30
|
KEYS = [
|
21
31
|
DIFF = 'Diff'.freeze,
|
22
|
-
INIT = '
|
23
|
-
|
32
|
+
INIT = 'Initialize'.freeze,
|
33
|
+
ACCEPT = 'Accept'.freeze,
|
34
|
+
INIT_STRICT = 'InitializeStrict'.freeze,
|
35
|
+
ACCEPT_STRICT = 'AcceptStrict'.freeze,
|
24
36
|
KEYS_AS_SYMBOL = 'KeysAsSymbol'.freeze,
|
25
|
-
AM_VALIDATIONS = '
|
37
|
+
AM_VALIDATIONS = 'ActiveModelValidations'.freeze
|
26
38
|
].sort.freeze
|
27
39
|
|
40
|
+
KEYS_TO_FEATURES = {
|
41
|
+
DIFF => Features::Diff,
|
42
|
+
INIT => Features::Initialize,
|
43
|
+
ACCEPT => Features::Accept,
|
44
|
+
INIT_STRICT => Features::Initialize::Strict,
|
45
|
+
ACCEPT_STRICT => Features::Accept::Strict,
|
46
|
+
KEYS_AS_SYMBOL => Features::KeysAsSymbol,
|
47
|
+
AM_VALIDATIONS => Features::ActiveModelValidations
|
48
|
+
}.freeze
|
49
|
+
|
28
50
|
NAMES_TO_KEYS = {
|
29
51
|
Name::DIFF => DIFF,
|
52
|
+
Name::ACCEPT => ACCEPT,
|
30
53
|
Name::INITIALIZE => INIT,
|
31
54
|
Name::KEYS_AS_SYMBOL => KEYS_AS_SYMBOL,
|
32
55
|
Name::ACTIVEMODEL_VALIDATIONS => AM_VALIDATIONS
|
33
56
|
}.freeze
|
34
57
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
58
|
+
INIT_INIT_STRICT = "#{INIT}_#{INIT_STRICT}".freeze
|
59
|
+
ACCEPT_ACCEPT_STRICT = "#{ACCEPT}_#{ACCEPT_STRICT}".freeze
|
60
|
+
|
61
|
+
BuildKey = -> combination do
|
62
|
+
combination.sort.join('_')
|
63
|
+
.sub(INIT_INIT_STRICT, INIT_STRICT)
|
64
|
+
.sub(ACCEPT_ACCEPT_STRICT, ACCEPT_STRICT)
|
65
|
+
end
|
66
|
+
|
67
|
+
KEYS_TO_MODULES = begin
|
68
|
+
combinations = (1..KEYS.size).map { |n| KEYS.combination(n).to_a }.flatten(1).sort_by { |i| "#{i.size}#{i.join}" }
|
69
|
+
combinations.delete_if { |combination| combination.include?(INIT_STRICT) && !combination.include?(INIT) }
|
70
|
+
combinations.delete_if { |combination| combination.include?(ACCEPT_STRICT) && !combination.include?(ACCEPT) }
|
71
|
+
combinations.each_with_object({}) do |combination, features|
|
72
|
+
included = [
|
73
|
+
'def self.included(base)',
|
74
|
+
' base.send(:include, ::Micro::Attributes)',
|
75
|
+
combination.map { |key| " base.send(:include, ::#{KEYS_TO_FEATURES[key].name})" },
|
76
|
+
'end'
|
77
|
+
].flatten.join("\n")
|
78
|
+
|
79
|
+
key = BuildKey.call(combination)
|
80
|
+
|
81
|
+
With.const_set(key, Module.new.tap { |mod| mod.instance_eval(included) })
|
82
|
+
|
83
|
+
features[key] = With.const_get(key, false)
|
84
|
+
end.freeze
|
85
|
+
end
|
86
|
+
|
87
|
+
ACTIVEMODEL_VALIDATION = 'activemodel_validation'.freeze
|
42
88
|
|
43
89
|
def self.fetch_key(arg)
|
44
90
|
if arg.is_a?(Hash)
|
91
|
+
return ACCEPT_STRICT if arg[:accept] == :strict
|
92
|
+
|
45
93
|
INIT_STRICT if arg[:initialize] == :strict
|
46
94
|
else
|
47
|
-
|
95
|
+
str = String(arg)
|
48
96
|
|
49
|
-
|
97
|
+
name = str == ACTIVEMODEL_VALIDATION ? Name::ACTIVEMODEL_VALIDATIONS : str
|
50
98
|
|
51
|
-
NAMES_TO_KEYS[name]
|
99
|
+
KEYS_TO_MODULES.key?(name) ? name : NAMES_TO_KEYS[name]
|
52
100
|
end
|
53
101
|
end
|
54
102
|
|
@@ -65,23 +113,26 @@ module Micro
|
|
65
113
|
yield(keys)
|
66
114
|
end
|
67
115
|
|
68
|
-
def self.
|
69
|
-
keys.delete_if { |key| key == INIT
|
116
|
+
def self.remove_base_if_has_strict(keys)
|
117
|
+
keys.delete_if { |key| key == INIT } if keys.include?(INIT_STRICT)
|
118
|
+
keys.delete_if { |key| key == ACCEPT } if keys.include?(ACCEPT_STRICT)
|
70
119
|
end
|
71
120
|
|
72
121
|
def self.without_keys(keys_to_exclude)
|
73
|
-
(KEYS - keys_to_exclude)
|
74
|
-
|
75
|
-
|
122
|
+
keys = (KEYS - keys_to_exclude)
|
123
|
+
keys.delete_if { |key| key == INIT || key == INIT_STRICT } if keys_to_exclude.include?(INIT)
|
124
|
+
keys.delete_if { |key| key == ACCEPT || key == ACCEPT_STRICT } if keys_to_exclude.include?(ACCEPT)
|
125
|
+
keys
|
76
126
|
end
|
77
127
|
|
78
|
-
def self.fetch_module_by_keys(
|
79
|
-
|
80
|
-
|
81
|
-
option = keys.sort.join('_')
|
128
|
+
def self.fetch_module_by_keys(combination)
|
129
|
+
key = BuildKey.call(combination)
|
82
130
|
|
83
|
-
KEYS_TO_MODULES.fetch(
|
131
|
+
KEYS_TO_MODULES.fetch(key)
|
84
132
|
end
|
133
|
+
|
134
|
+
private_constant :KEYS_TO_FEATURES, :NAMES_TO_KEYS, :INVALID_NAME
|
135
|
+
private_constant :INIT_INIT_STRICT, :ACCEPT_ACCEPT_STRICT, :BuildKey
|
85
136
|
end
|
86
137
|
|
87
138
|
def all
|
@@ -90,17 +141,24 @@ module Micro
|
|
90
141
|
|
91
142
|
def with(names)
|
92
143
|
Options.fetch_keys(names) do |keys|
|
144
|
+
Options.remove_base_if_has_strict(keys)
|
145
|
+
|
93
146
|
Options.fetch_module_by_keys(keys)
|
94
147
|
end
|
95
148
|
end
|
96
149
|
|
97
150
|
def without(names)
|
98
151
|
Options.fetch_keys(names) do |keys|
|
99
|
-
|
152
|
+
keys_to_fetch = Options.without_keys(keys)
|
100
153
|
|
101
|
-
|
154
|
+
return ::Micro::Attributes if keys_to_fetch.empty?
|
155
|
+
|
156
|
+
Options.fetch_module_by_keys(keys_to_fetch)
|
102
157
|
end
|
103
158
|
end
|
159
|
+
|
160
|
+
private_constant :Name
|
104
161
|
end
|
162
|
+
|
105
163
|
end
|
106
164
|
end
|
@@ -3,6 +3,60 @@
|
|
3
3
|
module Micro
|
4
4
|
module Attributes
|
5
5
|
module Macros
|
6
|
+
module Options
|
7
|
+
PERMITTED = [
|
8
|
+
:default, :required, :freeze, :protected, :private, # for all
|
9
|
+
:validate, :validates, # for ext: activemodel_validations
|
10
|
+
:accept, :reject, :allow_nil, :rejection_message # for ext: accept
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
INVALID_MESSAGE = [
|
14
|
+
"Found one or more invalid options: %{invalid_options}\n\nThe valid ones are: ",
|
15
|
+
PERMITTED.map { |key| ":#{key}" }.join(', ')
|
16
|
+
].join.freeze
|
17
|
+
|
18
|
+
def self.check(opt)
|
19
|
+
invalid_keys = opt.keys - PERMITTED
|
20
|
+
|
21
|
+
return if invalid_keys.empty?
|
22
|
+
|
23
|
+
invalid_options = { invalid_options: invalid_keys.inspect.tr('[', '').tr(']', '') }
|
24
|
+
|
25
|
+
raise ArgumentError, (INVALID_MESSAGE % invalid_options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.for_accept(opt)
|
29
|
+
allow_nil = opt[:allow_nil]
|
30
|
+
rejection_message = opt[:rejection_message]
|
31
|
+
|
32
|
+
return [:accept, opt[:accept], allow_nil, rejection_message] if opt.key?(:accept)
|
33
|
+
return [:reject, opt[:reject], allow_nil, rejection_message] if opt.key?(:reject)
|
34
|
+
|
35
|
+
Kind::Empty::ARRAY
|
36
|
+
end
|
37
|
+
|
38
|
+
ALL = 0
|
39
|
+
PUBLIC = 1
|
40
|
+
PRIVATE = 2
|
41
|
+
PROTECTED = 3
|
42
|
+
REQUIRED = 4
|
43
|
+
|
44
|
+
def self.visibility_index(opt)
|
45
|
+
return PRIVATE if opt[:private]
|
46
|
+
return PROTECTED if opt[:protected]
|
47
|
+
PUBLIC
|
48
|
+
end
|
49
|
+
|
50
|
+
VISIBILITY_NAMES = { PUBLIC => :public, PRIVATE => :private, PROTECTED => :protected }.freeze
|
51
|
+
|
52
|
+
def self.visibility_name_from_index(visibility_index)
|
53
|
+
VISIBILITY_NAMES[visibility_index]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.private?(visibility); visibility == PRIVATE; end
|
57
|
+
def self.protected?(visibility); visibility == PROTECTED; end
|
58
|
+
end
|
59
|
+
|
6
60
|
def attributes_are_all_required?
|
7
61
|
false
|
8
62
|
end
|
@@ -11,15 +65,31 @@ module Micro
|
|
11
65
|
:indifferent
|
12
66
|
end
|
13
67
|
|
14
|
-
def
|
68
|
+
def __attributes_groups
|
69
|
+
@__attributes_groups ||= [
|
70
|
+
Set.new, # all
|
71
|
+
Set.new, # public
|
72
|
+
[], # private
|
73
|
+
[], # protected
|
74
|
+
Set.new, # required
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
def __attributes; __attributes_groups[Options::ALL]; end
|
79
|
+
|
80
|
+
def __attributes_public; __attributes_groups[Options::PUBLIC]; end
|
81
|
+
|
82
|
+
def __attributes_required__; __attributes_groups[Options::REQUIRED]; end
|
83
|
+
|
84
|
+
def __attribute_key_check__(value)
|
15
85
|
value
|
16
86
|
end
|
17
87
|
|
18
|
-
def
|
88
|
+
def __attribute_key_transform__(value)
|
19
89
|
value.to_s
|
20
90
|
end
|
21
91
|
|
22
|
-
def
|
92
|
+
def __attributes_keys_transform__(hash)
|
23
93
|
Utils::Hashes.stringify_keys(hash)
|
24
94
|
end
|
25
95
|
|
@@ -28,62 +98,97 @@ module Micro
|
|
28
98
|
@__attributes_data__ ||= {}
|
29
99
|
end
|
30
100
|
|
31
|
-
def
|
32
|
-
|
101
|
+
def __attribute_reader(name, visibility_index)
|
102
|
+
attr_reader(name)
|
103
|
+
|
104
|
+
__attributes.add(name)
|
105
|
+
__attributes_groups[visibility_index] << name
|
106
|
+
|
107
|
+
private(name) if Options.private?(visibility_index)
|
108
|
+
protected(name) if Options.protected?(visibility_index)
|
33
109
|
end
|
34
110
|
|
35
|
-
def __attributes_required_add(name,
|
36
|
-
if
|
111
|
+
def __attributes_required_add(name, opt, hasnt_default)
|
112
|
+
if opt[:required] || (attributes_are_all_required? && hasnt_default)
|
37
113
|
__attributes_required__.add(name)
|
38
114
|
end
|
39
115
|
|
40
116
|
nil
|
41
117
|
end
|
42
118
|
|
43
|
-
def __attributes_data_to_assign(name,
|
44
|
-
hasnt_default = !
|
119
|
+
def __attributes_data_to_assign(name, opt, visibility_index)
|
120
|
+
hasnt_default = !opt.key?(:default)
|
45
121
|
|
46
|
-
hasnt_default ? __attributes_required_add(name,
|
47
|
-
end
|
122
|
+
default = hasnt_default ? __attributes_required_add(name, opt, hasnt_default) : opt[:default]
|
48
123
|
|
49
|
-
|
50
|
-
|
124
|
+
[
|
125
|
+
default,
|
126
|
+
Options.for_accept(opt),
|
127
|
+
opt[:freeze],
|
128
|
+
Options.visibility_name_from_index(visibility_index)
|
129
|
+
]
|
51
130
|
end
|
52
131
|
|
53
|
-
def
|
54
|
-
__attributes.add(name)
|
132
|
+
def __call_after_attribute_assign__(attr_name, options); end
|
55
133
|
|
56
|
-
|
57
|
-
|
134
|
+
def __attribute_assign(key, can_overwrite, opt)
|
135
|
+
name = __attribute_key_check__(__attribute_key_transform__(key))
|
58
136
|
|
59
|
-
|
60
|
-
name = __attribute_access__(__attribute_key__(key))
|
61
|
-
has_attribute = attribute?(name)
|
137
|
+
Options.check(opt)
|
62
138
|
|
63
|
-
|
139
|
+
has_attribute = attribute?(name, true)
|
64
140
|
|
65
|
-
|
141
|
+
visibility_index = Options.visibility_index(opt)
|
66
142
|
|
67
|
-
|
68
|
-
end
|
143
|
+
__attribute_reader(name, visibility_index) unless has_attribute
|
69
144
|
|
70
|
-
|
145
|
+
if can_overwrite || !has_attribute
|
146
|
+
__attributes_data__[name] = __attributes_data_to_assign(name, opt, visibility_index)
|
147
|
+
end
|
148
|
+
|
149
|
+
__call_after_attribute_assign__(name, opt)
|
150
|
+
end
|
71
151
|
|
72
152
|
# NOTE: can't be renamed! It is used by u-case v4.
|
73
153
|
def __attributes_set_after_inherit__(arg)
|
74
154
|
arg.each do |key, val|
|
75
|
-
|
155
|
+
opt = {}
|
156
|
+
|
157
|
+
default = val[0]
|
158
|
+
accept_key, accept_val = val[1]
|
159
|
+
freeze, visibility = val[2], val[3]
|
160
|
+
|
161
|
+
opt[:default] = default if default
|
162
|
+
opt[accept_key] = accept_val if accept_key
|
163
|
+
opt[:freeze] = freeze if freeze
|
164
|
+
opt[visibility] = true if visibility != :public
|
165
|
+
|
166
|
+
__attribute_assign(key, true, opt || Kind::Empty::HASH)
|
76
167
|
end
|
77
168
|
end
|
78
169
|
|
79
|
-
def attribute?(name)
|
80
|
-
|
170
|
+
def attribute?(name, include_all = false)
|
171
|
+
key = __attribute_key_transform__(name)
|
172
|
+
|
173
|
+
return __attributes.member?(key) if include_all
|
174
|
+
|
175
|
+
__attributes_public.member?(key)
|
81
176
|
end
|
82
177
|
|
83
178
|
def attribute(name, options = Kind::Empty::HASH)
|
84
179
|
__attribute_assign(name, false, options)
|
85
180
|
end
|
86
181
|
|
182
|
+
RaiseKindError = ->(expected, given) do
|
183
|
+
if (util = Kind.const_get(:KIND, false)) && util.respond_to?(:error!)
|
184
|
+
util.error!(expected, given)
|
185
|
+
else
|
186
|
+
raise Kind::Error.new(expected, given, label: nil)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
private_constant :RaiseKindError
|
191
|
+
|
87
192
|
def attributes(*args)
|
88
193
|
return __attributes.to_a if args.empty?
|
89
194
|
|
@@ -96,11 +201,19 @@ module Micro
|
|
96
201
|
if arg.is_a?(String) || arg.is_a?(Symbol)
|
97
202
|
__attribute_assign(arg, false, options)
|
98
203
|
else
|
99
|
-
|
204
|
+
RaiseKindError.call('String/Symbol'.freeze, arg)
|
100
205
|
end
|
101
206
|
end
|
102
207
|
end
|
103
208
|
|
209
|
+
def attributes_by_visibility
|
210
|
+
{
|
211
|
+
public: __attributes_groups[Options::PUBLIC].to_a,
|
212
|
+
private: __attributes_groups[Options::PRIVATE].dup,
|
213
|
+
protected: __attributes_groups[Options::PROTECTED].dup
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
104
217
|
# NOTE: can't be renamed! It is used by u-case v4.
|
105
218
|
module ForSubclasses
|
106
219
|
WRONG_NUMBER_OF_ARGS = 'wrong number of arguments (given 0, expected 1 or more)'.freeze
|
@@ -112,7 +225,7 @@ module Micro
|
|
112
225
|
private_constant :WRONG_NUMBER_OF_ARGS
|
113
226
|
end
|
114
227
|
|
115
|
-
private_constant :ForSubclasses
|
228
|
+
private_constant :Options, :ForSubclasses
|
116
229
|
end
|
117
230
|
|
118
231
|
private_constant :Macros
|