view_component 2.82.0 → 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.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/controllers/view_components_system_test_controller.rb +24 -1
- data/app/helpers/preview_helper.rb +2 -4
- data/docs/CHANGELOG.md +278 -0
- data/lib/view_component/base.rb +41 -92
- data/lib/view_component/capture_compatibility.rb +44 -0
- data/lib/view_component/collection.rb +2 -5
- data/lib/view_component/compiler.rb +51 -28
- data/lib/view_component/config.rb +9 -13
- data/lib/view_component/deprecation.rb +1 -1
- data/lib/view_component/docs_builder_component.html.erb +5 -1
- data/lib/view_component/docs_builder_component.rb +28 -9
- data/lib/view_component/engine.rb +12 -22
- data/lib/view_component/errors.rb +213 -0
- data/lib/view_component/inline_template.rb +55 -0
- data/lib/view_component/preview.rb +1 -7
- data/lib/view_component/rails/tasks/view_component.rake +1 -1
- data/lib/view_component/slot.rb +107 -1
- data/lib/view_component/slotable.rb +356 -96
- data/lib/view_component/system_test_helpers.rb +5 -5
- data/lib/view_component/test_helpers.rb +67 -54
- data/lib/view_component/translatable.rb +35 -23
- data/lib/view_component/version.rb +4 -3
- data/lib/view_component/with_content_helper.rb +3 -8
- data/lib/view_component.rb +3 -12
- metadata +28 -17
- data/lib/view_component/content_areas.rb +0 -56
- data/lib/view_component/polymorphic_slots.rb +0 -103
- data/lib/view_component/preview_template_error.rb +0 -6
- data/lib/view_component/slot_v2.rb +0 -98
- data/lib/view_component/slotable_v2.rb +0 -391
- data/lib/view_component/template_error.rb +0 -9
@@ -31,50 +31,58 @@ module ViewComponent
|
|
31
31
|
return if component_class == ViewComponent::Base
|
32
32
|
|
33
33
|
component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass?
|
34
|
-
subclass_instance_methods = component_class.instance_methods(false)
|
35
|
-
|
36
|
-
if subclass_instance_methods.include?(:with_content) && raise_errors
|
37
|
-
raise ViewComponent::ComponentError.new(
|
38
|
-
"#{component_class} implements a reserved method, `#with_content`.\n\n" \
|
39
|
-
"To fix this issue, change the name of the method."
|
40
|
-
)
|
41
|
-
end
|
42
34
|
|
43
35
|
if template_errors.present?
|
44
|
-
raise
|
36
|
+
raise TemplateError.new(template_errors) if raise_errors
|
45
37
|
|
46
38
|
return false
|
47
39
|
end
|
48
40
|
|
49
|
-
if subclass_instance_methods.include?(:before_render_check)
|
50
|
-
ViewComponent::Deprecation.deprecation_warning(
|
51
|
-
"`before_render_check`", :"`before_render`"
|
52
|
-
)
|
53
|
-
end
|
54
|
-
|
55
41
|
if raise_errors
|
56
42
|
component_class.validate_initialization_parameters!
|
57
43
|
component_class.validate_collection_parameter!
|
58
44
|
end
|
59
45
|
|
60
|
-
|
61
|
-
|
62
|
-
# as Ruby warns when redefining a method.
|
63
|
-
method_name = call_method_name(template[:variant])
|
46
|
+
if has_inline_template?
|
47
|
+
template = component_class.inline_template
|
64
48
|
|
65
49
|
redefinition_lock.synchronize do
|
66
|
-
component_class.silence_redefinition_of_method(
|
50
|
+
component_class.silence_redefinition_of_method("call")
|
67
51
|
# rubocop:disable Style/EvalWithLocation
|
68
|
-
component_class.class_eval <<-RUBY, template
|
69
|
-
def
|
70
|
-
#{
|
52
|
+
component_class.class_eval <<-RUBY, template.path, template.lineno
|
53
|
+
def call
|
54
|
+
#{compiled_inline_template(template)}
|
71
55
|
end
|
72
56
|
RUBY
|
73
57
|
# rubocop:enable Style/EvalWithLocation
|
58
|
+
|
59
|
+
component_class.silence_redefinition_of_method("render_template_for")
|
60
|
+
component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
61
|
+
def render_template_for(variant = nil)
|
62
|
+
call
|
63
|
+
end
|
64
|
+
RUBY
|
65
|
+
end
|
66
|
+
else
|
67
|
+
templates.each do |template|
|
68
|
+
# Remove existing compiled template methods,
|
69
|
+
# as Ruby warns when redefining a method.
|
70
|
+
method_name = call_method_name(template[:variant])
|
71
|
+
|
72
|
+
redefinition_lock.synchronize do
|
73
|
+
component_class.silence_redefinition_of_method(method_name)
|
74
|
+
# rubocop:disable Style/EvalWithLocation
|
75
|
+
component_class.class_eval <<-RUBY, template[:path], 0
|
76
|
+
def #{method_name}
|
77
|
+
#{compiled_template(template[:path])}
|
78
|
+
end
|
79
|
+
RUBY
|
80
|
+
# rubocop:enable Style/EvalWithLocation
|
81
|
+
end
|
74
82
|
end
|
75
|
-
end
|
76
83
|
|
77
|
-
|
84
|
+
define_render_template_for
|
85
|
+
end
|
78
86
|
|
79
87
|
component_class.build_i18n_backend
|
80
88
|
|
@@ -109,12 +117,16 @@ module ViewComponent
|
|
109
117
|
end
|
110
118
|
end
|
111
119
|
|
120
|
+
def has_inline_template?
|
121
|
+
component_class.respond_to?(:inline_template) && component_class.inline_template.present?
|
122
|
+
end
|
123
|
+
|
112
124
|
def template_errors
|
113
125
|
@__vc_template_errors ||=
|
114
126
|
begin
|
115
127
|
errors = []
|
116
128
|
|
117
|
-
if (templates + inline_calls).empty?
|
129
|
+
if (templates + inline_calls).empty? && !has_inline_template?
|
118
130
|
errors << "Couldn't find a template file or inline render method for #{component_class}."
|
119
131
|
end
|
120
132
|
|
@@ -222,9 +234,21 @@ module ViewComponent
|
|
222
234
|
end
|
223
235
|
end
|
224
236
|
|
237
|
+
def compiled_inline_template(template)
|
238
|
+
handler = ActionView::Template.handler_for_extension(template.language)
|
239
|
+
template.rstrip! if component_class.strip_trailing_whitespace?
|
240
|
+
|
241
|
+
compile_template(template.source, handler)
|
242
|
+
end
|
243
|
+
|
225
244
|
def compiled_template(file_path)
|
226
245
|
handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete("."))
|
227
246
|
template = File.read(file_path)
|
247
|
+
|
248
|
+
compile_template(template, handler)
|
249
|
+
end
|
250
|
+
|
251
|
+
def compile_template(template, handler)
|
228
252
|
template.rstrip! if component_class.strip_trailing_whitespace?
|
229
253
|
|
230
254
|
if handler.method(:call).parameters.length > 1
|
@@ -253,8 +277,7 @@ module ViewComponent
|
|
253
277
|
end
|
254
278
|
|
255
279
|
def should_compile_superclass?
|
256
|
-
development? &&
|
257
|
-
templates.empty? &&
|
280
|
+
development? && templates.empty? && !has_inline_template? &&
|
258
281
|
!(
|
259
282
|
component_class.instance_methods(false).include?(:call) ||
|
260
283
|
component_class.private_instance_methods(false).include?(:call)
|
@@ -23,7 +23,8 @@ module ViewComponent
|
|
23
23
|
show_previews: Rails.env.development? || Rails.env.test?,
|
24
24
|
preview_paths: default_preview_paths,
|
25
25
|
test_controller: "ApplicationController",
|
26
|
-
default_preview_layout: nil
|
26
|
+
default_preview_layout: nil,
|
27
|
+
capture_compatibility_patch_enabled: false
|
27
28
|
})
|
28
29
|
end
|
29
30
|
|
@@ -126,9 +127,6 @@ module ViewComponent
|
|
126
127
|
# The locations in which component previews will be looked up.
|
127
128
|
# Defaults to `['test/component/previews']` relative to your Rails root.
|
128
129
|
|
129
|
-
# @!attribute preview_path
|
130
|
-
# @deprecated Use #preview_paths instead. Will be removed in v3.0.0.
|
131
|
-
|
132
130
|
# @!attribute test_controller
|
133
131
|
# @return [String]
|
134
132
|
# The controller used for testing components.
|
@@ -140,6 +138,13 @@ module ViewComponent
|
|
140
138
|
# A custom default layout used for the previews index page and individual
|
141
139
|
# previews.
|
142
140
|
# Defaults to `nil`. If this is falsy, `"component_preview"` is used.
|
141
|
+
#
|
142
|
+
# @!attribute capture_compatibility_patch_enabled
|
143
|
+
# @return [Boolean]
|
144
|
+
# Enables the experimental capture compatibility patch that makes ViewComponent
|
145
|
+
# compatible with forms, capture, and other built-ins.
|
146
|
+
# previews.
|
147
|
+
# Defaults to `false`.
|
143
148
|
|
144
149
|
def default_preview_paths
|
145
150
|
return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
|
@@ -158,15 +163,6 @@ module ViewComponent
|
|
158
163
|
@config = self.class.defaults
|
159
164
|
end
|
160
165
|
|
161
|
-
def preview_path
|
162
|
-
preview_paths
|
163
|
-
end
|
164
|
-
|
165
|
-
def preview_path=(new_value)
|
166
|
-
ViewComponent::Deprecation.deprecation_warning("`preview_path`", :"`preview_paths`")
|
167
|
-
self.preview_paths = Array.wrap(new_value)
|
168
|
-
end
|
169
|
-
|
170
166
|
delegate_missing_to :config
|
171
167
|
|
172
168
|
private
|
@@ -12,7 +12,11 @@ nav_order: 3
|
|
12
12
|
## <%= section.heading %>
|
13
13
|
|
14
14
|
<% section.methods.each do |method| %>
|
15
|
-
### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method) %>
|
15
|
+
### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method, section.show_types) %>
|
16
|
+
|
17
|
+
<% end %>
|
18
|
+
<% section.error_klasses.each do |error_klass| %>
|
19
|
+
### <%== render ViewComponent::DocsBuilderComponent::ErrorKlassDoc.new(error_klass, section.show_types) %>
|
16
20
|
|
17
21
|
<% end %>
|
18
22
|
<% end %>
|
@@ -2,21 +2,40 @@
|
|
2
2
|
|
3
3
|
module ViewComponent
|
4
4
|
class DocsBuilderComponent < Base
|
5
|
-
class Section < Struct.new(:heading, :methods, :show_types, keyword_init: true)
|
6
|
-
def initialize(heading: nil, methods: [], show_types: true)
|
5
|
+
class Section < Struct.new(:heading, :methods, :error_klasses, :show_types, keyword_init: true)
|
6
|
+
def initialize(heading: nil, methods: [], error_klasses: [], show_types: true)
|
7
7
|
methods.sort_by! { |method| method[:name] }
|
8
|
+
error_klasses.sort!
|
8
9
|
super
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
|
-
class
|
13
|
-
def initialize(
|
14
|
-
@
|
15
|
-
|
13
|
+
class ErrorKlassDoc < ViewComponent::Base
|
14
|
+
def initialize(error_klass, _show_types)
|
15
|
+
@error_klass = error_klass
|
16
|
+
end
|
17
|
+
|
18
|
+
def klass_name
|
19
|
+
@error_klass.gsub("ViewComponent::", "").gsub("::MESSAGE", "")
|
16
20
|
end
|
17
21
|
|
18
|
-
def
|
19
|
-
@
|
22
|
+
def error_message
|
23
|
+
ViewComponent.const_get(@error_klass)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
<<~DOCS.chomp
|
28
|
+
`#{klass_name}`
|
29
|
+
|
30
|
+
#{error_message}
|
31
|
+
DOCS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class MethodDoc < ViewComponent::Base
|
36
|
+
def initialize(method, show_types = true)
|
37
|
+
@method = method
|
38
|
+
@show_types = show_types
|
20
39
|
end
|
21
40
|
|
22
41
|
def deprecated?
|
@@ -28,7 +47,7 @@ module ViewComponent
|
|
28
47
|
end
|
29
48
|
|
30
49
|
def types
|
31
|
-
" → [#{@method.tag(:return).types.join(",")}]" if @method.tag(:return)&.types && show_types
|
50
|
+
" → [#{@method.tag(:return).types.join(",")}]" if @method.tag(:return)&.types && @show_types
|
32
51
|
end
|
33
52
|
|
34
53
|
def signature_or_name
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails"
|
4
|
-
require "view_component/
|
4
|
+
require "view_component/config"
|
5
5
|
|
6
6
|
module ViewComponent
|
7
7
|
class Engine < Rails::Engine # :nodoc:
|
8
|
-
config.view_component = ViewComponent::
|
8
|
+
config.view_component = ViewComponent::Config.defaults
|
9
9
|
|
10
10
|
rake_tasks do
|
11
11
|
load "view_component/rails/tasks/view_component.rake"
|
@@ -14,9 +14,6 @@ module ViewComponent
|
|
14
14
|
initializer "view_component.set_configs" do |app|
|
15
15
|
options = app.config.view_component
|
16
16
|
|
17
|
-
%i[generate preview_controller preview_route show_previews_source].each do |config_option|
|
18
|
-
options[config_option] ||= ViewComponent::Base.public_send(config_option)
|
19
|
-
end
|
20
17
|
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
21
18
|
options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
|
22
19
|
options.show_previews = (Rails.env.development? || Rails.env.test?) if options.show_previews.nil?
|
@@ -39,6 +36,8 @@ module ViewComponent
|
|
39
36
|
|
40
37
|
initializer "view_component.enable_instrumentation" do |app|
|
41
38
|
ActiveSupport.on_load(:view_component) do
|
39
|
+
Base.config = app.config.view_component
|
40
|
+
|
42
41
|
if app.config.view_component.instrumentation_enabled.present?
|
43
42
|
# :nocov:
|
44
43
|
ViewComponent::Base.prepend(ViewComponent::Instrumentation)
|
@@ -47,6 +46,14 @@ module ViewComponent
|
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
49
|
+
# :nocov:
|
50
|
+
initializer "view_component.enable_capture_patch" do |app|
|
51
|
+
ActiveSupport.on_load(:view_component) do
|
52
|
+
ActionView::Base.include(ViewComponent::CaptureCompatibility) if app.config.view_component.capture_compatibility_patch_enabled
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# :nocov:
|
56
|
+
|
50
57
|
initializer "view_component.set_autoload_paths" do |app|
|
51
58
|
options = app.config.view_component
|
52
59
|
|
@@ -143,20 +150,3 @@ module ViewComponent
|
|
143
150
|
end
|
144
151
|
end
|
145
152
|
end
|
146
|
-
|
147
|
-
if RUBY_VERSION < "2.7.0"
|
148
|
-
ViewComponent::Deprecation.deprecation_warning("Support for Ruby versions < 2.7.0")
|
149
|
-
end
|
150
|
-
|
151
|
-
# :nocov:
|
152
|
-
unless defined?(ViewComponent::Base)
|
153
|
-
require "view_component/deprecation"
|
154
|
-
|
155
|
-
ViewComponent::Deprecation.deprecation_warning(
|
156
|
-
"Manually loading the engine",
|
157
|
-
"remove `require \"view_component/engine\"`"
|
158
|
-
)
|
159
|
-
|
160
|
-
require "view_component"
|
161
|
-
end
|
162
|
-
# :nocov:
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module ViewComponent
|
2
|
+
class BaseError < StandardError
|
3
|
+
def initialize
|
4
|
+
super(self.class::MESSAGE)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class DuplicateSlotContentError < StandardError
|
9
|
+
MESSAGE =
|
10
|
+
"It looks like a block was provided after calling `with_content` on COMPONENT, " \
|
11
|
+
"which means that ViewComponent doesn't know which content to use.\n\n" \
|
12
|
+
"To fix this issue, use either `with_content` or a block."
|
13
|
+
|
14
|
+
def initialize(klass_name)
|
15
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TemplateError < StandardError
|
20
|
+
def initialize(errors)
|
21
|
+
super(errors.join(", "))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class MultipleInlineTemplatesError < BaseError
|
26
|
+
MESSAGE = "Inline templates can only be defined once per-component."
|
27
|
+
end
|
28
|
+
|
29
|
+
class MissingPreviewTemplateError < StandardError
|
30
|
+
MESSAGE =
|
31
|
+
"A preview template for example EXAMPLE doesn't exist.\n\n" \
|
32
|
+
"To fix this issue, create a template for the example."
|
33
|
+
|
34
|
+
def initialize(example)
|
35
|
+
super(MESSAGE.gsub("EXAMPLE", example))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class DuplicateContentError < StandardError
|
40
|
+
MESSAGE =
|
41
|
+
"It looks like a block was provided after calling `with_content` on COMPONENT, " \
|
42
|
+
"which means that ViewComponent doesn't know which content to use.\n\n" \
|
43
|
+
"To fix this issue, use either `with_content` or a block."
|
44
|
+
|
45
|
+
def initialize(klass_name)
|
46
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class EmptyOrInvalidInitializerError < StandardError
|
51
|
+
MESSAGE =
|
52
|
+
"The COMPONENT initializer is empty or invalid. " \
|
53
|
+
"It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
|
54
|
+
"To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
|
55
|
+
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
|
56
|
+
|
57
|
+
def initialize(klass_name, parameter)
|
58
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class MissingCollectionArgumentError < StandardError
|
63
|
+
MESSAGE =
|
64
|
+
"The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
|
65
|
+
"which is required to render it as a collection.\n\n" \
|
66
|
+
"To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
|
67
|
+
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
|
68
|
+
|
69
|
+
def initialize(klass_name, parameter)
|
70
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class ReservedParameterError < StandardError
|
75
|
+
MESSAGE =
|
76
|
+
"COMPONENT initializer can't accept the parameter `PARAMETER`, as it will override a " \
|
77
|
+
"public ViewComponent method. To fix this issue, rename the parameter."
|
78
|
+
|
79
|
+
def initialize(klass_name, parameter)
|
80
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class InvalidCollectionArgumentError < BaseError
|
85
|
+
MESSAGE =
|
86
|
+
"The value of the first argument passed to `with_collection` isn't a valid collection. " \
|
87
|
+
"Make sure it responds to `to_ary`."
|
88
|
+
end
|
89
|
+
|
90
|
+
class ContentSlotNameError < StandardError
|
91
|
+
MESSAGE =
|
92
|
+
"COMPONENT declares a slot named content, which is a reserved word in ViewComponent.\n\n" \
|
93
|
+
"Content passed to a ViewComponent as a block is captured and assigned to the `content` accessor without having to create an explicit slot.\n\n" \
|
94
|
+
"To fix this issue, either use the `content` accessor directly or choose a different slot name."
|
95
|
+
|
96
|
+
def initialize(klass_name)
|
97
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class InvalidSlotDefinitionError < BaseError
|
102
|
+
MESSAGE =
|
103
|
+
"Invalid slot definition. Please pass a class, " \
|
104
|
+
"string, or callable (that is proc, lambda, etc)"
|
105
|
+
end
|
106
|
+
|
107
|
+
class SlotPredicateNameError < StandardError
|
108
|
+
MESSAGE =
|
109
|
+
"COMPONENT declares a slot named SLOT_NAME, which ends with a question mark.\n\n" \
|
110
|
+
"This isn't allowed because the ViewComponent framework already provides predicate " \
|
111
|
+
"methods ending in `?`.\n\n" \
|
112
|
+
"To fix this issue, choose a different name."
|
113
|
+
|
114
|
+
def initialize(klass_name, slot_name)
|
115
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class RedefinedSlotError < StandardError
|
120
|
+
MESSAGE =
|
121
|
+
"COMPONENT declares the SLOT_NAME slot multiple times.\n\n" \
|
122
|
+
"To fix this issue, choose a different slot name."
|
123
|
+
|
124
|
+
def initialize(klass_name, slot_name)
|
125
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class ReservedSingularSlotNameError < StandardError
|
130
|
+
MESSAGE =
|
131
|
+
"COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
|
132
|
+
"To fix this issue, choose a different name."
|
133
|
+
|
134
|
+
def initialize(klass_name, slot_name)
|
135
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class ReservedPluralSlotNameError < StandardError
|
140
|
+
MESSAGE =
|
141
|
+
"COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \
|
142
|
+
"To fix this issue, choose a different name."
|
143
|
+
|
144
|
+
def initialize(klass_name, slot_name)
|
145
|
+
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s))
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class ContentAlreadySetForPolymorphicSlotError < StandardError
|
150
|
+
MESSAGE = "Content for slot SLOT_NAME has already been provided."
|
151
|
+
|
152
|
+
def initialize(slot_name)
|
153
|
+
super(MESSAGE.gsub("SLOT_NAME", slot_name.to_s))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class NilWithContentError < BaseError
|
158
|
+
MESSAGE =
|
159
|
+
"No content provided to `#with_content` for #{self}.\n\n" \
|
160
|
+
"To fix this issue, pass a value."
|
161
|
+
end
|
162
|
+
|
163
|
+
class TranslateCalledBeforeRenderError < BaseError
|
164
|
+
MESSAGE =
|
165
|
+
"`#translate` can't be used during initialization as it depends " \
|
166
|
+
"on the view context that only exists once a ViewComponent is passed to " \
|
167
|
+
"the Rails render pipeline.\n\n" \
|
168
|
+
"It's sometimes possible to fix this issue by moving code dependent on " \
|
169
|
+
"`#translate` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
|
170
|
+
end
|
171
|
+
|
172
|
+
class HelpersCalledBeforeRenderError < BaseError
|
173
|
+
MESSAGE =
|
174
|
+
"`#helpers` can't be used during initialization as it depends " \
|
175
|
+
"on the view context that only exists once a ViewComponent is passed to " \
|
176
|
+
"the Rails render pipeline.\n\n" \
|
177
|
+
"It's sometimes possible to fix this issue by moving code dependent on " \
|
178
|
+
"`#helpers` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
|
179
|
+
end
|
180
|
+
|
181
|
+
class ControllerCalledBeforeRenderError < BaseError
|
182
|
+
MESSAGE =
|
183
|
+
"`#controller` can't be used during initialization, as it depends " \
|
184
|
+
"on the view context that only exists once a ViewComponent is passed to " \
|
185
|
+
"the Rails render pipeline.\n\n" \
|
186
|
+
"It's sometimes possible to fix this issue by moving code dependent on " \
|
187
|
+
"`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
|
188
|
+
end
|
189
|
+
|
190
|
+
class NoMatchingTemplatesForPreviewError < StandardError
|
191
|
+
MESSAGE = "Found 0 matches for templates for TEMPLATE_IDENTIFIER."
|
192
|
+
|
193
|
+
def initialize(template_identifier)
|
194
|
+
super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class MultipleMatchingTemplatesForPreviewError < StandardError
|
199
|
+
MESSAGE = "Found multiple templates for TEMPLATE_IDENTIFIER."
|
200
|
+
|
201
|
+
def initialize(template_identifier)
|
202
|
+
super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class SystemTestControllerOnlyAllowedInTestError < BaseError
|
207
|
+
MESSAGE = "ViewComponent SystemTest controller must only be called in a test environment for security reasons."
|
208
|
+
end
|
209
|
+
|
210
|
+
class SystemTestControllerNefariousPathError < BaseError
|
211
|
+
MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent # :nodoc:
|
4
|
+
module InlineTemplate
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
Template = Struct.new(:source, :language, :path, :lineno)
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def method_missing(method, *args)
|
10
|
+
return super if !method.end_with?("_template")
|
11
|
+
|
12
|
+
if defined?(@__vc_inline_template_defined) && @__vc_inline_template_defined
|
13
|
+
raise MultipleInlineTemplatesError
|
14
|
+
end
|
15
|
+
|
16
|
+
if args.size != 1
|
17
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)"
|
18
|
+
end
|
19
|
+
|
20
|
+
ext = method.to_s.gsub("_template", "")
|
21
|
+
template = args.first
|
22
|
+
|
23
|
+
@__vc_inline_template_language = ext
|
24
|
+
|
25
|
+
caller = caller_locations(1..1)[0]
|
26
|
+
@__vc_inline_template = Template.new(
|
27
|
+
template,
|
28
|
+
ext,
|
29
|
+
caller.absolute_path || caller.path,
|
30
|
+
caller.lineno
|
31
|
+
)
|
32
|
+
|
33
|
+
@__vc_inline_template_defined = true
|
34
|
+
end
|
35
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
36
|
+
|
37
|
+
def respond_to_missing?(method, include_all = false)
|
38
|
+
method.end_with?("_template") || super
|
39
|
+
end
|
40
|
+
|
41
|
+
def inline_template
|
42
|
+
@__vc_inline_template
|
43
|
+
end
|
44
|
+
|
45
|
+
def inline_template_language
|
46
|
+
@__vc_inline_template_language if defined?(@__vc_inline_template_language)
|
47
|
+
end
|
48
|
+
|
49
|
+
def inherited(subclass)
|
50
|
+
super
|
51
|
+
subclass.instance_variable_set(:@__vc_inline_template_language, inline_template_language)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -80,13 +80,7 @@ module ViewComponent # :nodoc:
|
|
80
80
|
Dir["#{path}/#{preview_name}_preview/#{example}.html.*"].first
|
81
81
|
end
|
82
82
|
|
83
|
-
if preview_path.nil?
|
84
|
-
raise(
|
85
|
-
PreviewTemplateError,
|
86
|
-
"A preview template for example #{example} doesn't exist.\n\n" \
|
87
|
-
"To fix this issue, create a template for the example."
|
88
|
-
)
|
89
|
-
end
|
83
|
+
raise MissingPreviewTemplateError.new(example) if preview_path.nil?
|
90
84
|
|
91
85
|
path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
|
92
86
|
Pathname.new(path)
|