u-attributes 2.1.1 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
 
@@ -18,15 +50,27 @@ module Micro::Attributes
18
50
  validate, validates = options.values_at(:validate, :validates)
19
51
 
20
52
  self.validate(validate) if validate
21
- self.validates(attr_name, validates) if validates
53
+ self.validates(attr_name, validates.dup) if validates
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::Of.(::Symbol, value)
14
+ end
15
+
16
+ def __attribute_key_transform__(value)
17
+ value
18
+ end
19
+
20
+ def __attributes_keys_transform__(hash)
21
+ Utils::Hashes.kind(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)
@@ -72,15 +183,27 @@ module Micro
72
183
  return __attributes.to_a if args.empty?
73
184
 
74
185
  args.flatten!
186
+
187
+ options =
188
+ args.size > 1 && args.last.is_a?(::Hash) ? args.pop : Kind::Empty::HASH
189
+
75
190
  args.each do |arg|
76
191
  if arg.is_a?(String) || arg.is_a?(Symbol)
77
- __attribute_assign(arg, false, Kind::Empty::HASH)
192
+ __attribute_assign(arg, false, options)
78
193
  else
79
194
  raise Kind::Error.new('String/Symbol'.freeze, arg)
80
195
  end
81
196
  end
82
197
  end
83
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
+
84
207
  # NOTE: can't be renamed! It is used by u-case v4.
85
208
  module ForSubclasses
86
209
  WRONG_NUMBER_OF_ARGS = 'wrong number of arguments (given 0, expected 1 or more)'.freeze
@@ -92,7 +215,7 @@ module Micro
92
215
  private_constant :WRONG_NUMBER_OF_ARGS
93
216
  end
94
217
 
95
- private_constant :ForSubclasses
218
+ private_constant :Options, :ForSubclasses
96
219
  end
97
220
 
98
221
  private_constant :Macros
@@ -1,25 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Micro
4
- module Attributes
5
- module Utils
6
- def self.stringify_hash_keys(arg)
7
- hash = Kind::Of.(::Hash, arg)
3
+ module Micro::Attributes
4
+ module Utils
5
+ module Hashes
6
+ def self.kind(hash)
7
+ Kind::Of.(::Hash, hash)
8
+ end
9
+
10
+ def self.stringify_keys(arg)
11
+ hash = kind(arg)
8
12
 
9
13
  return hash if hash.empty?
14
+ return hash.transform_keys(&:to_s) if hash.respond_to?(:transform_keys)
10
15
 
11
- if hash.respond_to?(:transform_keys)
12
- hash.transform_keys { |key| key.to_s }
13
- else
14
- hash.each_with_object({}) { |(key, val), memo| memo[key.to_s] = val }
15
- end
16
+ hash.each_with_object({}) { |(key, val), memo| memo[key.to_s] = val }
17
+ end
18
+
19
+ def self.symbolize_keys(arg)
20
+ hash = kind(arg)
21
+
22
+ return hash if hash.empty?
23
+ return hash.transform_keys(&:to_sym) if hash.respond_to?(:transform_keys)
24
+
25
+ hash.each_with_object({}) { |(key, val), memo| memo[key.to_sym] = val }
26
+ end
27
+
28
+ def self.keys_as(type, hash)
29
+ return kind(hash) unless type
30
+
31
+ return symbolize_keys(hash) if type == Symbol || type == :symbol
32
+ return stringify_keys(hash) if type == String || type == :string
33
+
34
+ raise ArgumentError, 'argument must be one of these values: :symbol, :string, Symbol, String'.freeze
35
+ end
36
+
37
+ def self.assoc(hash, key)
38
+ value = hash[key.to_s]
39
+
40
+ value.nil? ? hash[key.to_sym] : value
16
41
  end
42
+ end
43
+
44
+ module ExtractAttribute
45
+ def self.call(object, key:)
46
+ return object.public_send(key) if object.respond_to?(key)
17
47
 
18
- HashAccess = -> (hash, key) {
19
- return hash[key.to_s] unless hash[key.to_s].nil?
48
+ Hashes.assoc(object, key) if object.respond_to?(:[])
49
+ end
20
50
 
21
- hash[key.to_sym]
22
- }
51
+ def self.from(object, keys:)
52
+ Kind::Of.(::Array, keys).each_with_object({}) do |key, memo|
53
+ memo[key] = call(object, key: key)
54
+ end
55
+ end
23
56
  end
24
57
  end
25
58
  end