metaractor 2.1.1 → 3.0.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/Gemfile +1 -0
- data/README.md +89 -8
- data/lib/metaractor/context_errors.rb +10 -10
- data/lib/metaractor/errors.rb +130 -9
- data/lib/metaractor/failure_output.rb +2 -14
- data/lib/metaractor/handle_errors.rb +8 -8
- data/lib/metaractor/namespace.rb +46 -0
- data/lib/metaractor/parameters.rb +125 -23
- data/lib/metaractor/version.rb +1 -1
- data/lib/metaractor.rb +24 -1
- data/metaractor.gemspec +3 -0
- metadata +45 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67f974131ea1d40f037b6bd29173092b95ab31fbe2263d4274d8dd614ba0483c
|
4
|
+
data.tar.gz: 25bfc2e214f0b8da6fe6964a8426989454ea244d841138b341b1a70f4ec2e5b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0fbacb1db9083b9c596f35d9e8eaaabe54fef7c8ede4ac3ad2f92b0b086efec4c55ced03caa0211325344a3242006d6aea0e0b72e96d16aada1b929168f2e7d
|
7
|
+
data.tar.gz: 307a426d305331d9730b6b12eba50ff0703706e53b7b8235e9082e7d5ddf910644263662465462f838c5c79b0c439858dd03daa19afd74e813bd2bc3c05b6d03
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -44,7 +44,7 @@ result.failure?
|
|
44
44
|
# => true
|
45
45
|
result.valid?
|
46
46
|
# => false
|
47
|
-
result.
|
47
|
+
result.error_messages
|
48
48
|
# => ["Required parameters: (user_id or user)"]
|
49
49
|
```
|
50
50
|
|
@@ -68,20 +68,41 @@ Metaractor supports complex required parameter statements and you can chain thes
|
|
68
68
|
required and: [:token, or: [:recipient_id, :recipient] ]
|
69
69
|
```
|
70
70
|
|
71
|
+
You can also mark a parameter as required with the `required` option:
|
72
|
+
```ruby
|
73
|
+
parameter :user, required: true
|
74
|
+
```
|
75
|
+
|
71
76
|
### Optional Parameters
|
72
77
|
As optional parameters have no enforcement, they are merely advisory.
|
73
78
|
```ruby
|
74
79
|
optional :enable_logging
|
75
80
|
```
|
76
81
|
|
77
|
-
###
|
82
|
+
### Parameter Options
|
83
|
+
Metaractor supports arbitrary parameter options. The following are currently built in.
|
84
|
+
Note that you can specify a block of `required` or `optional` parameters and then use
|
85
|
+
`parameter` or `parameters` to add options to one or more of them.
|
86
|
+
|
87
|
+
#### Skipping Blank Parameter Removal
|
78
88
|
By default Metaractor removes blank values that are passed in. You may skip this behavior on a per-parameter basis:
|
79
89
|
```ruby
|
80
|
-
|
90
|
+
parameter :name, allow_blank: true
|
81
91
|
```
|
82
92
|
|
83
93
|
You may check to see if a parameter exists via `context.has_key?`.
|
84
94
|
|
95
|
+
#### Default Values
|
96
|
+
You can specify a default value for a parameter:
|
97
|
+
```ruby
|
98
|
+
optional :role, default: :user
|
99
|
+
```
|
100
|
+
|
101
|
+
This works with `allow_blank` and can also be anything that responds to `#call`.
|
102
|
+
```ruby
|
103
|
+
parameter :role, allow_blank: true, default: -> { context.default_role }
|
104
|
+
```
|
105
|
+
|
85
106
|
### Custom Validation
|
86
107
|
Metaractor supports doing custom validation before any user supplied before_hooks run.
|
87
108
|
```ruby
|
@@ -161,6 +182,64 @@ result.errors.to_h
|
|
161
182
|
# }
|
162
183
|
```
|
163
184
|
|
185
|
+
### I18n
|
186
|
+
As of v3.0.0, metaractor supports i18n along with structured errors.
|
187
|
+
```ruby
|
188
|
+
module Users
|
189
|
+
class UpdateUser
|
190
|
+
include Metaractor
|
191
|
+
|
192
|
+
optional :is_admin
|
193
|
+
optional :user
|
194
|
+
|
195
|
+
def call
|
196
|
+
fail_with_error!(
|
197
|
+
errors: {
|
198
|
+
base: :invalid_configuration,
|
199
|
+
is_admin: :true_or_false,
|
200
|
+
user: [ title: :blank, username: [:unique, :blank] ]
|
201
|
+
}
|
202
|
+
)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
Locale:
|
209
|
+
```yaml
|
210
|
+
en:
|
211
|
+
errors:
|
212
|
+
parameters:
|
213
|
+
invalid_configuration: 'Invalid configuration'
|
214
|
+
blank: '%{parameter} cannot be blank'
|
215
|
+
unique: '%{parameter} must be unique'
|
216
|
+
|
217
|
+
users:
|
218
|
+
is_admin:
|
219
|
+
true_or_false: 'must be true or false'
|
220
|
+
user:
|
221
|
+
username:
|
222
|
+
unique: 'Username has already been taken'
|
223
|
+
```
|
224
|
+
|
225
|
+
Metaractor will attempt to use the namespace of the code that reported the error.
|
226
|
+
You can see that above with the `users` key in the locale.
|
227
|
+
|
228
|
+
The i18n integration will walk its way from the most specific message to the least specific one, stopping at the first one it can find.
|
229
|
+
We currently expose the following variables for use in the message:
|
230
|
+
- `error_key`: the error we added (ex: `blank` or `invalid_configuration`)
|
231
|
+
- `parameter`: the name of the parameter
|
232
|
+
|
233
|
+
You can also use this feature to work with machine readable keys:
|
234
|
+
```ruby
|
235
|
+
result = Users::UpdateUser.call
|
236
|
+
if result.failure? &&
|
237
|
+
result.errors[:is_admin] == :true_or_false
|
238
|
+
|
239
|
+
# handle this specific case
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
164
243
|
### Spec Helpers
|
165
244
|
Enable the helpers and/or matchers:
|
166
245
|
```ruby
|
@@ -221,8 +300,8 @@ expect(result).to include_errors(
|
|
221
300
|
expect(result).to include_errors('user.title cannot be blank')
|
222
301
|
```
|
223
302
|
|
224
|
-
###
|
225
|
-
Metaractor customizes the
|
303
|
+
### Hash Formatting
|
304
|
+
Metaractor customizes the output for `Metaractor::Errors#inspect` and `Interactor::Failure`:
|
226
305
|
```
|
227
306
|
Interactor::Failure:
|
228
307
|
Errors:
|
@@ -235,10 +314,12 @@ Interactor::Failure:
|
|
235
314
|
{:parent=>true, :chained=>true}
|
236
315
|
```
|
237
316
|
|
238
|
-
You can further customize the
|
317
|
+
You can further customize the hash formatting:
|
239
318
|
```ruby
|
240
|
-
|
241
|
-
|
319
|
+
Metaractor.configure do |config|
|
320
|
+
# Configure Metaractor to use awesome_print
|
321
|
+
config.hash_formatter = ->(hash) { hash.ai }
|
322
|
+
end
|
242
323
|
```
|
243
324
|
|
244
325
|
### Further Reading
|
@@ -8,29 +8,29 @@ module Metaractor
|
|
8
8
|
super
|
9
9
|
end
|
10
10
|
|
11
|
-
def fail_with_error!(message: nil, errors: nil)
|
12
|
-
add_error(message: message, errors: errors)
|
11
|
+
def fail_with_error!(message: nil, errors: nil, **args)
|
12
|
+
add_error(message: message, errors: errors, **args)
|
13
13
|
fail!
|
14
14
|
end
|
15
15
|
|
16
|
-
def fail_with_errors!(messages: [], errors: {})
|
17
|
-
add_errors(messages: messages, errors: errors)
|
16
|
+
def fail_with_errors!(messages: [], errors: {}, **args)
|
17
|
+
add_errors(messages: messages, errors: errors, **args)
|
18
18
|
fail!
|
19
19
|
end
|
20
20
|
|
21
|
-
def add_error(message: nil, errors: nil)
|
21
|
+
def add_error(message: nil, errors: nil, **args)
|
22
22
|
if message.nil?
|
23
|
-
add_errors(errors: errors)
|
23
|
+
add_errors(errors: errors, **args)
|
24
24
|
else
|
25
|
-
add_errors(messages: Array(message))
|
25
|
+
add_errors(messages: Array(message), **args)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
def add_errors(messages: [], errors: {})
|
29
|
+
def add_errors(messages: [], errors: {}, **args)
|
30
30
|
if !messages.empty?
|
31
|
-
self.errors.add(errors: { base: messages })
|
31
|
+
self.errors.add(errors: { base: messages }, **args)
|
32
32
|
else
|
33
|
-
self.errors.add(errors: errors)
|
33
|
+
self.errors.add(errors: errors, **args)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
data/lib/metaractor/errors.rb
CHANGED
@@ -4,13 +4,72 @@ module Metaractor
|
|
4
4
|
class Errors
|
5
5
|
extend Forwardable
|
6
6
|
|
7
|
+
class Error
|
8
|
+
attr_reader :value, :object
|
9
|
+
|
10
|
+
def initialize(value:, object: nil)
|
11
|
+
@value = value
|
12
|
+
@object = object
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_message(path_elements:)
|
16
|
+
if @value.is_a? Symbol
|
17
|
+
defaults = []
|
18
|
+
|
19
|
+
if object.class.respond_to?(:i18n_parent_names) &&
|
20
|
+
!object.class.i18n_parent_names.empty?
|
21
|
+
|
22
|
+
names = object.class.i18n_parent_names
|
23
|
+
until names.empty?
|
24
|
+
defaults << ['errors', names.join('.'), 'parameters', path_elements.join('.'), @value.to_s].reject do |item|
|
25
|
+
item.nil? || item == ''
|
26
|
+
end.join('.').to_sym
|
27
|
+
names.pop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
unless path_elements.empty?
|
32
|
+
defaults << :"errors.parameters.#{path_elements.join('.')}.#{@value}"
|
33
|
+
end
|
34
|
+
defaults << :"errors.parameters.#{@value}"
|
35
|
+
|
36
|
+
key = defaults.shift
|
37
|
+
I18n.translate(
|
38
|
+
key,
|
39
|
+
default: defaults,
|
40
|
+
error_key: @value,
|
41
|
+
parameter: path_elements.last
|
42
|
+
)
|
43
|
+
else
|
44
|
+
"#{path_elements.join('.')} #{@value}".lstrip
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==(other)
|
49
|
+
if other.is_a?(self.class)
|
50
|
+
@value == other.value
|
51
|
+
else
|
52
|
+
@value == other
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias eql? ==
|
56
|
+
|
57
|
+
def hash
|
58
|
+
@value.hash
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"(Error) #{@value.inspect}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
7
66
|
def initialize
|
8
67
|
@tree = Sycamore::Tree.new
|
9
68
|
end
|
10
69
|
|
11
|
-
def_delegators :@tree, :
|
70
|
+
def_delegators :@tree, :empty?
|
12
71
|
|
13
|
-
def add(error: {}, errors: {})
|
72
|
+
def add(error: {}, errors: {}, object: nil)
|
14
73
|
trees = []
|
15
74
|
[error, errors].each do |h|
|
16
75
|
tree = nil
|
@@ -29,7 +88,17 @@ module Metaractor
|
|
29
88
|
end
|
30
89
|
|
31
90
|
trees.each do |tree|
|
32
|
-
|
91
|
+
tree.each_path do |path|
|
92
|
+
node = path.node
|
93
|
+
unless node.is_a?(Error)
|
94
|
+
node = Error.new(
|
95
|
+
value: path.node,
|
96
|
+
object: object
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
@tree[path.parent] << node
|
101
|
+
end
|
33
102
|
end
|
34
103
|
@tree.compact
|
35
104
|
end
|
@@ -58,9 +127,9 @@ module Metaractor
|
|
58
127
|
result = @tree.dig(*path)
|
59
128
|
|
60
129
|
if result.strict_leaves?
|
61
|
-
result.nodes
|
130
|
+
unwrapped_enum(result.nodes)
|
62
131
|
else
|
63
|
-
result.to_h
|
132
|
+
unwrapped_tree(result).to_h
|
64
133
|
end
|
65
134
|
end
|
66
135
|
alias [] dig
|
@@ -68,9 +137,15 @@ module Metaractor
|
|
68
137
|
def include?(*elements)
|
69
138
|
if elements.size == 1 &&
|
70
139
|
elements.first.is_a?(Hash)
|
71
|
-
|
140
|
+
unwrapped_tree.include?(*elements)
|
72
141
|
else
|
73
|
-
|
142
|
+
if elements.all? {|e| e.is_a? String }
|
143
|
+
full_messages.include?(*elements)
|
144
|
+
else
|
145
|
+
elements.all? do |element|
|
146
|
+
@tree.include_path?(element)
|
147
|
+
end
|
148
|
+
end
|
74
149
|
end
|
75
150
|
end
|
76
151
|
|
@@ -83,7 +158,28 @@ module Metaractor
|
|
83
158
|
end
|
84
159
|
end
|
85
160
|
|
86
|
-
new_tree.to_h
|
161
|
+
unwrapped_tree(new_tree).to_h
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_h(unwrap: true)
|
165
|
+
if unwrap
|
166
|
+
unwrapped_tree.to_h
|
167
|
+
else
|
168
|
+
@tree.to_h
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def inspect
|
173
|
+
str = "<##{self.class.name}: "
|
174
|
+
|
175
|
+
if !self.empty?
|
176
|
+
str << "Errors:\n"
|
177
|
+
str << Metaractor.format_hash(to_h(unwrap: false))
|
178
|
+
str << "\n"
|
179
|
+
end
|
180
|
+
|
181
|
+
str << ">"
|
182
|
+
str
|
87
183
|
end
|
88
184
|
|
89
185
|
private
|
@@ -96,7 +192,32 @@ module Metaractor
|
|
96
192
|
end
|
97
193
|
end
|
98
194
|
|
99
|
-
|
195
|
+
path.node.generate_message(path_elements: path_elements)
|
100
196
|
end
|
197
|
+
|
198
|
+
def unwrapped_tree(orig_tree = @tree)
|
199
|
+
tree = Sycamore::Tree.new
|
200
|
+
orig_tree.each_path do |path|
|
201
|
+
node = path.node
|
202
|
+
if node.is_a? Error
|
203
|
+
node = node.value
|
204
|
+
end
|
205
|
+
|
206
|
+
tree[path.parent] << node
|
207
|
+
end
|
208
|
+
|
209
|
+
tree
|
210
|
+
end
|
211
|
+
|
212
|
+
def unwrapped_enum(orig)
|
213
|
+
orig.map do |element|
|
214
|
+
if element.is_a? Error
|
215
|
+
element.value
|
216
|
+
else
|
217
|
+
element
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
101
222
|
end
|
102
223
|
end
|
@@ -1,23 +1,11 @@
|
|
1
1
|
module Metaractor
|
2
2
|
module FailureOutput
|
3
|
-
def self.format_hash(hash)
|
4
|
-
if @hash_formatter.nil?
|
5
|
-
@hash_formatter = ->(hash){ hash.inspect }
|
6
|
-
end
|
7
|
-
|
8
|
-
@hash_formatter.call(hash)
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.hash_formatter=(callable)
|
12
|
-
@hash_formatter = callable
|
13
|
-
end
|
14
|
-
|
15
3
|
def to_s
|
16
4
|
str = ''
|
17
5
|
|
18
6
|
if !context.errors.empty?
|
19
7
|
str << "Errors:\n"
|
20
|
-
str << Metaractor
|
8
|
+
str << Metaractor.format_hash(context.errors.to_h)
|
21
9
|
str << "\n\n"
|
22
10
|
end
|
23
11
|
|
@@ -31,7 +19,7 @@ module Metaractor
|
|
31
19
|
end
|
32
20
|
|
33
21
|
str << "Context:\n"
|
34
|
-
str << Metaractor
|
22
|
+
str << Metaractor.format_hash(context.to_h.reject{|k,_| k == :errors})
|
35
23
|
str
|
36
24
|
end
|
37
25
|
end
|
@@ -3,20 +3,20 @@ module Metaractor
|
|
3
3
|
class InvalidError < Error; end
|
4
4
|
|
5
5
|
module HandleErrors
|
6
|
-
def fail_with_error!(
|
7
|
-
context.fail_with_error!(
|
6
|
+
def fail_with_error!(**args)
|
7
|
+
context.fail_with_error!(object: self, **args)
|
8
8
|
end
|
9
9
|
|
10
|
-
def fail_with_errors!(
|
11
|
-
context.fail_with_errors!(
|
10
|
+
def fail_with_errors!(**args)
|
11
|
+
context.fail_with_errors!(object: self, **args)
|
12
12
|
end
|
13
13
|
|
14
|
-
def add_error(
|
15
|
-
context.add_error(
|
14
|
+
def add_error(**args)
|
15
|
+
context.add_error(object: self, **args)
|
16
16
|
end
|
17
17
|
|
18
|
-
def add_errors(
|
19
|
-
context.add_errors(
|
18
|
+
def add_errors(**args)
|
19
|
+
context.add_errors(object: self, **args)
|
20
20
|
end
|
21
21
|
|
22
22
|
def error_messages
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Metaractor
|
2
|
+
module Namespace
|
3
|
+
# The following code is adapted from rails.
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def module_parent_name
|
11
|
+
if defined?(@parent_name)
|
12
|
+
@parent_name
|
13
|
+
else
|
14
|
+
parent_name = name =~ /::[^:]+\z/ ? -$` : nil
|
15
|
+
@parent_name = parent_name unless frozen?
|
16
|
+
parent_name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def module_parent_names
|
21
|
+
parents = []
|
22
|
+
if module_parent_name
|
23
|
+
parents = module_parent_name.split("::")
|
24
|
+
end
|
25
|
+
parents
|
26
|
+
end
|
27
|
+
|
28
|
+
def i18n_parent_names
|
29
|
+
module_parent_names.map {|name| underscore_module_name(name).to_sym }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def underscore_module_name(camel_cased_word)
|
35
|
+
return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
|
36
|
+
word = camel_cased_word.to_s.gsub("::", "/")
|
37
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
38
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
39
|
+
word.tr!("-", "_")
|
40
|
+
word.downcase!
|
41
|
+
word
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# Special thanks to the `hashie` and `active_attr` gems for code and inspiration.
|
2
|
+
|
1
3
|
module Metaractor
|
2
4
|
module Parameters
|
3
5
|
def self.included(base)
|
@@ -6,40 +8,92 @@ module Metaractor
|
|
6
8
|
include Metaractor::HandleErrors
|
7
9
|
|
8
10
|
class << self
|
9
|
-
attr_writer :
|
10
|
-
attr_writer :_optional_parameters
|
11
|
-
attr_writer :_allow_blank
|
11
|
+
attr_writer :requirement_trees
|
12
12
|
end
|
13
13
|
|
14
14
|
before :remove_blank_values
|
15
|
+
before :apply_defaults
|
15
16
|
before :validate_required_parameters
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
20
|
+
class Parameter
|
21
|
+
include Comparable
|
22
|
+
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
def initialize(name, **options)
|
26
|
+
@name = name.to_sym
|
27
|
+
@options = options
|
28
|
+
end
|
29
|
+
|
30
|
+
def <=>(other)
|
31
|
+
return nil unless other.instance_of? self.class
|
32
|
+
return nil if name == other.name && options != other.options
|
33
|
+
self.name.to_s <=> other.name.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
@options[key]
|
38
|
+
end
|
39
|
+
|
40
|
+
def dig(name, *names)
|
41
|
+
@options.dig(name, *names)
|
42
|
+
end
|
43
|
+
|
44
|
+
def merge!(**options)
|
45
|
+
@options.merge!(**options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
name.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_sym
|
53
|
+
name
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
attr_reader :options
|
58
|
+
end
|
59
|
+
|
19
60
|
module ClassMethods
|
20
|
-
def
|
21
|
-
|
61
|
+
def parameter(name, **options)
|
62
|
+
if param = self.parameter_hash[name.to_sym]
|
63
|
+
param.merge!(**options)
|
64
|
+
else
|
65
|
+
Parameter.new(name, **options).tap do |parameter|
|
66
|
+
self.parameter_hash[parameter.name] = parameter
|
67
|
+
end
|
68
|
+
end
|
22
69
|
end
|
23
70
|
|
24
|
-
def
|
25
|
-
|
71
|
+
def parameters(*names, **options)
|
72
|
+
names.each do |name|
|
73
|
+
parameter(name, **options)
|
74
|
+
end
|
26
75
|
end
|
27
|
-
alias_method :required_parameters, :required
|
28
76
|
|
29
|
-
def
|
30
|
-
@
|
77
|
+
def parameter_hash
|
78
|
+
@parameters ||= {}
|
31
79
|
end
|
32
80
|
|
33
|
-
def
|
34
|
-
|
81
|
+
def requirement_trees
|
82
|
+
@requirement_trees ||= []
|
35
83
|
end
|
36
84
|
|
37
|
-
def
|
38
|
-
|
85
|
+
def required(*params, **options)
|
86
|
+
if params.empty?
|
87
|
+
tree = options
|
88
|
+
self.requirement_trees << tree
|
89
|
+
parameters(*parameters_in_tree(tree), required: tree)
|
90
|
+
else
|
91
|
+
parameters(*params, required: true, **options)
|
92
|
+
end
|
39
93
|
end
|
40
94
|
|
41
|
-
def
|
42
|
-
|
95
|
+
def optional(*params, **options)
|
96
|
+
parameters(*params, **options)
|
43
97
|
end
|
44
98
|
|
45
99
|
def validate_parameters(*hooks, &block)
|
@@ -50,26 +104,74 @@ module Metaractor
|
|
50
104
|
def validate_hooks
|
51
105
|
@validate_hooks ||= []
|
52
106
|
end
|
107
|
+
|
108
|
+
def parameters_in_tree(tree)
|
109
|
+
if tree.respond_to?(:to_h)
|
110
|
+
tree.to_h.values.first.to_a.flat_map {|t| parameters_in_tree(t)}
|
111
|
+
else
|
112
|
+
[tree]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def parameters
|
118
|
+
self.class.parameter_hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def requirement_trees
|
122
|
+
self.class.requirement_trees
|
123
|
+
end
|
124
|
+
|
125
|
+
def requirement_trees=(trees)
|
126
|
+
self.class.requirement_trees=(trees)
|
53
127
|
end
|
54
128
|
|
55
129
|
def remove_blank_values
|
56
130
|
to_delete = []
|
57
|
-
context.each_pair do |
|
58
|
-
next if
|
131
|
+
context.each_pair do |name, value|
|
132
|
+
next if parameters.dig(name, :allow_blank)
|
59
133
|
|
60
134
|
# The following regex is borrowed from Rails' String#blank?
|
61
|
-
to_delete <<
|
135
|
+
to_delete << name if (value.is_a?(String) && /\A[[:space:]]*\z/ === value) || value.nil?
|
62
136
|
end
|
63
|
-
|
64
|
-
|
137
|
+
|
138
|
+
to_delete.each do |name|
|
139
|
+
context.delete_field name
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def apply_defaults
|
144
|
+
parameters.each do |name, parameter|
|
145
|
+
next unless parameter[:default]
|
146
|
+
|
147
|
+
unless context.has_key?(name)
|
148
|
+
context[name] = _parameter_default(name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def _parameter_default(name)
|
154
|
+
default = self.parameters[name][:default]
|
155
|
+
|
156
|
+
case
|
157
|
+
when default.respond_to?(:call) then instance_exec(&default)
|
158
|
+
when default.respond_to?(:dup) then default.dup
|
159
|
+
else default
|
65
160
|
end
|
66
161
|
end
|
67
162
|
|
68
163
|
def validate_required_parameters
|
69
164
|
context.errors ||= []
|
70
165
|
|
71
|
-
|
72
|
-
|
166
|
+
parameters.each do |name, parameter|
|
167
|
+
next if !parameter[:required] ||
|
168
|
+
parameter[:required].is_a?(Hash)
|
169
|
+
|
170
|
+
require_parameter name
|
171
|
+
end
|
172
|
+
|
173
|
+
requirement_trees.each do |tree|
|
174
|
+
require_parameter tree
|
73
175
|
end
|
74
176
|
|
75
177
|
run_validate_hooks
|
data/lib/metaractor/version.rb
CHANGED
data/lib/metaractor.rb
CHANGED
@@ -10,6 +10,8 @@ require 'metaractor/chain_failures'
|
|
10
10
|
require 'metaractor/fail_from_context'
|
11
11
|
require 'metaractor/context_has_key'
|
12
12
|
require 'metaractor/failure_output'
|
13
|
+
require 'i18n'
|
14
|
+
require 'metaractor/namespace'
|
13
15
|
|
14
16
|
module Metaractor
|
15
17
|
def self.included(base)
|
@@ -43,7 +45,8 @@ module Metaractor
|
|
43
45
|
{ module: Metaractor::HandleErrors, method: :include },
|
44
46
|
{ module: Metaractor::Parameters, method: :include },
|
45
47
|
{ module: Metaractor::RunWithContext, method: :include },
|
46
|
-
{ module: Metaractor::ChainFailures, method: :include }
|
48
|
+
{ module: Metaractor::ChainFailures, method: :include },
|
49
|
+
{ module: Metaractor::Namespace, method: :include }
|
47
50
|
]
|
48
51
|
end
|
49
52
|
|
@@ -54,4 +57,24 @@ module Metaractor
|
|
54
57
|
def self.prepend_module(mod)
|
55
58
|
modules << { module: mod, method: :prepend }
|
56
59
|
end
|
60
|
+
|
61
|
+
def self.format_hash(hash)
|
62
|
+
if @hash_formatter.nil?
|
63
|
+
@hash_formatter = default_hash_formatter
|
64
|
+
end
|
65
|
+
|
66
|
+
@hash_formatter.call(hash)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.default_hash_formatter
|
70
|
+
->(hash){ hash.inspect }
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.hash_formatter
|
74
|
+
@hash_formatter
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.hash_formatter=(callable)
|
78
|
+
@hash_formatter = callable
|
79
|
+
end
|
57
80
|
end
|
data/metaractor.gemspec
CHANGED
@@ -20,8 +20,11 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'interactor', '~> 3.1'
|
22
22
|
spec.add_runtime_dependency 'sycamore', '~> 0.3'
|
23
|
+
spec.add_runtime_dependency 'i18n', '~> 1.8'
|
23
24
|
|
24
25
|
spec.add_development_dependency 'bundler', '~> 2'
|
25
26
|
spec.add_development_dependency 'rake', '~> 13.0'
|
26
27
|
spec.add_development_dependency 'rspec', '~> 3.9'
|
28
|
+
spec.add_development_dependency 'awesome_print', '~> 1.8'
|
29
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.9'
|
27
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metaractor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Schlesinger
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: interactor
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: i18n
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.8'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +94,34 @@ dependencies:
|
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '3.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: awesome_print
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.8'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.8'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry-byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.9'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.9'
|
83
125
|
description:
|
84
126
|
email:
|
85
127
|
- ryan@outstand.com
|
@@ -105,6 +147,7 @@ files:
|
|
105
147
|
- lib/metaractor/fail_from_context.rb
|
106
148
|
- lib/metaractor/failure_output.rb
|
107
149
|
- lib/metaractor/handle_errors.rb
|
150
|
+
- lib/metaractor/namespace.rb
|
108
151
|
- lib/metaractor/parameters.rb
|
109
152
|
- lib/metaractor/run_with_context.rb
|
110
153
|
- lib/metaractor/spec.rb
|