view_component 4.15.0 → 4.16.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/docs/CHANGELOG.md +40 -0
- data/lib/view_component/base.rb +66 -22
- data/lib/view_component/cache_digest/dependency_tracking.rb +0 -5
- data/lib/view_component/cache_digest/resolver.rb +0 -5
- data/lib/view_component/cache_digest.rb +15 -6
- data/lib/view_component/collection.rb +19 -16
- data/lib/view_component/compiler.rb +2 -0
- data/lib/view_component/engine.rb +1 -0
- data/lib/view_component/experimentally_cacheable.rb +32 -4
- data/lib/view_component/instrumentation.rb +5 -1
- data/lib/view_component/request_details.rb +15 -2
- data/lib/view_component/template.rb +29 -19
- data/lib/view_component/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6b893658a7831510ca06effa1b9abb1d50669a6910c31049f4225a164987e858
|
|
4
|
+
data.tar.gz: c4e55c9e7f4e49117c8abbaeaafb077c535be72d3b78775283e8599110cd47aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4d1ad504f345a9eff5088d22f34955d9a0d95f6990d16923e373f264c189ed8871a8551ec039985650251e330f22c752d7cb04a8bc1e0ddfc40dfde42148947
|
|
7
|
+
data.tar.gz: 05a6b4ace5d3bbb6fede8ea9f8d93a1291d1222f998d8cce3158133167430c06d5371cc2fdf7794dc2e432bde2ec650a0edeccea0465eabfb2dad6589289acd2
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,46 @@ nav_order: 6
|
|
|
10
10
|
|
|
11
11
|
## main
|
|
12
12
|
|
|
13
|
+
## 4.16.0
|
|
14
|
+
|
|
15
|
+
* Fix subclasses defined after their parent renders a collection using the parent's default collection parameter name instead of their own. Fix recompiling a component not refreshing its collection parameter, counter, and iteration detection. Compute that detection when a component compiles, and store the results frozen so they're shareable across Ractors.
|
|
16
|
+
|
|
17
|
+
*Ben Sheldon*
|
|
18
|
+
|
|
19
|
+
* Fix collection counter and iteration detection, and collection and reserved parameter validation, when `initialize` arguments are forwarded through anonymous arguments (`...` or `**`), such as by prepended modules and subclasses calling `super`.
|
|
20
|
+
|
|
21
|
+
*Ben Sheldon*
|
|
22
|
+
|
|
23
|
+
* Update development and documentation dependencies, baseline existing Audition findings, and accommodate updated lint rules.
|
|
24
|
+
|
|
25
|
+
*Joel Hawksley*
|
|
26
|
+
|
|
27
|
+
* Invalidate Action View's memoized template digests when a component registers with `ViewComponent::CacheDigest`, so a digest computed before the component loaded isn't served for the rest of the process.
|
|
28
|
+
|
|
29
|
+
*Erik Axel Nielsen*
|
|
30
|
+
|
|
31
|
+
* Raise errors encountered while computing a component's cache digest instead of degrading to an untracked component that can serve stale fragments.
|
|
32
|
+
|
|
33
|
+
*Erik Axel Nielsen*
|
|
34
|
+
|
|
35
|
+
* Give `<% cache %>` blocks inside a component's own template a digest, for components that `include ViewComponent::ExperimentallyCacheable`.
|
|
36
|
+
|
|
37
|
+
Rails digests the virtual path of whichever template is rendering. Inside a component that path resolves to no template, because component templates aren't in the view paths, so the Digestor returned an empty digest and the fragment was never invalidated. The only signal was a `Couldn't find template for digesting` line in the log. 4.15.0 fixed the case where the `cache` block wraps the component in a view. This fixes the case where the block sits in the component's template.
|
|
38
|
+
|
|
39
|
+
*Erik Axel Nielsen*
|
|
40
|
+
|
|
41
|
+
* Fix line numbers and, under coverage, missing output for ERB templates that Rails doesn't annotate.
|
|
42
|
+
|
|
43
|
+
On Rails 8.1+, ViewComponent compensated for the newline Rails adds to compiled ERB output ([rails/rails#53731](https://github.com/rails/rails/pull/53731)) for every ERB template, whether from a file or from `erb_template`. That newline lives inside the `<!-- BEGIN ... -->` annotation, which Rails only emits when `annotate_rendered_view_with_filenames` is enabled *and* the template's format is HTML. Compensating unconditionally shifted backtraces for non-HTML templates (`.text.erb`, `.css.erb`) and for every ERB template when annotations are disabled, such as in production, by one line. When coverage was running, the same mismatch made the annotation-stripping workaround remove real template source, so non-HTML templates rendered empty.
|
|
44
|
+
|
|
45
|
+
Inline templates now decide the compensation when they compile rather than when the component class is defined, matching file templates.
|
|
46
|
+
|
|
47
|
+
*Ryutaro Mizokami*
|
|
48
|
+
|
|
49
|
+
* Reduce per-render allocations. Inline renders drop 2 to 3 allocations and collection renders drop 4 to 8 depending on Rails/Ruby version by caching the instrumentation-enabled flag at the module level, memoizing the empty-details `Requested` per `LookupContext`, hoisting per-item metadata lookups out of the collection render loop, and dropping a few gratuitous `**` splats on the `Collection` API boundary.
|
|
50
|
+
|
|
51
|
+
*Joel Hawksley*
|
|
52
|
+
|
|
13
53
|
## 4.15.0
|
|
14
54
|
|
|
15
55
|
* Add experimental caching support, opt-in per component via `include ViewComponent::ExperimentallyCacheable`.
|
data/lib/view_component/base.rb
CHANGED
|
@@ -631,7 +631,7 @@ module ViewComponent
|
|
|
631
631
|
# @param spacer_component [ViewComponent::Base] Component instance to be rendered between items.
|
|
632
632
|
# @param args [Arguments] Arguments to pass to the ViewComponent every time.
|
|
633
633
|
def with_collection(collection, spacer_component: nil, **args)
|
|
634
|
-
Collection.new(self, collection, spacer_component,
|
|
634
|
+
Collection.new(self, collection, spacer_component, args)
|
|
635
635
|
end
|
|
636
636
|
|
|
637
637
|
# @private
|
|
@@ -716,7 +716,7 @@ module ViewComponent
|
|
|
716
716
|
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
|
717
717
|
def with_collection_parameter(parameter)
|
|
718
718
|
@__vc_provided_collection_parameter = parameter
|
|
719
|
-
@
|
|
719
|
+
@__vc_collection_arguments = nil
|
|
720
720
|
end
|
|
721
721
|
|
|
722
722
|
# Strips trailing whitespace from templates before compiling them.
|
|
@@ -746,10 +746,14 @@ module ViewComponent
|
|
|
746
746
|
# rendering is optional.
|
|
747
747
|
# @private
|
|
748
748
|
def __vc_validate_collection_parameter!(validate_default: false)
|
|
749
|
-
|
|
749
|
+
if validate_default
|
|
750
|
+
return if __vc_collection_arguments[:parameter_accepted]
|
|
750
751
|
|
|
751
|
-
|
|
752
|
-
|
|
752
|
+
raise MissingCollectionArgumentError.new(name, __vc_collection_parameter)
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
parameter = __vc_provided_collection_parameter
|
|
756
|
+
return if __vc_collection_parameter_accepted?(parameter)
|
|
753
757
|
|
|
754
758
|
raise MissingCollectionArgumentError.new(name, parameter)
|
|
755
759
|
end
|
|
@@ -766,51 +770,91 @@ module ViewComponent
|
|
|
766
770
|
|
|
767
771
|
# @private
|
|
768
772
|
def __vc_collection_parameter
|
|
769
|
-
|
|
773
|
+
__vc_collection_arguments[:parameter]
|
|
770
774
|
end
|
|
771
775
|
|
|
772
776
|
# @private
|
|
773
777
|
def __vc_collection_counter_parameter
|
|
774
|
-
|
|
778
|
+
__vc_collection_arguments[:counter_parameter]
|
|
775
779
|
end
|
|
776
780
|
|
|
777
781
|
# @private
|
|
778
782
|
def __vc_counter_argument_present?
|
|
779
|
-
|
|
783
|
+
__vc_collection_arguments[:counter]
|
|
780
784
|
end
|
|
781
785
|
|
|
782
786
|
# @private
|
|
783
787
|
def __vc_collection_iteration_parameter
|
|
784
|
-
|
|
788
|
+
__vc_collection_arguments[:iteration_parameter]
|
|
785
789
|
end
|
|
786
790
|
|
|
787
791
|
# @private
|
|
788
792
|
def __vc_iteration_argument_present?
|
|
789
|
-
|
|
793
|
+
__vc_collection_arguments[:iteration]
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
# @private
|
|
797
|
+
def __vc_build_collection_arguments
|
|
798
|
+
names =
|
|
799
|
+
if respond_to?(:attribute_names)
|
|
800
|
+
attribute_names.map(&:to_sym)
|
|
801
|
+
else
|
|
802
|
+
__vc_initialize_signatures.flat_map { |_owner, parameters| parameters.map(&:last) }
|
|
803
|
+
end
|
|
804
|
+
parameter = __vc_provided_collection_parameter || (name && name.demodulize.underscore.chomp("_component").to_sym)
|
|
805
|
+
counter_parameter = :"#{parameter}_counter"
|
|
806
|
+
iteration_parameter = :"#{parameter}_iteration"
|
|
807
|
+
|
|
808
|
+
@__vc_collection_arguments = Ractor.make_shareable(
|
|
809
|
+
{
|
|
810
|
+
initialize_parameter_names: names,
|
|
811
|
+
parameter: parameter,
|
|
812
|
+
parameter_accepted: parameter.nil? || names.include?(parameter) || __vc_splatted_keyword_argument_present?,
|
|
813
|
+
counter_parameter: counter_parameter,
|
|
814
|
+
counter: names.include?(counter_parameter),
|
|
815
|
+
iteration_parameter: iteration_parameter,
|
|
816
|
+
iteration: names.include?(iteration_parameter)
|
|
817
|
+
}
|
|
818
|
+
)
|
|
790
819
|
end
|
|
791
820
|
|
|
792
821
|
private
|
|
793
822
|
|
|
794
|
-
def
|
|
795
|
-
|
|
823
|
+
def __vc_collection_arguments
|
|
824
|
+
@__vc_collection_arguments || __vc_build_collection_arguments
|
|
796
825
|
end
|
|
797
826
|
|
|
798
|
-
def
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
827
|
+
def __vc_collection_parameter_accepted?(parameter)
|
|
828
|
+
parameter.nil? || __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
def __vc_splatted_keyword_argument_present?
|
|
832
|
+
prepended_modules = ancestors.grep(Class).flat_map { |klass| klass.ancestors.take_while { |mod| mod != klass } }
|
|
833
|
+
|
|
834
|
+
__vc_initialize_signatures.any? do |owner, parameters|
|
|
835
|
+
parameters.any? do |type, name|
|
|
836
|
+
type == :keyrest && (name != :** || !prepended_modules.include?(owner))
|
|
804
837
|
end
|
|
838
|
+
end
|
|
805
839
|
end
|
|
806
840
|
|
|
807
|
-
def
|
|
808
|
-
|
|
841
|
+
def __vc_initialize_parameter_names
|
|
842
|
+
__vc_collection_arguments[:initialize_parameter_names]
|
|
809
843
|
end
|
|
810
844
|
|
|
811
|
-
def
|
|
812
|
-
|
|
845
|
+
def __vc_initialize_signatures
|
|
846
|
+
signatures = []
|
|
847
|
+
method = instance_method(:initialize)
|
|
848
|
+
while method
|
|
849
|
+
signatures << [method.owner, method.parameters]
|
|
850
|
+
break unless method.parameters.any? { |type, _| type == :keyrest }
|
|
851
|
+
|
|
852
|
+
method = method.super_method
|
|
853
|
+
end
|
|
854
|
+
signatures
|
|
813
855
|
end
|
|
856
|
+
|
|
857
|
+
attr_reader :__vc_provided_collection_parameter
|
|
814
858
|
end
|
|
815
859
|
|
|
816
860
|
ActiveSupport.run_load_hooks(:view_component, self)
|
|
@@ -27,11 +27,6 @@ module ViewComponent
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
dependencies + CacheDigest.dependencies_in(template)
|
|
30
|
-
rescue
|
|
31
|
-
# A broken digest is preferable to a broken render. Falling back to the
|
|
32
|
-
# dependencies Rails found on its own means the component simply isn't
|
|
33
|
-
# tracked, which is the pre-existing behavior.
|
|
34
|
-
super
|
|
35
30
|
end
|
|
36
31
|
|
|
37
32
|
# @private
|
|
@@ -30,11 +30,6 @@ module ViewComponent
|
|
|
30
30
|
return [] unless component
|
|
31
31
|
|
|
32
32
|
[build_template(component, virtual_path, details)]
|
|
33
|
-
rescue
|
|
34
|
-
# Never let digest resolution break rendering. Returning no template
|
|
35
|
-
# makes the Digestor treat this as a missing node, which degrades to
|
|
36
|
-
# the behavior components have without this feature.
|
|
37
|
-
[]
|
|
38
33
|
end
|
|
39
34
|
|
|
40
35
|
def to_s
|
|
@@ -75,8 +75,16 @@ module ViewComponent
|
|
|
75
75
|
# @private
|
|
76
76
|
def register(component)
|
|
77
77
|
return unless component.virtual_path && component.name
|
|
78
|
+
return if registry[component.virtual_path] == component.name
|
|
78
79
|
|
|
79
80
|
registry[component.virtual_path] = component.name
|
|
81
|
+
|
|
82
|
+
# Components register as they load, and under lazy loading that happens
|
|
83
|
+
# after Action View has already digested and memoized some templates.
|
|
84
|
+
# Those digests were computed without this component's dependencies and
|
|
85
|
+
# would otherwise be served for the rest of the process, making the
|
|
86
|
+
# digest a function of load order rather than of source.
|
|
87
|
+
expire_digests
|
|
80
88
|
end
|
|
81
89
|
|
|
82
90
|
# The synthetic virtual path a component is digested under.
|
|
@@ -146,9 +154,6 @@ module ViewComponent
|
|
|
146
154
|
RENDER_PARSER.new(name, source).render_calls.uniq.select do |path|
|
|
147
155
|
source.include?(path) || source.include?(path.sub(%r{(\A|/)_}, '\1'))
|
|
148
156
|
end
|
|
149
|
-
rescue
|
|
150
|
-
# Never let digest computation break rendering.
|
|
151
|
-
[]
|
|
152
157
|
end
|
|
153
158
|
|
|
154
159
|
# Action View has shipped its render parser as a class (Rails 7.1, and
|
|
@@ -223,6 +228,13 @@ module ViewComponent
|
|
|
223
228
|
|
|
224
229
|
private
|
|
225
230
|
|
|
231
|
+
# Drop Action View's memoized template digests, leaving its resolver
|
|
232
|
+
# caches alone: no template changed, only the set of dependencies the
|
|
233
|
+
# Digestor can see.
|
|
234
|
+
def expire_digests
|
|
235
|
+
ActionView::LookupContext::DetailsKey.digest_caches.each(&:clear)
|
|
236
|
+
end
|
|
237
|
+
|
|
226
238
|
# Resolve a constant name to a component that opted into caching.
|
|
227
239
|
#
|
|
228
240
|
# Returns nil for anything else, including constants that don't exist.
|
|
@@ -234,9 +246,6 @@ module ViewComponent
|
|
|
234
246
|
return unless component.respond_to?(:__vc_cacheable?) && component.__vc_cacheable?
|
|
235
247
|
|
|
236
248
|
component
|
|
237
|
-
rescue
|
|
238
|
-
# Never let digest computation break rendering.
|
|
239
|
-
nil
|
|
240
249
|
end
|
|
241
250
|
end
|
|
242
251
|
|
|
@@ -12,6 +12,9 @@ module ViewComponent
|
|
|
12
12
|
|
|
13
13
|
delegate :size, to: :@collection
|
|
14
14
|
|
|
15
|
+
EMPTY_SPACER = "".html_safe.freeze
|
|
16
|
+
private_constant :EMPTY_SPACER
|
|
17
|
+
|
|
15
18
|
def render_in(view_context, **_, &block)
|
|
16
19
|
rendered = components.map! do |component|
|
|
17
20
|
component.render_in(view_context, &block)
|
|
@@ -36,18 +39,27 @@ module ViewComponent
|
|
|
36
39
|
# Always rebuild child component instances per render to avoid leaking
|
|
37
40
|
# request-scoped state from a previous render into a later one (GHSA).
|
|
38
41
|
def components
|
|
39
|
-
iterator = ActionView::PartialIteration.new(@collection.size)
|
|
40
|
-
|
|
41
42
|
component.__vc_validate_collection_parameter!(validate_default: true)
|
|
42
43
|
|
|
44
|
+
iterator = ActionView::PartialIteration.new(@collection.size)
|
|
45
|
+
collection_param = component.__vc_collection_parameter
|
|
46
|
+
counter_present = component.__vc_counter_argument_present?
|
|
47
|
+
counter_param = component.__vc_collection_counter_parameter if counter_present
|
|
48
|
+
iteration_present = component.__vc_iteration_argument_present?
|
|
49
|
+
iteration_param = component.__vc_collection_iteration_parameter if iteration_present
|
|
50
|
+
item_options = @options.dup
|
|
51
|
+
|
|
43
52
|
@collection.map do |item|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
item_options[collection_param] = item
|
|
54
|
+
item_options[counter_param] = iterator.index if counter_present
|
|
55
|
+
item_options[iteration_param] = iterator.dup if iteration_present
|
|
56
|
+
instance = component.new(**item_options)
|
|
57
|
+
iterator.iterate!
|
|
58
|
+
instance
|
|
47
59
|
end
|
|
48
60
|
end
|
|
49
61
|
|
|
50
|
-
def initialize(component, object, spacer_component,
|
|
62
|
+
def initialize(component, object, spacer_component, options = {})
|
|
51
63
|
@component = component
|
|
52
64
|
@collection = collection_variable(object || [])
|
|
53
65
|
@spacer_component = spacer_component
|
|
@@ -62,20 +74,11 @@ module ViewComponent
|
|
|
62
74
|
end
|
|
63
75
|
end
|
|
64
76
|
|
|
65
|
-
def component_options(item, iterator)
|
|
66
|
-
item_options = @options.dup
|
|
67
|
-
item_options[component.__vc_collection_parameter] = item
|
|
68
|
-
item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present?
|
|
69
|
-
item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present?
|
|
70
|
-
|
|
71
|
-
item_options
|
|
72
|
-
end
|
|
73
|
-
|
|
74
77
|
# Render the spacer through a fresh `dup` so a collection rendered multiple
|
|
75
78
|
# times does not reuse (and trip the single-render guard on) the spacer
|
|
76
79
|
# instance passed by the caller.
|
|
77
80
|
def rendered_spacer(view_context)
|
|
78
|
-
return
|
|
81
|
+
return EMPTY_SPACER unless @spacer_component
|
|
79
82
|
|
|
80
83
|
spacer = @spacer_component.dup
|
|
81
84
|
if spacer.instance_variable_defined?(:@__vc_rendered)
|
|
@@ -28,6 +28,7 @@ module ViewComponent
|
|
|
28
28
|
initializer "view_component.enable_instrumentation" do |app|
|
|
29
29
|
ActiveSupport.on_load(:view_component) do
|
|
30
30
|
if app.config.view_component.instrumentation_enabled.present?
|
|
31
|
+
ViewComponent::Instrumentation.enabled = true
|
|
31
32
|
ViewComponent::Base.prepend(ViewComponent::Instrumentation)
|
|
32
33
|
end
|
|
33
34
|
end
|
|
@@ -12,9 +12,10 @@ module ViewComponent
|
|
|
12
12
|
# Including this module does two things:
|
|
13
13
|
#
|
|
14
14
|
# 1. Registers the component with Rails' template digest tree, so a
|
|
15
|
-
# `<% cache %>` block
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
# `<% cache %>` block is invalidated when the component's template, Ruby
|
|
16
|
+
# class, sidecar files, or child components change. This covers blocks
|
|
17
|
+
# wrapping the component in a view and blocks inside the component's own
|
|
18
|
+
# template.
|
|
18
19
|
# 2. Enables the `cache_on` macro, which caches the component's own rendered
|
|
19
20
|
# output.
|
|
20
21
|
#
|
|
@@ -185,7 +186,7 @@ module ViewComponent
|
|
|
185
186
|
if (cached = store.read(key))
|
|
186
187
|
# Safe to mark as HTML-safe: the cached string was produced by this same
|
|
187
188
|
# rendering pipeline, which escapes output before it's written.
|
|
188
|
-
return cached.html_safe
|
|
189
|
+
return cached.html_safe
|
|
189
190
|
end
|
|
190
191
|
|
|
191
192
|
super.tap do |output|
|
|
@@ -225,6 +226,33 @@ module ViewComponent
|
|
|
225
226
|
)
|
|
226
227
|
end
|
|
227
228
|
|
|
229
|
+
# The digest Rails mixes into the key of a `<% cache %>` block.
|
|
230
|
+
#
|
|
231
|
+
# `ActionView::Helpers::CacheHelper` digests the virtual path of whichever
|
|
232
|
+
# template is rendering. Inside a component that path is the component's
|
|
233
|
+
# own, which resolves to nothing: component templates aren't in the view
|
|
234
|
+
# paths. The Digestor returns an empty digest, `CacheHelper` falls back to
|
|
235
|
+
# the bare virtual path, and the fragment never invalidates.
|
|
236
|
+
#
|
|
237
|
+
# Substituting the digest the component already computes makes a `cache`
|
|
238
|
+
# block in a component template behave like one in a view. Everything else
|
|
239
|
+
# the component renders — a partial, say — keeps Rails' behavior.
|
|
240
|
+
#
|
|
241
|
+
# @private
|
|
242
|
+
def digest_path_from_template(template)
|
|
243
|
+
component_path = self.class.virtual_path
|
|
244
|
+
return super unless component_path && template.virtual_path == component_path
|
|
245
|
+
|
|
246
|
+
digest = self.class.cache_digest(
|
|
247
|
+
finder: lookup_context,
|
|
248
|
+
format: template.format || __vc_cache_format(lookup_context)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# An empty digest means the component couldn't be resolved. Falling back
|
|
252
|
+
# to the bare virtual path matches what `CacheHelper` does with one.
|
|
253
|
+
digest.present? ? "#{component_path}:#{digest}" : component_path
|
|
254
|
+
end
|
|
255
|
+
|
|
228
256
|
private
|
|
229
257
|
|
|
230
258
|
# Slots set by the caller via `with_*`. Checked before rendering, so slots
|
|
@@ -8,8 +8,12 @@ module ViewComponent # :nodoc:
|
|
|
8
8
|
mod.prepend(self) unless self <= ViewComponent::Instrumentation
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
class << self
|
|
12
|
+
attr_accessor :enabled
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def render_in(...)
|
|
12
|
-
return super
|
|
16
|
+
return super unless Instrumentation.enabled
|
|
13
17
|
|
|
14
18
|
payload = {
|
|
15
19
|
name: self.class.name,
|
|
@@ -24,8 +24,21 @@ module ViewComponent
|
|
|
24
24
|
# The hash `user_details` would normally be the standard arguments that
|
|
25
25
|
# `render` accepts, but there's currently no mechanism for users to
|
|
26
26
|
# provide these when calling render on a ViewComponent.
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
if user_details.equal?(EMPTY_DETAILS)
|
|
28
|
+
# Fast path: memoize the empty-details Requested per LookupContext.
|
|
29
|
+
# Rendered many times with the same context, the tuple/Requested
|
|
30
|
+
# allocations from ActionView are then paid at most once.
|
|
31
|
+
cached = instance_variable_defined?(:@__vc_requested_details_cache) &&
|
|
32
|
+
@__vc_requested_details_cache
|
|
33
|
+
return cached if cached
|
|
34
|
+
|
|
35
|
+
details, from_cache = detail_args_for(EMPTY_DETAILS)
|
|
36
|
+
@__vc_requested_details_cache =
|
|
37
|
+
from_cache || ActionView::TemplateDetails::Requested.new(**details)
|
|
38
|
+
else
|
|
39
|
+
details, from_cache = detail_args_for(user_details)
|
|
40
|
+
from_cache || ActionView::TemplateDetails::Requested.new(**details)
|
|
41
|
+
end
|
|
29
42
|
end
|
|
30
43
|
end
|
|
31
44
|
end
|
|
@@ -60,13 +60,7 @@ module ViewComponent
|
|
|
60
60
|
# Strip the annotation line to maintain correct line numbers when coverage
|
|
61
61
|
# is running (avoids segfault from negative lineno)
|
|
62
62
|
def strip_annotation_line?
|
|
63
|
-
erb_newline_compensation_needed? &&
|
|
64
|
-
coverage_running? &&
|
|
65
|
-
ActionView::Base.annotate_rendered_view_with_filenames
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def erb_newline_compensation_needed?
|
|
69
|
-
Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
|
|
63
|
+
erb_newline_compensation_needed? && coverage_running?
|
|
70
64
|
end
|
|
71
65
|
|
|
72
66
|
def compiled_source
|
|
@@ -82,22 +76,11 @@ module ViewComponent
|
|
|
82
76
|
def initialize(component:, inline_template:)
|
|
83
77
|
details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, nil, nil)
|
|
84
78
|
|
|
85
|
-
# Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
|
|
86
|
-
# Subtract 1 to compensate for correct line numbers in stack traces.
|
|
87
|
-
# Inline templates start at line 2+ (defined inside a class), so this
|
|
88
|
-
# won't result in negative line numbers that cause segfaults with coverage.
|
|
89
|
-
lineno =
|
|
90
|
-
if Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 && details.handler == :erb
|
|
91
|
-
inline_template.lineno - 1
|
|
92
|
-
else
|
|
93
|
-
inline_template.lineno
|
|
94
|
-
end
|
|
95
|
-
|
|
96
79
|
super(
|
|
97
80
|
component: component,
|
|
98
81
|
details: details,
|
|
99
82
|
path: inline_template.path,
|
|
100
|
-
lineno: lineno,
|
|
83
|
+
lineno: inline_template.lineno,
|
|
101
84
|
)
|
|
102
85
|
|
|
103
86
|
@source = inline_template.source.dup
|
|
@@ -106,6 +89,19 @@ module ViewComponent
|
|
|
106
89
|
def type
|
|
107
90
|
:inline
|
|
108
91
|
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Rails 8.1 added a newline to compiled ERB output (rails/rails#53731).
|
|
96
|
+
# Subtract 1 to compensate for correct line numbers in stack traces.
|
|
97
|
+
# Inline templates start at line 2+ (defined inside a class), so this
|
|
98
|
+
# won't result in negative line numbers that cause segfaults with coverage.
|
|
99
|
+
#
|
|
100
|
+
# Computed at compile time rather than at initialization because the
|
|
101
|
+
# annotation setting can change between the two.
|
|
102
|
+
def lineno
|
|
103
|
+
erb_newline_compensation_needed? ? super - 1 : super
|
|
104
|
+
end
|
|
109
105
|
end
|
|
110
106
|
|
|
111
107
|
class InlineCall < Template
|
|
@@ -196,6 +192,20 @@ module ViewComponent
|
|
|
196
192
|
|
|
197
193
|
attr_reader :lineno
|
|
198
194
|
|
|
195
|
+
def erb_newline_compensation_needed?
|
|
196
|
+
Rails::VERSION::MAJOR >= 8 && Rails::VERSION::MINOR > 0 &&
|
|
197
|
+
details.handler == :erb &&
|
|
198
|
+
annotated?
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The newline compensated for lives inside the `<!-- BEGIN ... -->`
|
|
202
|
+
# annotation, which Rails only prepends to HTML templates, and only when
|
|
203
|
+
# annotations are enabled. Compensating outside those conditions shifts
|
|
204
|
+
# backtraces by a line and, under coverage, strips real template source.
|
|
205
|
+
def annotated?
|
|
206
|
+
ActionView::Base.annotate_rendered_view_with_filenames && html?
|
|
207
|
+
end
|
|
208
|
+
|
|
199
209
|
def compiled_source
|
|
200
210
|
handler = details.handler_class
|
|
201
211
|
this_source = source
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: view_component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ViewComponent Team
|
|
@@ -142,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
142
142
|
- !ruby/object:Gem::Version
|
|
143
143
|
version: '0'
|
|
144
144
|
requirements: []
|
|
145
|
-
rubygems_version: 4.0.
|
|
145
|
+
rubygems_version: 4.0.20
|
|
146
146
|
specification_version: 4
|
|
147
147
|
summary: A framework for building reusable, testable & encapsulated view components
|
|
148
148
|
in Ruby on Rails.
|