u-attributes 2.4.0 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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