servactory 2.12.0.rc2 → 2.12.0.rc3
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/config/locales/en.yml +12 -6
- data/config/locales/ru.yml +12 -6
- data/lib/servactory/configuration/setup.rb +3 -0
- data/lib/servactory/context/workspace/inputs.rb +0 -35
- data/lib/servactory/inputs/dsl.rb +0 -1
- data/lib/servactory/inputs/input.rb +0 -4
- data/lib/servactory/internals/dsl.rb +0 -1
- data/lib/servactory/internals/internal.rb +0 -4
- data/lib/servactory/maintenance/attributes/option.rb +7 -13
- data/lib/servactory/maintenance/attributes/options/registrar.rb +1 -23
- data/lib/servactory/maintenance/attributes/translator/must.rb +1 -1
- data/lib/servactory/maintenance/attributes/translator/type.rb +3 -38
- data/lib/servactory/maintenance/attributes/validations/must.rb +8 -6
- data/lib/servactory/maintenance/validations/types.rb +3 -26
- data/lib/servactory/outputs/dsl.rb +0 -1
- data/lib/servactory/outputs/output.rb +0 -4
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +9 -26
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +11 -27
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb +91 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +6 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/schema_matcher.rb +83 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +30 -11
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +7 -1
- data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +32 -11
- data/lib/servactory/tool_kit/dynamic_options/must.rb +29 -5
- data/lib/servactory/tool_kit/dynamic_options/schema.rb +193 -0
- data/lib/servactory/version.rb +1 -1
- metadata +5 -3
- data/lib/servactory/maintenance/validations/object_schema.rb +0 -116
data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb
CHANGED
@@ -6,15 +6,17 @@ module Servactory
|
|
6
6
|
module Matchers
|
7
7
|
module HaveServiceAttributeMatchers
|
8
8
|
class InclusionMatcher
|
9
|
+
OPTION_NAME = :inclusion
|
10
|
+
OPTION_BODY_KEY = :in
|
11
|
+
|
9
12
|
attr_reader :missing_option
|
10
13
|
|
11
|
-
def initialize(described_class, attribute_type, attribute_name, values
|
14
|
+
def initialize(described_class, attribute_type, attribute_name, values)
|
12
15
|
@described_class = described_class
|
13
16
|
@attribute_type = attribute_type
|
14
17
|
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
18
|
@attribute_name = attribute_name
|
16
19
|
@values = values
|
17
|
-
@custom_message = custom_message
|
18
20
|
|
19
21
|
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
20
22
|
|
@@ -42,37 +44,19 @@ module Servactory
|
|
42
44
|
:attribute_type_plural,
|
43
45
|
:attribute_name,
|
44
46
|
:values,
|
45
|
-
:custom_message,
|
46
47
|
:attribute_data
|
47
48
|
|
48
|
-
def submatcher_passes?(_subject)
|
49
|
-
attribute_inclusion = attribute_data.fetch(
|
50
|
-
attribute_inclusion_in = attribute_inclusion.fetch(
|
51
|
-
attribute_inclusion_message = attribute_inclusion.fetch(:message)
|
52
|
-
|
53
|
-
matched = attribute_inclusion_in.difference(values).empty? &&
|
54
|
-
values.difference(attribute_inclusion_in).empty?
|
55
|
-
|
56
|
-
if custom_message.present? && !attribute_inclusion_message.nil?
|
57
|
-
if custom_message.is_a?(RSpec::Matchers::BuiltIn::BaseMatcher)
|
58
|
-
RSpec::Expectations::ValueExpectationTarget
|
59
|
-
.new(attribute_inclusion_message)
|
60
|
-
.to(custom_message)
|
61
|
-
else
|
62
|
-
matched &&= if attribute_inclusion_message.is_a?(Proc)
|
63
|
-
attribute_inclusion_message.call.casecmp(custom_message).zero?
|
64
|
-
else
|
65
|
-
attribute_inclusion_message.casecmp(custom_message).zero?
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
49
|
+
def submatcher_passes?(_subject)
|
50
|
+
attribute_inclusion = attribute_data.fetch(OPTION_NAME)
|
51
|
+
attribute_inclusion_in = attribute_inclusion.fetch(OPTION_BODY_KEY)
|
69
52
|
|
70
|
-
|
53
|
+
attribute_inclusion_in.difference(values).empty? &&
|
54
|
+
values.difference(attribute_inclusion_in).empty?
|
71
55
|
end
|
72
56
|
|
73
57
|
def build_missing_option
|
74
|
-
attribute_inclusion = attribute_data.fetch(
|
75
|
-
attribute_inclusion_in = attribute_inclusion.fetch(
|
58
|
+
attribute_inclusion = attribute_data.fetch(OPTION_NAME)
|
59
|
+
attribute_inclusion_in = attribute_inclusion.fetch(OPTION_BODY_KEY)
|
76
60
|
|
77
61
|
<<~MESSAGE
|
78
62
|
should include the expected values
|
data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceAttributeMatchers
|
8
|
+
class MessageMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, submatcher, custom_message)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
@custom_message = custom_message
|
17
|
+
|
18
|
+
attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
19
|
+
|
20
|
+
attribute_schema = attribute_data.fetch(submatcher.class::OPTION_NAME)
|
21
|
+
@attribute_schema_is = attribute_schema.fetch(submatcher.class::OPTION_BODY_KEY)
|
22
|
+
@attribute_schema_message = attribute_schema.fetch(:message)
|
23
|
+
|
24
|
+
@missing_option = ""
|
25
|
+
end
|
26
|
+
|
27
|
+
def description
|
28
|
+
result = "message: "
|
29
|
+
result + attribute_schema_message
|
30
|
+
end
|
31
|
+
|
32
|
+
def matches?(subject)
|
33
|
+
if submatcher_passes?(subject)
|
34
|
+
true
|
35
|
+
else
|
36
|
+
@missing_option = build_missing_option
|
37
|
+
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :described_class,
|
45
|
+
:attribute_type,
|
46
|
+
:attribute_type_plural,
|
47
|
+
:attribute_name,
|
48
|
+
:option_types,
|
49
|
+
:custom_message,
|
50
|
+
:attribute_schema_is,
|
51
|
+
:attribute_schema_message
|
52
|
+
|
53
|
+
def submatcher_passes?(_subject)
|
54
|
+
schema_message_equal?
|
55
|
+
end
|
56
|
+
|
57
|
+
def schema_message_equal? # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
58
|
+
@schema_message_equal ||=
|
59
|
+
if custom_message.present? && !attribute_schema_message.nil?
|
60
|
+
if custom_message.is_a?(RSpec::Matchers::BuiltIn::BaseMatcher)
|
61
|
+
RSpec::Expectations::ValueExpectationTarget
|
62
|
+
.new(attribute_schema_message)
|
63
|
+
.to(custom_message)
|
64
|
+
|
65
|
+
true
|
66
|
+
elsif attribute_schema_message.is_a?(Proc)
|
67
|
+
attribute_schema_message.call.casecmp(custom_message).zero?
|
68
|
+
else
|
69
|
+
attribute_schema_message.casecmp(custom_message).zero?
|
70
|
+
end
|
71
|
+
else
|
72
|
+
true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_missing_option
|
77
|
+
unless schema_message_equal? # rubocop:disable Style/GuardClause
|
78
|
+
<<~MESSAGE
|
79
|
+
should return expected message in case of problem:
|
80
|
+
|
81
|
+
expected #{attribute_schema_message.inspect}
|
82
|
+
got #{custom_message.inspect}
|
83
|
+
MESSAGE
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -51,6 +51,12 @@ module Servactory
|
|
51
51
|
# NOTE: Even though the dynamic option `consists_of` is a `must`, here we are testing explicit `must`.
|
52
52
|
attribute_must_keys.delete(:consists_of)
|
53
53
|
|
54
|
+
# NOTE: Even though the dynamic option `schema` is a `must`, here we are testing explicit `must`.
|
55
|
+
attribute_must_keys.delete(:schema)
|
56
|
+
|
57
|
+
# NOTE: Even though the dynamic option `inclusion` is a `must`, here we are testing explicit `must`.
|
58
|
+
attribute_must_keys.delete(:inclusion)
|
59
|
+
|
54
60
|
attribute_must_keys.difference(must_names).empty? &&
|
55
61
|
must_names.difference(attribute_must_keys).empty?
|
56
62
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceAttributeMatchers
|
8
|
+
class SchemaMatcher
|
9
|
+
OPTION_NAME = :schema
|
10
|
+
OPTION_BODY_KEY = :is
|
11
|
+
|
12
|
+
attr_reader :missing_option
|
13
|
+
|
14
|
+
def initialize(described_class, attribute_type, attribute_name, option_types, schema_data) # rubocop:disable Metrics/MethodLength
|
15
|
+
@described_class = described_class
|
16
|
+
@attribute_type = attribute_type
|
17
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
18
|
+
@attribute_name = attribute_name
|
19
|
+
@option_types = option_types
|
20
|
+
@schema_data = schema_data
|
21
|
+
|
22
|
+
attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
23
|
+
|
24
|
+
attribute_schema = attribute_data.fetch(OPTION_NAME)
|
25
|
+
@attribute_schema_is = attribute_schema.fetch(OPTION_BODY_KEY)
|
26
|
+
@attribute_schema_message = attribute_schema.fetch(:message)
|
27
|
+
|
28
|
+
@missing_option = ""
|
29
|
+
end
|
30
|
+
|
31
|
+
def description
|
32
|
+
result = "schema: "
|
33
|
+
result + schema_data
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches?(subject)
|
37
|
+
if submatcher_passes?(subject)
|
38
|
+
true
|
39
|
+
else
|
40
|
+
@missing_option = build_missing_option
|
41
|
+
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :described_class,
|
49
|
+
:attribute_type,
|
50
|
+
:attribute_type_plural,
|
51
|
+
:attribute_name,
|
52
|
+
:option_types,
|
53
|
+
:schema_data,
|
54
|
+
:attribute_schema_is,
|
55
|
+
:attribute_schema_message
|
56
|
+
|
57
|
+
def submatcher_passes?(_subject)
|
58
|
+
schema_data_equal?
|
59
|
+
end
|
60
|
+
|
61
|
+
def schema_data_equal?
|
62
|
+
@schema_data_equal ||=
|
63
|
+
(
|
64
|
+
schema_data.present? && schema_data == attribute_schema_is
|
65
|
+
) || schema_data.blank?
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_missing_option
|
69
|
+
return if schema_data_equal?
|
70
|
+
|
71
|
+
<<~MESSAGE
|
72
|
+
should be schema with corresponding template
|
73
|
+
|
74
|
+
expected #{attribute_schema_is.inspect}
|
75
|
+
got #{schema_data.inspect}
|
76
|
+
MESSAGE
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -84,31 +84,37 @@ module Servactory
|
|
84
84
|
self
|
85
85
|
end
|
86
86
|
|
87
|
-
def consists_of(*types)
|
88
|
-
message = block_given? ? yield : nil
|
89
|
-
|
87
|
+
def consists_of(*types)
|
90
88
|
add_submatcher(
|
91
89
|
HaveServiceAttributeMatchers::ConsistsOfMatcher,
|
92
90
|
described_class,
|
93
91
|
:input,
|
94
92
|
input_name,
|
95
93
|
@option_types,
|
96
|
-
Array(types)
|
97
|
-
message
|
94
|
+
Array(types)
|
98
95
|
)
|
99
96
|
self
|
100
97
|
end
|
101
98
|
|
102
|
-
def
|
103
|
-
|
99
|
+
def schema(data = {})
|
100
|
+
add_submatcher(
|
101
|
+
HaveServiceAttributeMatchers::SchemaMatcher,
|
102
|
+
described_class,
|
103
|
+
:input,
|
104
|
+
input_name,
|
105
|
+
@option_types,
|
106
|
+
data
|
107
|
+
)
|
108
|
+
self
|
109
|
+
end
|
104
110
|
|
111
|
+
def inclusion(values)
|
105
112
|
add_submatcher(
|
106
113
|
HaveServiceAttributeMatchers::InclusionMatcher,
|
107
114
|
described_class,
|
108
115
|
:input,
|
109
116
|
input_name,
|
110
|
-
Array(values)
|
111
|
-
message
|
117
|
+
Array(values)
|
112
118
|
)
|
113
119
|
self
|
114
120
|
end
|
@@ -135,6 +141,18 @@ module Servactory
|
|
135
141
|
self
|
136
142
|
end
|
137
143
|
|
144
|
+
def message(message)
|
145
|
+
add_submatcher(
|
146
|
+
HaveServiceAttributeMatchers::MessageMatcher,
|
147
|
+
described_class,
|
148
|
+
:input,
|
149
|
+
input_name,
|
150
|
+
@last_submatcher,
|
151
|
+
message
|
152
|
+
)
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
138
156
|
# NOTE: Used for delayed chain implementation
|
139
157
|
# def not_implemented_chain(*description)
|
140
158
|
# Kernel.warn <<-MESSAGE.squish
|
@@ -165,11 +183,12 @@ module Servactory
|
|
165
183
|
|
166
184
|
protected
|
167
185
|
|
168
|
-
attr_reader :submatchers, :missing, :subject
|
186
|
+
attr_reader :last_submatcher, :submatchers, :missing, :subject
|
169
187
|
|
170
188
|
def add_submatcher(matcher_class, *args)
|
171
189
|
remove_submatcher(matcher_class)
|
172
|
-
|
190
|
+
@last_submatcher = matcher_class.new(*args)
|
191
|
+
submatchers << @last_submatcher
|
173
192
|
end
|
174
193
|
|
175
194
|
def remove_submatcher(matcher_class)
|
data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb
CHANGED
@@ -47,12 +47,13 @@ module Servactory
|
|
47
47
|
:attribute_data,
|
48
48
|
:i18n_root_key
|
49
49
|
|
50
|
-
def submatcher_passes?(_subject) # rubocop:disable Metrics/CyclomaticComplexity
|
50
|
+
def submatcher_passes?(_subject) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
51
51
|
success_passes? &&
|
52
52
|
failure_type_passes? &&
|
53
53
|
failure_required_passes? &&
|
54
54
|
failure_optional_passes? &&
|
55
55
|
failure_consists_of_passes? &&
|
56
|
+
failure_schema_passes? &&
|
56
57
|
failure_format_passes? &&
|
57
58
|
failure_inclusion_passes? &&
|
58
59
|
failure_must_passes?
|
@@ -117,6 +118,11 @@ module Servactory
|
|
117
118
|
true
|
118
119
|
end
|
119
120
|
|
121
|
+
def failure_schema_passes?
|
122
|
+
# NOTE: Checking for negative cases is not implemented for `schema`
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
120
126
|
def failure_format_passes?
|
121
127
|
# NOTE: Checking for negative cases is not implemented for `format`
|
122
128
|
true
|
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
require_relative "have_service_attribute_matchers/types_matcher"
|
4
4
|
require_relative "have_service_attribute_matchers/consists_of_matcher"
|
5
|
+
require_relative "have_service_attribute_matchers/schema_matcher"
|
5
6
|
require_relative "have_service_attribute_matchers/inclusion_matcher"
|
6
7
|
require_relative "have_service_attribute_matchers/must_matcher"
|
8
|
+
require_relative "have_service_attribute_matchers/message_matcher"
|
7
9
|
|
8
10
|
module Servactory
|
9
11
|
module TestKit
|
@@ -50,31 +52,37 @@ module Servactory
|
|
50
52
|
self
|
51
53
|
end
|
52
54
|
|
53
|
-
def consists_of(*types)
|
54
|
-
message = block_given? ? yield : nil
|
55
|
-
|
55
|
+
def consists_of(*types)
|
56
56
|
add_submatcher(
|
57
57
|
HaveServiceAttributeMatchers::ConsistsOfMatcher,
|
58
58
|
described_class,
|
59
59
|
:internal,
|
60
60
|
internal_name,
|
61
61
|
@option_types,
|
62
|
-
Array(types)
|
63
|
-
message
|
62
|
+
Array(types)
|
64
63
|
)
|
65
64
|
self
|
66
65
|
end
|
67
66
|
|
68
|
-
def
|
69
|
-
|
67
|
+
def schema(data = {})
|
68
|
+
add_submatcher(
|
69
|
+
HaveServiceAttributeMatchers::SchemaMatcher,
|
70
|
+
described_class,
|
71
|
+
:internal,
|
72
|
+
internal_name,
|
73
|
+
@option_types,
|
74
|
+
data
|
75
|
+
)
|
76
|
+
self
|
77
|
+
end
|
70
78
|
|
79
|
+
def inclusion(values)
|
71
80
|
add_submatcher(
|
72
81
|
HaveServiceAttributeMatchers::InclusionMatcher,
|
73
82
|
described_class,
|
74
83
|
:internal,
|
75
84
|
internal_name,
|
76
|
-
Array(values)
|
77
|
-
message
|
85
|
+
Array(values)
|
78
86
|
)
|
79
87
|
self
|
80
88
|
end
|
@@ -90,6 +98,18 @@ module Servactory
|
|
90
98
|
self
|
91
99
|
end
|
92
100
|
|
101
|
+
def message(message)
|
102
|
+
add_submatcher(
|
103
|
+
HaveServiceAttributeMatchers::MessageMatcher,
|
104
|
+
described_class,
|
105
|
+
:internal,
|
106
|
+
internal_name,
|
107
|
+
@last_submatcher,
|
108
|
+
message
|
109
|
+
)
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
93
113
|
def description
|
94
114
|
"#{internal_name} with #{submatchers.map(&:description).join(', ')}"
|
95
115
|
end
|
@@ -110,11 +130,12 @@ module Servactory
|
|
110
130
|
|
111
131
|
protected
|
112
132
|
|
113
|
-
attr_reader :submatchers, :missing, :subject
|
133
|
+
attr_reader :last_submatcher, :submatchers, :missing, :subject
|
114
134
|
|
115
135
|
def add_submatcher(matcher_class, *args)
|
116
136
|
remove_submatcher(matcher_class)
|
117
|
-
|
137
|
+
@last_submatcher = matcher_class.new(*args)
|
138
|
+
submatchers << @last_submatcher
|
118
139
|
end
|
119
140
|
|
120
141
|
def remove_submatcher(matcher_class)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Servactory
|
4
4
|
module ToolKit
|
5
5
|
module DynamicOptions
|
6
|
-
class Must
|
6
|
+
class Must # rubocop:disable Metrics/ClassLength
|
7
7
|
class WorkOption
|
8
8
|
attr_reader :name,
|
9
9
|
:value,
|
@@ -80,24 +80,48 @@ module Servactory
|
|
80
80
|
is_option_message_present = option.message.present?
|
81
81
|
is_option_message_proc = option.message.is_a?(Proc) if is_option_message_present
|
82
82
|
|
83
|
-
lambda do |input: nil, internal: nil, output: nil, **attributes|
|
83
|
+
lambda do |input: nil, internal: nil, output: nil, **attributes| # rubocop:disable Metrics/BlockLength
|
84
84
|
default_attributes = { **attributes, option_name: option.name, option_value: option.value }
|
85
85
|
|
86
86
|
if Servactory::Utils.really_input?(input)
|
87
87
|
if is_option_message_present
|
88
|
-
is_option_message_proc
|
88
|
+
if is_option_message_proc
|
89
|
+
option.message.call(
|
90
|
+
input:,
|
91
|
+
**default_attributes.delete(:meta) || {},
|
92
|
+
**default_attributes
|
93
|
+
)
|
94
|
+
else
|
95
|
+
option.message
|
96
|
+
end
|
89
97
|
else
|
90
98
|
message_for_input_with(**default_attributes, input:)
|
91
99
|
end
|
92
100
|
elsif Servactory::Utils.really_internal?(internal)
|
93
101
|
if is_option_message_present
|
94
|
-
is_option_message_proc
|
102
|
+
if is_option_message_proc
|
103
|
+
option.message.call(
|
104
|
+
internal:,
|
105
|
+
**default_attributes.delete(:meta) || {},
|
106
|
+
**default_attributes
|
107
|
+
)
|
108
|
+
else
|
109
|
+
option.message
|
110
|
+
end
|
95
111
|
else
|
96
112
|
message_for_internal_with(**default_attributes, internal:)
|
97
113
|
end
|
98
114
|
elsif Servactory::Utils.really_output?(output)
|
99
115
|
if is_option_message_present
|
100
|
-
is_option_message_proc
|
116
|
+
if is_option_message_proc
|
117
|
+
option.message.call(
|
118
|
+
output:,
|
119
|
+
**default_attributes.delete(:meta) || {},
|
120
|
+
**default_attributes
|
121
|
+
)
|
122
|
+
else
|
123
|
+
option.message
|
124
|
+
end
|
101
125
|
else
|
102
126
|
message_for_output_with(**default_attributes, output:)
|
103
127
|
end
|