u-attributes 2.2.0 → 2.7.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.
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Attributes
4
+ module Features
5
+ module Accept
6
+ def attributes_errors
7
+ @__attributes_errors
8
+ end
9
+
10
+ def attributes_errors?
11
+ !@__attributes_errors.empty?
12
+ end
13
+
14
+ def rejected_attributes
15
+ @__rejected_attributes ||= attributes_errors.keys
16
+ end
17
+
18
+ def accepted_attributes
19
+ @__accepted_attributes ||= defined_attributes - rejected_attributes
20
+ end
21
+
22
+ def rejected_attributes?
23
+ attributes_errors?
24
+ end
25
+
26
+ def accepted_attributes?
27
+ !rejected_attributes?
28
+ end
29
+
30
+ private
31
+
32
+ def __call_before_attributes_assign
33
+ @__attributes_errors = {}
34
+ end
35
+
36
+ KeepProc = -> validation_data { validation_data[0] == :accept && validation_data[1] == Proc }
37
+
38
+ def __attribute_assign(key, initialize_value, attribute_data)
39
+ validation_data = attribute_data[1]
40
+
41
+ value_to_assign = FetchValueToAssign.(initialize_value, attribute_data, KeepProc.(validation_data))
42
+
43
+ value = __attributes[key] = instance_variable_set("@#{key}", value_to_assign)
44
+
45
+ __attribute_accept_or_reject(key, value, validation_data) if !validation_data.empty?
46
+ end
47
+
48
+ def __attribute_accept_or_reject(key, value, validation_data)
49
+ context = Context.with(key, value, validation_data)
50
+
51
+ error_msg = context.rejection_message(Validate.call(context))
52
+
53
+ @__attributes_errors[key] = error_msg if error_msg
54
+ end
55
+
56
+ Context = Struct.new(:key, :value, :validation, :expected, :allow_nil, :rejection) do
57
+ def self.with(key, value, data)
58
+ new(key, value, data[0], data[1], data[2], data[3])
59
+ end
60
+
61
+ def allow_nil?
62
+ allow_nil && value.nil?
63
+ end
64
+
65
+ def accept?
66
+ validation == :accept
67
+ end
68
+
69
+ def rejection_message(default_msg)
70
+ return unless default_msg
71
+
72
+ return default_msg unless rejection || expected.respond_to?(:rejection_message)
73
+
74
+ rejection_msg = rejection || expected.rejection_message
75
+
76
+ return rejection_msg unless rejection_msg.is_a?(Proc)
77
+
78
+ rejection_msg.arity == 0 ? rejection_msg.call : rejection_msg.call(key)
79
+ end
80
+ end
81
+
82
+ module Validate
83
+ module Callable
84
+ MESSAGE = 'is invalid'.freeze
85
+
86
+ def self.call?(exp); exp.respond_to?(:call); end
87
+ def self.call(exp, val); exp.call(val); end
88
+ def self.accept_failed(_exp); MESSAGE; end
89
+ def self.reject_failed(_exp); MESSAGE; end
90
+ end
91
+
92
+ module KindOf
93
+ def self.call?(exp); exp.is_a?(Class) || exp.is_a?(Module); end
94
+ def self.call(exp, val); val.kind_of?(exp); end
95
+ def self.accept_failed(exp); "expected to be a kind of #{exp}"; end
96
+ def self.reject_failed(exp); "expected to not be a kind of #{exp}"; end
97
+ end
98
+
99
+ module Predicate
100
+ QUESTION_MARK = '?'.freeze
101
+
102
+ def self.call?(exp); exp.is_a?(Symbol) && exp.to_s.end_with?(QUESTION_MARK); end
103
+ def self.call(exp, val); val.public_send(exp); end
104
+ def self.accept_failed(exp); "expected to be #{exp}"; end
105
+ def self.reject_failed(exp); "expected to not be #{exp}"; end
106
+ end
107
+
108
+ def self.with(expected)
109
+ return Callable if Callable.call?(expected)
110
+ return KindOf if KindOf.call?(expected)
111
+ return Predicate if Predicate.call?(expected)
112
+ end
113
+
114
+ def self.call(context)
115
+ return if context.allow_nil?
116
+
117
+ validate = self.with(expected = context.expected)
118
+
119
+ return unless validate
120
+
121
+ truthy = validate.call(expected, context.value)
122
+
123
+ return truthy ? nil : validate.accept_failed(expected) if context.accept?
124
+
125
+ validate.reject_failed(expected) if truthy
126
+ end
127
+ end
128
+
129
+ private_constant :KeepProc, :Context, :Validate
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Attributes
4
+ module Features
5
+ module Accept
6
+
7
+ module Strict
8
+ ATTRIBUTES_REJECTED = "One or more attributes were rejected. Errors:\n".freeze
9
+
10
+ def __call_after_attributes_assign
11
+ return unless attributes_errors?
12
+
13
+ __raise_error_if_found_attributes_errors
14
+ end
15
+
16
+ def __raise_error_if_found_attributes_errors
17
+ raise ArgumentError, [
18
+ ATTRIBUTES_REJECTED,
19
+ attributes_errors.map { |key, msg| "* #{key.inspect} #{msg}" }.join("\n")
20
+ ].join
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -3,13 +3,45 @@
3
3
  module Micro::Attributes
4
4
  module Features
5
5
  module ActiveModelValidations
6
- def self.included(base)
7
- begin
8
- require 'active_model'
6
+ module Standard
7
+ private def __call_after_attributes_assign
8
+ run_validations!
9
+ end
10
+ end
9
11
 
10
- base.send(:include, ::ActiveModel::Validations)
11
- base.extend(ClassMethods)
12
- rescue LoadError
12
+ module CheckActivemodelValidationErrors
13
+ private def __check_activemodel_validation_errors
14
+ return if errors.blank?
15
+
16
+ errors_hash = errors.to_hash
17
+
18
+ defined_attributes.each do |key|
19
+ value = Utils::Hashes.assoc(errors_hash, key)
20
+
21
+ @__attributes_errors[key] = value.join(', ') if value.present?
22
+ end
23
+ end
24
+ end
25
+
26
+ module WithAccept
27
+ include CheckActivemodelValidationErrors
28
+
29
+ private def __call_after_attributes_assign
30
+ run_validations! unless attributes_errors?
31
+
32
+ __check_activemodel_validation_errors
33
+ end
34
+ end
35
+
36
+ module WithAcceptStrict
37
+ include CheckActivemodelValidationErrors
38
+
39
+ private def __call_after_attributes_assign
40
+ __raise_error_if_found_attributes_errors if attributes_errors?
41
+
42
+ run_validations!
43
+
44
+ __check_activemodel_validation_errors
13
45
  end
14
46
  end
15
47
 
@@ -22,11 +54,23 @@ module Micro::Attributes
22
54
  end
23
55
  end
24
56
 
25
- private
57
+ def self.included(base)
58
+ begin
59
+ require 'active_model'
60
+
61
+ base.send(:include, ::ActiveModel::Validations)
62
+ base.extend(ClassMethods)
26
63
 
27
- def __call_after_micro_attribute
28
- run_validations! if respond_to?(:run_validations!, true)
64
+ case
65
+ when base <= Features::Accept::Strict then base.send(:include, WithAcceptStrict)
66
+ when base <= Features::Accept then base.send(:include, WithAccept)
67
+ else base.send(:include, Standard)
68
+ end
69
+ rescue LoadError
29
70
  end
71
+ end
72
+
73
+ private_constant :Standard, :CheckActivemodelValidationErrors, :WithAccept, :WithAcceptStrict
30
74
  end
31
75
  end
32
76
  end
@@ -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
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Attributes
4
+ module Features
5
+ module KeysAsSymbol
6
+
7
+ module ClassMethods
8
+ def attributes_access
9
+ :symbol
10
+ end
11
+
12
+ def __attribute_key_check__(value)
13
+ Kind::Symbol[value]
14
+ end
15
+
16
+ def __attribute_key_transform__(value)
17
+ value
18
+ end
19
+
20
+ def __attributes_keys_transform__(hash)
21
+ Kind::Hash[hash]
22
+ end
23
+ end
24
+
25
+ def self.included(base)
26
+ base.send(:extend, ClassMethods)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -3,65 +3,176 @@
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
9
63
 
64
+ def attributes_access
65
+ :indifferent
66
+ end
67
+
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)
85
+ value
86
+ end
87
+
88
+ def __attribute_key_transform__(value)
89
+ value.to_s
90
+ end
91
+
92
+ def __attributes_keys_transform__(hash)
93
+ Utils::Hashes.stringify_keys(hash)
94
+ end
95
+
10
96
  # NOTE: can't be renamed! It is used by u-case v4.
11
97
  def __attributes_data__
12
98
  @__attributes_data__ ||= {}
13
99
  end
14
100
 
15
- def __attributes_required__
16
- @__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)
17
109
  end
18
110
 
19
- def __attributes_required_add(name, is_required, hasnt_default)
20
- 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)
21
113
  __attributes_required__.add(name)
22
114
  end
23
115
 
24
116
  nil
25
117
  end
26
118
 
27
- def __attributes_data_to_assign(name, options)
28
- hasnt_default = !options.key?(:default)
119
+ def __attributes_data_to_assign(name, opt, visibility_index)
120
+ hasnt_default = !opt.key?(:default)
29
121
 
30
- hasnt_default ? __attributes_required_add(name, options[:required], hasnt_default) : options[:default]
31
- end
122
+ default = hasnt_default ? __attributes_required_add(name, opt, hasnt_default) : opt[:default]
32
123
 
33
- def __attributes
34
- @__attributes ||= Set.new
124
+ [
125
+ default,
126
+ Options.for_accept(opt),
127
+ opt[:freeze],
128
+ Options.visibility_name_from_index(visibility_index)
129
+ ]
35
130
  end
36
131
 
37
- def __attribute_reader(name)
38
- __attributes.add(name)
132
+ def __call_after_attribute_assign__(attr_name, options); end
39
133
 
40
- attr_reader(name)
41
- end
134
+ def __attribute_assign(key, can_overwrite, opt)
135
+ name = __attribute_key_check__(__attribute_key_transform__(key))
42
136
 
43
- def __attribute_assign(key, can_overwrite, options)
44
- name = key.to_s
45
- has_attribute = attribute?(name)
137
+ Options.check(opt)
46
138
 
47
- __attribute_reader(name) unless has_attribute
139
+ has_attribute = attribute?(name, true)
48
140
 
49
- __attributes_data__[name] = __attributes_data_to_assign(name, options) if can_overwrite || !has_attribute
141
+ visibility_index = Options.visibility_index(opt)
50
142
 
51
- __call_after_attribute_assign__(name, options)
52
- end
143
+ __attribute_reader(name, visibility_index) unless has_attribute
53
144
 
54
- 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
55
151
 
56
152
  # NOTE: can't be renamed! It is used by u-case v4.
57
153
  def __attributes_set_after_inherit__(arg)
58
154
  arg.each do |key, val|
59
- __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)
60
167
  end
61
168
  end
62
169
 
63
- def attribute?(name)
64
- __attributes.member?(name.to_s)
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)
65
176
  end
66
177
 
67
178
  def attribute(name, options = Kind::Empty::HASH)
@@ -80,11 +191,19 @@ module Micro
80
191
  if arg.is_a?(String) || arg.is_a?(Symbol)
81
192
  __attribute_assign(arg, false, options)
82
193
  else
83
- raise Kind::Error.new('String/Symbol'.freeze, arg)
194
+ Kind::KIND.error!('String/Symbol'.freeze, arg)
84
195
  end
85
196
  end
86
197
  end
87
198
 
199
+ def attributes_by_visibility
200
+ {
201
+ public: __attributes_groups[Options::PUBLIC].to_a,
202
+ private: __attributes_groups[Options::PRIVATE].dup,
203
+ protected: __attributes_groups[Options::PROTECTED].dup
204
+ }
205
+ end
206
+
88
207
  # NOTE: can't be renamed! It is used by u-case v4.
89
208
  module ForSubclasses
90
209
  WRONG_NUMBER_OF_ARGS = 'wrong number of arguments (given 0, expected 1 or more)'.freeze
@@ -96,7 +215,7 @@ module Micro
96
215
  private_constant :WRONG_NUMBER_OF_ARGS
97
216
  end
98
217
 
99
- private_constant :ForSubclasses
218
+ private_constant :Options, :ForSubclasses
100
219
  end
101
220
 
102
221
  private_constant :Macros