view_component 3.0.0.rc1 → 3.0.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.
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 +22 -1
- data/docs/CHANGELOG.md +76 -0
- data/lib/view_component/base.rb +32 -11
- data/lib/view_component/capture_compatibility.rb +42 -0
- data/lib/view_component/compiler.rb +49 -11
- data/lib/view_component/config.rb +9 -1
- data/lib/view_component/engine.rb +12 -5
- data/lib/view_component/inline_template.rb +55 -0
- data/lib/view_component/rails/tasks/view_component.rake +1 -1
- data/lib/view_component/{slot_v2.rb → slot.rb} +1 -1
- data/lib/view_component/slotable.rb +361 -45
- data/lib/view_component/system_test_helpers.rb +5 -5
- data/lib/view_component/test_helpers.rb +56 -45
- data/lib/view_component/translatable.rb +33 -23
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +2 -0
- metadata +5 -5
- data/lib/view_component/polymorphic_slots.rb +0 -91
- data/lib/view_component/slotable_v2.rb +0 -336
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f88375c08b20eb604992d315362fbdf6e0654fd87d7766d1ac404daecb1b57c9
|
4
|
+
data.tar.gz: 5f37ce63b609f5ff542a7e7c4629c952e3a02fc4e2ffc83df28aee86ff5d40cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 425107ab124527b5199b4174437c17a0f2dae4447b77a9393f68dd8c7a88517e25a3100fa0ed4cf6190d7e3b38390c6fd5cf48718753a53169f72dfe50ae5d12
|
7
|
+
data.tar.gz: b1642a727d8a586d8dec7a2e7075b570f5ccb20cee0b3c41037ac0b119adc6261ca1d1fb1dea0558f4bb40a0a45ba73b111ce4816a85be6798e3fb2bfaf76daf
|
@@ -1,7 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
|
4
|
+
TEMP_DIR = FileUtils.mkdir_p("./tmp/view_components/").first
|
5
|
+
|
6
|
+
before_action :validate_test_env
|
7
|
+
before_action :validate_file_path
|
8
|
+
|
4
9
|
def system_test_entrypoint
|
5
|
-
render file:
|
10
|
+
render file: @path
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def validate_test_env
|
16
|
+
raise "ViewComponentsSystemTestController must only be called in a test environment" unless Rails.env.test?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Ensure that the file path is valid and doesn't target files outside
|
20
|
+
# the expected directory (e.g. via a path traversal or symlink attack)
|
21
|
+
def validate_file_path
|
22
|
+
base_path = ::File.realpath(TEMP_DIR)
|
23
|
+
@path = ::File.realpath(params.permit(:file)[:file], base_path)
|
24
|
+
unless @path.start_with?(base_path)
|
25
|
+
raise ArgumentError, "Invalid file path"
|
26
|
+
end
|
6
27
|
end
|
7
28
|
end
|
data/docs/CHANGELOG.md
CHANGED
@@ -10,6 +10,82 @@ nav_order: 5
|
|
10
10
|
|
11
11
|
## main
|
12
12
|
|
13
|
+
## v3.0.0.rc3
|
14
|
+
|
15
|
+
Run into an issue with this release candidate? [Let us know](https://github.com/ViewComponent/view_component/issues/1629).
|
16
|
+
|
17
|
+
* Fix typos in generator docs.
|
18
|
+
|
19
|
+
*Sascha Karnatz*
|
20
|
+
|
21
|
+
* Add `TestHelpers#vc_test_controller`.
|
22
|
+
|
23
|
+
*Joel Hawksley*
|
24
|
+
|
25
|
+
* Document `config.view_component.capture_compatibility_patch_enabled` as option for the known incompatibilities with Rails form helpers.
|
26
|
+
|
27
|
+
*Tobias L. Maier*
|
28
|
+
|
29
|
+
* Add support for experimental inline templates.
|
30
|
+
|
31
|
+
*Blake Williams*
|
32
|
+
|
33
|
+
* Expose `translate` and `t` I18n methods on component classes.
|
34
|
+
|
35
|
+
*Elia Schito*
|
36
|
+
|
37
|
+
* Protect against Arbitrary File Read edge case in `ViewComponentsSystemTestController`.
|
38
|
+
|
39
|
+
*Nick Malcolm*
|
40
|
+
|
41
|
+
## v3.0.0.rc2
|
42
|
+
|
43
|
+
Run into an issue with this release? [Let us know](https://github.com/ViewComponent/view_component/issues/1629).
|
44
|
+
|
45
|
+
* BREAKING: Rename `SlotV2` to `Slot` and `SlotableV2` to `Slotable`.
|
46
|
+
|
47
|
+
*Joel Hawksley*
|
48
|
+
|
49
|
+
* BREAKING: Incorporate `PolymorphicSlots` into `Slotable`. To migrate, remove any references to `PolymorphicSlots` as they are no longer necessary.
|
50
|
+
|
51
|
+
*Joel Hawksley*
|
52
|
+
|
53
|
+
* BREAKING: Rename private TestHelpers#controller, #build_controller, #request, and #preview_class to avoid conflicts. Note: While these methods were undocumented and marked as private, they was easily accessible in tests. As such, we're cautiously considering this to be a breaking change.
|
54
|
+
|
55
|
+
*Joel Hawksley*
|
56
|
+
|
57
|
+
* Avoid loading ActionView::Base during Rails initialization. Originally submitted in #1528.
|
58
|
+
|
59
|
+
*Jonathan del Strother*
|
60
|
+
|
61
|
+
* Improve documentation of known incompatibilities with Rails form helpers.
|
62
|
+
|
63
|
+
*Tobias L. Maier*
|
64
|
+
|
65
|
+
* Remove dependency on environment task from `view_component:statsetup`.
|
66
|
+
|
67
|
+
*Svetlin Simonyan*
|
68
|
+
|
69
|
+
* Add experimental `config.view_component.capture_compatibility_patch_enabled` option resolving rendering issues related to forms, capture, turbo frames, etc.
|
70
|
+
|
71
|
+
*Blake Williams*
|
72
|
+
|
73
|
+
* Add `#content?` method that indicates if content has been passed to component.
|
74
|
+
|
75
|
+
*Joel Hawksley*
|
76
|
+
|
77
|
+
* Added example of a custom preview controller.
|
78
|
+
|
79
|
+
*Graham Rogers*
|
80
|
+
|
81
|
+
* Add Krystal to list of companies using ViewComponent.
|
82
|
+
|
83
|
+
*Matt Bearman*
|
84
|
+
|
85
|
+
* Add Mon Ami to list of companies using ViewComponent.
|
86
|
+
|
87
|
+
*Ethan Lee-Tyson*
|
88
|
+
|
13
89
|
## 3.0.0.rc1
|
14
90
|
|
15
91
|
1,000+ days and 100+ releases later, the 200+ contributors to ViewComponent are proud to ship v3.0.0!
|
data/lib/view_component/base.rb
CHANGED
@@ -6,9 +6,8 @@ require "view_component/collection"
|
|
6
6
|
require "view_component/compile_cache"
|
7
7
|
require "view_component/compiler"
|
8
8
|
require "view_component/config"
|
9
|
-
require "view_component/polymorphic_slots"
|
10
9
|
require "view_component/preview"
|
11
|
-
require "view_component/
|
10
|
+
require "view_component/slotable"
|
12
11
|
require "view_component/translatable"
|
13
12
|
require "view_component/with_content_helper"
|
14
13
|
|
@@ -21,7 +20,7 @@ module ViewComponent
|
|
21
20
|
#
|
22
21
|
# @return [ViewComponent::Config]
|
23
22
|
def config
|
24
|
-
@config ||=
|
23
|
+
@config ||= ActiveSupport::OrderedOptions.new
|
25
24
|
end
|
26
25
|
|
27
26
|
# Replaces the entire config. You shouldn't need to use this directly
|
@@ -29,8 +28,7 @@ module ViewComponent
|
|
29
28
|
attr_writer :config
|
30
29
|
end
|
31
30
|
|
32
|
-
include ViewComponent::
|
33
|
-
include ViewComponent::SlotableV2
|
31
|
+
include ViewComponent::Slotable
|
34
32
|
include ViewComponent::Translatable
|
35
33
|
include ViewComponent::WithContentHelper
|
36
34
|
|
@@ -245,22 +243,40 @@ module ViewComponent
|
|
245
243
|
@request ||= controller.request if controller.respond_to?(:request)
|
246
244
|
end
|
247
245
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
246
|
+
# The content passed to the component instance as a block.
|
247
|
+
#
|
248
|
+
# @return [String]
|
252
249
|
def content
|
253
250
|
@__vc_content_evaluated = true
|
254
251
|
return @__vc_content if defined?(@__vc_content)
|
255
252
|
|
256
253
|
@__vc_content =
|
257
|
-
if
|
254
|
+
if __vc_render_in_block_provided?
|
258
255
|
view_context.capture(self, &@__vc_render_in_block)
|
259
|
-
elsif
|
256
|
+
elsif __vc_content_set_by_with_content_defined?
|
260
257
|
@__vc_content_set_by_with_content
|
261
258
|
end
|
262
259
|
end
|
263
260
|
|
261
|
+
# Whether `content` has been passed to the component.
|
262
|
+
#
|
263
|
+
# @return [Boolean]
|
264
|
+
def content?
|
265
|
+
__vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
|
266
|
+
end
|
267
|
+
|
268
|
+
private
|
269
|
+
|
270
|
+
attr_reader :view_context
|
271
|
+
|
272
|
+
def __vc_render_in_block_provided?
|
273
|
+
@view_context && @__vc_render_in_block
|
274
|
+
end
|
275
|
+
|
276
|
+
def __vc_content_set_by_with_content_defined?
|
277
|
+
defined?(@__vc_content_set_by_with_content)
|
278
|
+
end
|
279
|
+
|
264
280
|
def content_evaluated?
|
265
281
|
@__vc_content_evaluated
|
266
282
|
end
|
@@ -465,6 +481,11 @@ module ViewComponent
|
|
465
481
|
compiler.compiled?
|
466
482
|
end
|
467
483
|
|
484
|
+
# @private
|
485
|
+
def ensure_compiled
|
486
|
+
compile unless compiled?
|
487
|
+
end
|
488
|
+
|
468
489
|
# Compile templates to instance methods, assuming they haven't been compiled already.
|
469
490
|
#
|
470
491
|
# Do as much work as possible in this step, as doing so reduces the amount
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
# CaptureCompatibility is a module that patches #capture to fix issues
|
5
|
+
# related to ViewComponent and functionality that relies on `capture`
|
6
|
+
# like forms, capture itself, turbo frames, etc.
|
7
|
+
#
|
8
|
+
# This underlying incompatibility with ViewComponent and capture is
|
9
|
+
# that several features like forms keep a reference to the primary
|
10
|
+
# `ActionView::Base` instance which has its own @output_buffer. When
|
11
|
+
# `#capture` is called on the original `ActionView::Base` instance while
|
12
|
+
# evaluating a block from a ViewComponent the @output_buffer is overridden
|
13
|
+
# in the ActionView::Base instance, and *not* the component. This results
|
14
|
+
# in a double render due to `#capture` implementation details.
|
15
|
+
#
|
16
|
+
# To resolve the issue, we override `#capture` so that we can delegate
|
17
|
+
# the `capture` logic to the ViewComponent that created the block.
|
18
|
+
module CaptureCompatibility
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
alias_method :original_capture, :capture
|
22
|
+
end
|
23
|
+
|
24
|
+
base.prepend(InstanceMethods)
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
def capture(*args, &block)
|
29
|
+
# Handle blocks that originate from C code and raise, such as `&:method`
|
30
|
+
return original_capture(*args, &block) if block.source_location.nil?
|
31
|
+
|
32
|
+
block_context = block.binding.receiver
|
33
|
+
|
34
|
+
if block_context != self && block_context.class < ActionView::Base
|
35
|
+
block_context.original_capture(*args, &block)
|
36
|
+
else
|
37
|
+
original_capture(*args, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -51,24 +51,46 @@ module ViewComponent
|
|
51
51
|
component_class.validate_collection_parameter!
|
52
52
|
end
|
53
53
|
|
54
|
-
|
55
|
-
|
56
|
-
# as Ruby warns when redefining a method.
|
57
|
-
method_name = call_method_name(template[:variant])
|
54
|
+
if has_inline_template?
|
55
|
+
template = component_class.inline_template
|
58
56
|
|
59
57
|
redefinition_lock.synchronize do
|
60
|
-
component_class.silence_redefinition_of_method(
|
58
|
+
component_class.silence_redefinition_of_method("call")
|
61
59
|
# rubocop:disable Style/EvalWithLocation
|
62
|
-
component_class.class_eval <<-RUBY, template
|
63
|
-
def
|
64
|
-
#{
|
60
|
+
component_class.class_eval <<-RUBY, template.path, template.lineno
|
61
|
+
def call
|
62
|
+
#{compiled_inline_template(template)}
|
65
63
|
end
|
66
64
|
RUBY
|
67
65
|
# rubocop:enable Style/EvalWithLocation
|
66
|
+
|
67
|
+
component_class.silence_redefinition_of_method("render_template_for")
|
68
|
+
component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
69
|
+
def render_template_for(variant = nil)
|
70
|
+
call
|
71
|
+
end
|
72
|
+
RUBY
|
73
|
+
end
|
74
|
+
else
|
75
|
+
templates.each do |template|
|
76
|
+
# Remove existing compiled template methods,
|
77
|
+
# as Ruby warns when redefining a method.
|
78
|
+
method_name = call_method_name(template[:variant])
|
79
|
+
|
80
|
+
redefinition_lock.synchronize do
|
81
|
+
component_class.silence_redefinition_of_method(method_name)
|
82
|
+
# rubocop:disable Style/EvalWithLocation
|
83
|
+
component_class.class_eval <<-RUBY, template[:path], 0
|
84
|
+
def #{method_name}
|
85
|
+
#{compiled_template(template[:path])}
|
86
|
+
end
|
87
|
+
RUBY
|
88
|
+
# rubocop:enable Style/EvalWithLocation
|
89
|
+
end
|
68
90
|
end
|
69
|
-
end
|
70
91
|
|
71
|
-
|
92
|
+
define_render_template_for
|
93
|
+
end
|
72
94
|
|
73
95
|
component_class.build_i18n_backend
|
74
96
|
|
@@ -103,12 +125,16 @@ module ViewComponent
|
|
103
125
|
end
|
104
126
|
end
|
105
127
|
|
128
|
+
def has_inline_template?
|
129
|
+
component_class.respond_to?(:inline_template) && component_class.inline_template.present?
|
130
|
+
end
|
131
|
+
|
106
132
|
def template_errors
|
107
133
|
@__vc_template_errors ||=
|
108
134
|
begin
|
109
135
|
errors = []
|
110
136
|
|
111
|
-
if (templates + inline_calls).empty?
|
137
|
+
if (templates + inline_calls).empty? && !has_inline_template?
|
112
138
|
errors << "Couldn't find a template file or inline render method for #{component_class}."
|
113
139
|
end
|
114
140
|
|
@@ -216,9 +242,21 @@ module ViewComponent
|
|
216
242
|
end
|
217
243
|
end
|
218
244
|
|
245
|
+
def compiled_inline_template(template)
|
246
|
+
handler = ActionView::Template.handler_for_extension(template.language)
|
247
|
+
template.rstrip! if component_class.strip_trailing_whitespace?
|
248
|
+
|
249
|
+
compile_template(template.source, handler)
|
250
|
+
end
|
251
|
+
|
219
252
|
def compiled_template(file_path)
|
220
253
|
handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete("."))
|
221
254
|
template = File.read(file_path)
|
255
|
+
|
256
|
+
compile_template(template, handler)
|
257
|
+
end
|
258
|
+
|
259
|
+
def compile_template(template, handler)
|
222
260
|
template.rstrip! if component_class.strip_trailing_whitespace?
|
223
261
|
|
224
262
|
if handler.method(:call).parameters.length > 1
|
@@ -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
|
|
@@ -137,6 +138,13 @@ module ViewComponent
|
|
137
138
|
# A custom default layout used for the previews index page and individual
|
138
139
|
# previews.
|
139
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`.
|
140
148
|
|
141
149
|
def default_preview_paths
|
142
150
|
return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
|
@@ -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
|
|
@@ -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 ViewComponent::ComponentError, "inline templates can only be defined once per-component"
|
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
|