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.
@@ -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
- private
24
-
25
- def __call_after_micro_attribute; end
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 __attribute_access__(value)
13
- Kind::Of.(::Symbol, value)
12
+ def __attribute_key_check__(value)
13
+ Kind::Symbol[value]
14
14
  end
15
15
 
16
- def __attribute_key__(value)
16
+ def __attribute_key_transform__(value)
17
17
  value
18
18
  end
19
19
 
20
- def __attributes_keys__(hash)
21
- Utils::Hashes.kind(hash)
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 = 'Init'.freeze,
23
- INIT_STRICT = 'InitStrict'.freeze,
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 = 'AMValidations'.freeze
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
- KEYS_TO_MODULES = {
36
- DIFF => With::Diff,
37
- INIT => With::Initialize,
38
- INIT_STRICT => With::StrictInitialize,
39
- KEYS_AS_SYMBOL => With::KeysAsSymbol,
40
- AM_VALIDATIONS => With::ActiveModelValidations
41
- }.freeze
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
- name = String(arg)
95
+ str = String(arg)
48
96
 
49
- return name if KEYS_TO_MODULES.key?(name)
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.remove_init_keys(keys, if_has_init_in:)
69
- keys.delete_if { |key| key == INIT || key == INIT_STRICT } if if_has_init_in.include?(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).tap do |keys|
74
- remove_init_keys(keys, if_has_init_in: keys_to_exclude)
75
- end
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(keys)
79
- keys.delete_if { |key| key == INIT } if keys.include?(INIT_STRICT)
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(option) { With.const_get(option, false) }
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
- keys = Options.without_keys(keys)
152
+ keys_to_fetch = Options.without_keys(keys)
100
153
 
101
- keys.empty? ? ::Micro::Attributes : Options.fetch_module_by_keys(keys)
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 __attribute_access__(value)
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 __attribute_key__(value)
88
+ def __attribute_key_transform__(value)
19
89
  value.to_s
20
90
  end
21
91
 
22
- def __attributes_keys__(hash)
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 __attributes_required__
32
- @__attributes_required__ ||= Set.new
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, is_required, hasnt_default)
36
- if is_required || (attributes_are_all_required? && hasnt_default)
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, options)
44
- hasnt_default = !options.key?(: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, options[:required], hasnt_default) : options[:default]
47
- end
122
+ default = hasnt_default ? __attributes_required_add(name, opt, hasnt_default) : opt[:default]
48
123
 
49
- def __attributes
50
- @__attributes ||= Set.new
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 __attribute_reader(name)
54
- __attributes.add(name)
132
+ def __call_after_attribute_assign__(attr_name, options); end
55
133
 
56
- attr_reader(name)
57
- end
134
+ def __attribute_assign(key, can_overwrite, opt)
135
+ name = __attribute_key_check__(__attribute_key_transform__(key))
58
136
 
59
- def __attribute_assign(key, can_overwrite, options)
60
- name = __attribute_access__(__attribute_key__(key))
61
- has_attribute = attribute?(name)
137
+ Options.check(opt)
62
138
 
63
- __attribute_reader(name) unless has_attribute
139
+ has_attribute = attribute?(name, true)
64
140
 
65
- __attributes_data__[name] = __attributes_data_to_assign(name, options) if can_overwrite || !has_attribute
141
+ visibility_index = Options.visibility_index(opt)
66
142
 
67
- __call_after_attribute_assign__(name, options)
68
- end
143
+ __attribute_reader(name, visibility_index) unless has_attribute
69
144
 
70
- def __call_after_attribute_assign__(attr_name, options); end
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
- __attribute_assign(key, true, val ? { default: val } : {})
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
- __attributes.member?(__attribute_key__(name))
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
- raise Kind::Error.new('String/Symbol'.freeze, arg)
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