view_component 4.0.0.alpha6 → 4.0.0.alpha7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbca6043a0eebf7c6f63358c7b1c193ef59170b81a35eb9290f211d365bfb3cb
4
- data.tar.gz: 033db44d3dab35956b7ab8ac1a1a12673b7e82a0d4e0bbf3d3e791234dbcf757
3
+ metadata.gz: 8be3a213c479ad3bc1cb82fad0a04b6b9a3f424a4995e0162e447c50cd6b0ef9
4
+ data.tar.gz: a7744949cc46b57628a55e78bb0bd766f048f140c8ca1769c271032e9d648176
5
5
  SHA512:
6
- metadata.gz: ddd3ac59908bf697da69f8f0fa29b2e779e468a07a6f37e96240165797fce088bbf6937473363d349b7e48fa8011434377b52345c8f60204ff94a7a794a4fee8
7
- data.tar.gz: c8fac0e676aa9632453337a13169b690ed36bc9cd65e0de754c63f77878ba1b598c9a344f8f547a8b7c530c0e12c285de2fc0eee3afa4a1f7fe8805293414b8e
6
+ metadata.gz: 13776ebd6447652ce0ad682a40bc70a4141b6b2db8690c32d7191828adbe9fd16ec8d30775007b29ff4c71bc559317cc56b60c94ab2af67ef4d259ea89ec3220
7
+ data.tar.gz: 2463587d1336f2b30e17e51f728526157c27752adb66bbc3703d20b68326d20eb734ee2990c7583866723ba3d927564d22ea6ddd9c0a70e515264043ce1d08d6
@@ -8,7 +8,7 @@ module ViewComponent
8
8
  prepend_view_path File.expand_path("../../../views", __dir__)
9
9
 
10
10
  around_action :set_locale, only: :previews
11
- before_action :require_local!, unless: :show_previews?
11
+ before_action :require_local!, unless: :previews_enabled?
12
12
 
13
13
  content_security_policy(false)
14
14
 
@@ -51,8 +51,8 @@ module ViewComponent
51
51
  end
52
52
 
53
53
  # :doc:
54
- def show_previews?
55
- ViewComponent::Base.config.show_previews
54
+ def previews_enabled?
55
+ ViewComponent::Base.config.previews.enabled
56
56
  end
57
57
 
58
58
  # :doc:
@@ -0,0 +1 @@
1
+ <%= render(UrlForMailerComponent.new) %>
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,36 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
+ ## 4.0.0.alpha7
14
+
15
+ * BREAKING: Remove deprecated `use_helper(s)`. Use `include MyHelper` or `helpers.` proxy instead.
16
+
17
+ *Joel Hawksley*
18
+
19
+ * BREAKING: Support compatibility with `Dry::Initializer`. As a result, `EmptyOrInvalidInitializerError` will no longer be raised.
20
+
21
+ *Joel Hawksley*
22
+
23
+ * BREAKING: Rename `config.generate.component_parent_class` to `config.generate.parent_class`.
24
+
25
+ *Joel Hawksley*
26
+
27
+ * Fix bug where `config.previews.enabled` did not function properly in production environments.
28
+
29
+ *Joel Hawksley*
30
+
31
+ * `config.previews.default_layout` should default to nil.
32
+
33
+ *Joel Hawksley*
34
+
35
+ * Add test case for absolute URL path helpers in mailers.
36
+
37
+ *Joel Hawksley*
38
+
39
+ * Fix bug where response format wasn't set, which caused issues with Turbo Frames.
40
+
41
+ *Joel Hawksley*
42
+
13
43
  ## 4.0.0.alpha6
14
44
 
15
45
  * BREAKING: Remove `config.test_controller` in favor of `vc_test_controller_class` test helper method.
@@ -186,6 +216,10 @@ This release makes the following breaking changes:
186
216
 
187
217
  *Simon Fish*
188
218
 
219
+ * Deprecate `use_helper(s)`. Use `include MyHelper` or `helpers.` proxy instead.
220
+
221
+ *Joel Hawksley*
222
+
189
223
  * Reduce string allocations during compilation.
190
224
 
191
225
  *Jonathan del Strother*
@@ -14,7 +14,6 @@ require "view_component/slotable"
14
14
  require "view_component/template"
15
15
  require "view_component/translatable"
16
16
  require "view_component/with_content_helper"
17
- require "view_component/use_helpers"
18
17
 
19
18
  module ActionView
20
19
  class OutputBuffer
@@ -48,11 +47,11 @@ module ViewComponent
48
47
  end
49
48
 
50
49
  include ActionView::Helpers
50
+ include Rails.application.routes.url_helpers if defined?(Rails) && Rails.application
51
51
  include ERB::Escape
52
52
  include ActiveSupport::CoreExt::ERBUtil
53
53
 
54
54
  include ViewComponent::InlineTemplate
55
- include ViewComponent::UseHelpers
56
55
  include ViewComponent::Slotable
57
56
  include ViewComponent::Translatable
58
57
  include ViewComponent::WithContentHelper
@@ -70,8 +69,9 @@ module ViewComponent
70
69
  delegate :content_security_policy_nonce, to: :helpers
71
70
 
72
71
  # Config option that strips trailing whitespace in templates before compiling them.
73
- class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
74
- self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2
72
+ class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false
73
+
74
+ class_attribute :__vc_response_format, instance_accessor: false, instance_predicate: false, default: nil
75
75
 
76
76
  attr_accessor :__vc_original_view_context
77
77
  attr_reader :current_template
@@ -91,6 +91,11 @@ module ViewComponent
91
91
 
92
92
  using RequestDetails
93
93
 
94
+ # Including `Rails.application.routes.url_helpers` defines an initializer that accepts (...),
95
+ # so we have to define our own empty initializer to overwrite it.
96
+ def initialize
97
+ end
98
+
94
99
  # Entrypoint for rendering components.
95
100
  #
96
101
  # - `view_context`: ActionView context from calling view
@@ -141,7 +146,7 @@ module ViewComponent
141
146
  value = if output_preamble.blank? && output_postamble.blank?
142
147
  rendered_template
143
148
  else
144
- safe_output_preamble + rendered_template + safe_output_postamble
149
+ __vc_safe_output_preamble + rendered_template + __vc_safe_output_postamble
145
150
  end
146
151
  end
147
152
 
@@ -334,6 +339,10 @@ module ViewComponent
334
339
  __vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
335
340
  end
336
341
 
342
+ def format
343
+ self.class.__vc_response_format
344
+ end
345
+
337
346
  private
338
347
 
339
348
  attr_reader :view_context
@@ -346,11 +355,7 @@ module ViewComponent
346
355
  defined?(@__vc_content_set_by_with_content)
347
356
  end
348
357
 
349
- def content_evaluated?
350
- defined?(@__vc_content_evaluated) && @__vc_content_evaluated
351
- end
352
-
353
- def maybe_escape_html(text)
358
+ def __vc_maybe_escape_html(text)
354
359
  return text if @current_template && !@current_template.html?
355
360
  return text if text.blank?
356
361
 
@@ -362,14 +367,14 @@ module ViewComponent
362
367
  end
363
368
  end
364
369
 
365
- def safe_output_preamble
366
- maybe_escape_html(output_preamble) do
370
+ def __vc_safe_output_preamble
371
+ __vc_maybe_escape_html(output_preamble) do
367
372
  Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe preamble. The preamble will be automatically escaped, but you may want to investigate.")
368
373
  end
369
374
  end
370
375
 
371
- def safe_output_postamble
372
- maybe_escape_html(output_postamble) do
376
+ def __vc_safe_output_postamble
377
+ __vc_maybe_escape_html(output_postamble) do
373
378
  Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe postamble. The postamble will be automatically escaped, but you may want to investigate.")
374
379
  end
375
380
  end
@@ -432,12 +437,12 @@ module ViewComponent
432
437
  #
433
438
  # Defaults to `false`.
434
439
  #
435
- # #### component_parent_class
440
+ # #### ßparent_class
436
441
  #
437
442
  # Parent class for generated components
438
443
  #
439
444
  # ```ruby
440
- # config.view_component.generate.component_parent_class = "MyBaseComponent"
445
+ # config.view_component.generate.parent_class = "MyBaseComponent"
441
446
  # ```
442
447
  #
443
448
  # Defaults to nil. If this is falsy, generators will use
@@ -511,6 +516,11 @@ module ViewComponent
511
516
  Collection.new(self, collection, spacer_component, **args)
512
517
  end
513
518
 
519
+ # @private
520
+ def __vc_compile(raise_errors: false, force: false)
521
+ __vc_compiler.compile(raise_errors: raise_errors, force: force)
522
+ end
523
+
514
524
  # @private
515
525
  def inherited(child)
516
526
  # Compile so child will inherit compiled `call_*` template methods that
@@ -533,12 +543,6 @@ module ViewComponent
533
543
  RUBY
534
544
  end
535
545
 
536
- # If Rails application is loaded, add application url_helpers to the component context
537
- # we need to check this to use this gem as a dependency
538
- if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers)
539
- child.include Rails.application.routes.url_helpers
540
- end
541
-
542
546
  # Derive the source location of the component Ruby file from the call stack.
543
547
  # We need to ignore `inherited` frames here as they indicate that `inherited`
544
548
  # has been re-defined by the consuming application, likely in ApplicationComponent.
@@ -548,7 +552,7 @@ module ViewComponent
548
552
  child.virtual_path = child.name&.underscore
549
553
 
550
554
  # Set collection parameter to the extended component
551
- child.with_collection_parameter provided_collection_parameter
555
+ child.with_collection_parameter(__vc_provided_collection_parameter)
552
556
 
553
557
  if instance_methods(false).include?(:render_template_for)
554
558
  vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
@@ -570,11 +574,6 @@ module ViewComponent
570
574
  __vc_compile unless __vc_compiled?
571
575
  end
572
576
 
573
- # @private
574
- def __vc_compile(raise_errors: false, force: false)
575
- __vc_compiler.compile(raise_errors: raise_errors, force: force)
576
- end
577
-
578
577
  # @private
579
578
  def __vc_compiler
580
579
  @__vc_compiler ||= Compiler.new(self)
@@ -588,8 +587,8 @@ module ViewComponent
588
587
  #
589
588
  # @param parameter [Symbol] The parameter name used when rendering elements of a collection.
590
589
  def with_collection_parameter(parameter)
591
- @provided_collection_parameter = parameter
592
- @initialize_parameters = nil
590
+ @__vc_provided_collection_parameter = parameter
591
+ @__vc_initialize_parameters = nil
593
592
  end
594
593
 
595
594
  # Strips trailing whitespace from templates before compiling them.
@@ -619,17 +618,10 @@ module ViewComponent
619
618
  # rendering is optional.
620
619
  # @private
621
620
  def __vc_validate_collection_parameter!(validate_default: false)
622
- parameter = validate_default ? __vc_collection_parameter : provided_collection_parameter
621
+ parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
623
622
 
624
623
  return unless parameter
625
- return if initialize_parameter_names.include?(parameter) || splatted_keyword_argument_present?
626
-
627
- # If Ruby can't parse the component class, then the initialize
628
- # parameters will be empty and ViewComponent will not be able to render
629
- # the component.
630
- if initialize_parameters.empty?
631
- raise EmptyOrInvalidInitializerError.new(name, parameter)
632
- end
624
+ return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
633
625
 
634
626
  raise MissingCollectionArgumentError.new(name, parameter)
635
627
  end
@@ -639,55 +631,57 @@ module ViewComponent
639
631
  # methods.
640
632
  # @private
641
633
  def __vc_validate_initialization_parameters!
642
- return unless initialize_parameter_names.include?(:content)
634
+ return unless __vc_initialize_parameter_names.include?(:content)
643
635
 
644
636
  raise ReservedParameterError.new(name, :content)
645
637
  end
646
638
 
647
639
  # @private
648
640
  def __vc_collection_parameter
649
- provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym
641
+ @__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
650
642
  end
651
643
 
652
644
  # @private
653
645
  def __vc_collection_counter_parameter
654
- :"#{__vc_collection_parameter}_counter"
646
+ @__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
655
647
  end
656
648
 
657
649
  # @private
658
650
  def __vc_counter_argument_present?
659
- initialize_parameter_names.include?(__vc_collection_counter_parameter)
651
+ __vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
660
652
  end
661
653
 
662
654
  # @private
663
655
  def __vc_collection_iteration_parameter
664
- :"#{__vc_collection_parameter}_iteration"
656
+ @__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
665
657
  end
666
658
 
667
659
  # @private
668
660
  def __vc_iteration_argument_present?
669
- initialize_parameter_names.include?(__vc_collection_iteration_parameter)
661
+ __vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
670
662
  end
671
663
 
672
664
  private
673
665
 
674
- def splatted_keyword_argument_present?
675
- initialize_parameters.flatten.include?(:keyrest) &&
676
- !initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count!
666
+ def __vc_splatted_keyword_argument_present?
667
+ __vc_initialize_parameters.flatten.include?(:keyrest)
677
668
  end
678
669
 
679
- def initialize_parameter_names
680
- return attribute_names.map(&:to_sym) if respond_to?(:attribute_names)
681
-
682
- initialize_parameters.map(&:last)
670
+ def __vc_initialize_parameter_names
671
+ @__vc_initialize_parameter_names ||=
672
+ if respond_to?(:attribute_names)
673
+ attribute_names.map(&:to_sym)
674
+ else
675
+ __vc_initialize_parameters.map(&:last)
676
+ end
683
677
  end
684
678
 
685
- def initialize_parameters
686
- @initialize_parameters ||= instance_method(:initialize).parameters
679
+ def __vc_initialize_parameters
680
+ @__vc_initialize_parameters ||= instance_method(:initialize).parameters
687
681
  end
688
682
 
689
- def provided_collection_parameter
690
- @provided_collection_parameter ||= nil
683
+ def __vc_provided_collection_parameter
684
+ @__vc_provided_collection_parameter ||= nil
691
685
  end
692
686
  end
693
687
 
@@ -48,7 +48,16 @@ module ViewComponent
48
48
 
49
49
  define_render_template_for
50
50
 
51
- @component.register_default_slots
51
+ # Set the format if the component only responds to a single format.
52
+ # Unfortunately we cannot determine which format a multi-format
53
+ # component will respond to until render time, so those components
54
+ # will not set the response format.
55
+ #
56
+ # TODO: Investigate upstream changes necessary to support multi-format renderables
57
+ unique_formats = templates.map(&:format).uniq
58
+ @component.__vc_response_format = unique_formats.last if unique_formats.one?
59
+
60
+ @component.__vc_register_default_slots
52
61
  @component.__vc_build_i18n_backend
53
62
 
54
63
  CompileCache.register(@component)
@@ -109,6 +118,7 @@ module ViewComponent
109
118
  errors << "Couldn't find a template file or inline render method for #{@component}." if @templates.empty?
110
119
 
111
120
  @templates
121
+ .reject { |template| template.inline_call? && !template.defined_on_self? }
112
122
  .map { |template| [template.variant, template.format] }
113
123
  .tally
114
124
  .select { |_, count| count > 1 }
@@ -167,10 +177,10 @@ module ViewComponent
167
177
 
168
178
  def gather_templates
169
179
  @templates ||=
170
- if @component.inline_template.present?
180
+ if @component.__vc_inline_template.present?
171
181
  [Template::Inline.new(
172
182
  component: @component,
173
- inline_template: @component.inline_template
183
+ inline_template: @component.__vc_inline_template
174
184
  )]
175
185
  else
176
186
  path_parser = ActionView::Resolver::PathParser.new
@@ -118,9 +118,9 @@ module ViewComponent
118
118
  #
119
119
  # #### `#default_layout`
120
120
  #
121
- # A custom default layout used for the previews index page and individual previews. Defaults to `false`:
121
+ # A custom default layout used for the previews index page and individual previews. Defaults to `nil`:
122
122
  #
123
- # config.view_component.previews.default_layout = false
123
+ # config.view_component.previews.default_layout = "preview_layout"
124
124
  #
125
125
 
126
126
  # @!attribute instrumentation_enabled
@@ -128,12 +128,6 @@ module ViewComponent
128
128
  # Whether ActiveSupport notifications are enabled.
129
129
  # Defaults to `false`.
130
130
 
131
- # @!attribute test_controller
132
- # @return [String]
133
- # The controller used for testing components.
134
- # Can also be configured on a per-test basis using `#with_controller_class`.
135
- # Defaults to `ApplicationController`.
136
-
137
131
  def default_preview_paths
138
132
  (default_rails_preview_paths + default_rails_engines_preview_paths).uniq
139
133
  end
@@ -170,7 +164,7 @@ module ViewComponent
170
164
  options.controller = "ViewComponentsController"
171
165
  options.route = "/rails/view_components"
172
166
  options.enabled = Rails.env.development? || Rails.env.test?
173
- options.default_layout = false
167
+ options.default_layout = nil
174
168
  options.paths = default_preview_paths
175
169
  options
176
170
  end
@@ -5,7 +5,7 @@ module ViewComponent
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:test_controller)
8
+ next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:instrumentation_enabled)
9
9
 
10
10
  include ActiveSupport::Configurable
11
11
 
@@ -15,9 +15,9 @@ module ViewComponent
15
15
  options[config_option] ||= ViewComponent::Base.public_send(config_option)
16
16
  end
17
17
  options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
18
- options.show_previews = (Rails.env.development? || Rails.env.test?) if options.show_previews.nil?
18
+ options.previews.enabled = (Rails.env.development? || Rails.env.test?) if options.previews.enabled.nil?
19
19
 
20
- if options.show_previews
20
+ if options.previews.enabled
21
21
  # This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified.
22
22
  options.previews.paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
23
23
  "#{Rails.root}/test/components/previews"
@@ -36,7 +36,7 @@ module ViewComponent
36
36
  initializer "view_component.set_autoload_paths" do |app|
37
37
  options = app.config.view_component
38
38
 
39
- if options.show_previews && !options.previews.paths.empty?
39
+ if options.previews.enabled && !options.previews.paths.empty?
40
40
  paths_to_add = options.previews.paths - ActiveSupport::Dependencies.autoload_paths
41
41
  ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any?
42
42
  end
@@ -88,7 +88,7 @@ module ViewComponent
88
88
  config.after_initialize do |app|
89
89
  options = app.config.view_component
90
90
 
91
- if options.show_previews
91
+ if options.previews.enabled
92
92
  app.routes.prepend do
93
93
  preview_controller = options.previews.controller.sub(/Controller$/, "").underscore
94
94
 
@@ -65,18 +65,6 @@ module ViewComponent
65
65
  end
66
66
  end
67
67
 
68
- class EmptyOrInvalidInitializerError < StandardError
69
- MESSAGE =
70
- "The COMPONENT initializer is empty or invalid. " \
71
- "It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
72
- "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
73
- "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
74
-
75
- def initialize(klass_name, parameter)
76
- super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
77
- end
78
- end
79
-
80
68
  class MissingCollectionArgumentError < StandardError
81
69
  MESSAGE =
82
70
  "The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
@@ -37,7 +37,7 @@ module ViewComponent # :nodoc:
37
37
  method.end_with?("_template") || super
38
38
  end
39
39
 
40
- def inline_template
40
+ def __vc_inline_template
41
41
  @__vc_inline_template if defined?(@__vc_inline_template)
42
42
  end
43
43
 
@@ -73,25 +73,25 @@ module ViewComponent
73
73
  #
74
74
  # <%= render_inline(MyComponent.new.with_header_content("Foo")) %>
75
75
  def renders_one(slot_name, callable = nil)
76
- validate_singular_slot_name(slot_name)
76
+ __vc_validate_singular_slot_name(slot_name)
77
77
 
78
78
  if callable.is_a?(Hash) && callable.key?(:types)
79
- register_polymorphic_slot(slot_name, callable[:types], collection: false)
79
+ __vc_register_polymorphic_slot(slot_name, callable[:types], collection: false)
80
80
  else
81
- validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
81
+ __vc_validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
82
82
 
83
83
  setter_method_name = :"with_#{slot_name}"
84
84
 
85
85
  define_method setter_method_name do |*args, **kwargs, &block|
86
- set_slot(slot_name, nil, *args, **kwargs, &block)
86
+ __vc_set_slot(slot_name, nil, *args, **kwargs, &block)
87
87
  end
88
88
 
89
89
  self::GeneratedSlotMethods.define_method slot_name do
90
- get_slot(slot_name)
90
+ __vc_get_slot(slot_name)
91
91
  end
92
92
 
93
93
  self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
94
- get_slot(slot_name).present?
94
+ __vc_get_slot(slot_name).present?
95
95
  end
96
96
 
97
97
  define_method :"with_#{slot_name}_content" do |content|
@@ -100,7 +100,7 @@ module ViewComponent
100
100
  self
101
101
  end
102
102
 
103
- register_slot(slot_name, collection: false, callable: callable)
103
+ __vc_register_slot(slot_name, collection: false, callable: callable)
104
104
  end
105
105
  end
106
106
 
@@ -142,18 +142,18 @@ module ViewComponent
142
142
  # <% end %>
143
143
  # <% end %>
144
144
  def renders_many(slot_name, callable = nil)
145
- validate_plural_slot_name(slot_name)
145
+ __vc_validate_plural_slot_name(slot_name)
146
146
 
147
147
  if callable.is_a?(Hash) && callable.key?(:types)
148
- register_polymorphic_slot(slot_name, callable[:types], collection: true)
148
+ __vc_register_polymorphic_slot(slot_name, callable[:types], collection: true)
149
149
  else
150
150
  singular_name = ActiveSupport::Inflector.singularize(slot_name)
151
- validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
151
+ __vc_validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
152
152
 
153
153
  setter_method_name = :"with_#{singular_name}"
154
154
 
155
155
  define_method setter_method_name do |*args, **kwargs, &block|
156
- set_slot(slot_name, nil, *args, **kwargs, &block)
156
+ __vc_set_slot(slot_name, nil, *args, **kwargs, &block)
157
157
  end
158
158
 
159
159
  define_method :"with_#{singular_name}_content" do |content|
@@ -165,22 +165,22 @@ module ViewComponent
165
165
  define_method :"with_#{slot_name}" do |collection_args = nil, &block|
166
166
  collection_args.map do |args|
167
167
  if args.respond_to?(:to_hash)
168
- set_slot(slot_name, nil, **args, &block)
168
+ __vc_set_slot(slot_name, nil, **args, &block)
169
169
  else
170
- set_slot(slot_name, nil, *args, &block)
170
+ __vc_set_slot(slot_name, nil, *args, &block)
171
171
  end
172
172
  end
173
173
  end
174
174
 
175
175
  self::GeneratedSlotMethods.define_method slot_name do
176
- get_slot(slot_name)
176
+ __vc_get_slot(slot_name)
177
177
  end
178
178
 
179
179
  self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
180
- get_slot(slot_name).present?
180
+ __vc_get_slot(slot_name).present?
181
181
  end
182
182
 
183
- register_slot(slot_name, collection: true, callable: callable)
183
+ __vc_register_slot(slot_name, collection: true, callable: callable)
184
184
  end
185
185
  end
186
186
 
@@ -211,8 +211,9 @@ module ViewComponent
211
211
  super
212
212
  end
213
213
 
214
+ # @private
214
215
  # Called by the compiler, as instance methods are not defined when slots are first registered
215
- def register_default_slots
216
+ def __vc_register_default_slots
216
217
  registered_slots.each do |slot_name, config|
217
218
  default_method_name = :"default_#{slot_name}"
218
219
  config[:default_method] = instance_methods.find { |method_name| method_name == default_method_name }
@@ -223,17 +224,17 @@ module ViewComponent
223
224
 
224
225
  private
225
226
 
226
- def register_slot(slot_name, **kwargs)
227
- registered_slots[slot_name] = define_slot(slot_name, **kwargs)
227
+ def __vc_register_slot(slot_name, **kwargs)
228
+ registered_slots[slot_name] = __vc_define_slot(slot_name, **kwargs)
228
229
  end
229
230
 
230
- def register_polymorphic_slot(slot_name, types, collection:)
231
+ def __vc_register_polymorphic_slot(slot_name, types, collection:)
231
232
  self::GeneratedSlotMethods.define_method(slot_name) do
232
- get_slot(slot_name)
233
+ __vc_get_slot(slot_name)
233
234
  end
234
235
 
235
236
  self::GeneratedSlotMethods.define_method(:"#{slot_name}?") do
236
- get_slot(slot_name).present?
237
+ __vc_get_slot(slot_name).present?
237
238
  end
238
239
 
239
240
  renderable_hash = types.each_with_object({}) do |(poly_type, poly_attributes_or_callable), memo|
@@ -252,7 +253,7 @@ module ViewComponent
252
253
  "#{slot_name}_#{poly_type}"
253
254
  end
254
255
 
255
- memo[poly_type] = define_slot(
256
+ memo[poly_type] = __vc_define_slot(
256
257
  poly_slot_name, collection: collection, callable: poly_callable
257
258
  )
258
259
 
@@ -263,7 +264,7 @@ module ViewComponent
263
264
  end
264
265
 
265
266
  define_method(setter_method_name) do |*args, **kwargs, &block|
266
- set_polymorphic_slot(slot_name, poly_type, *args, **kwargs, &block)
267
+ __vc_set_polymorphic_slot(slot_name, poly_type, *args, **kwargs, &block)
267
268
  end
268
269
 
269
270
  define_method :"with_#{poly_slot_name}_content" do |content|
@@ -279,7 +280,7 @@ module ViewComponent
279
280
  }
280
281
  end
281
282
 
282
- def define_slot(slot_name, collection:, callable:)
283
+ def __vc_define_slot(slot_name, collection:, callable:)
283
284
  slot = {collection: collection}
284
285
  return slot unless callable
285
286
 
@@ -302,18 +303,18 @@ module ViewComponent
302
303
  slot
303
304
  end
304
305
 
305
- def validate_plural_slot_name(slot_name)
306
+ def __vc_validate_plural_slot_name(slot_name)
306
307
  if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
307
308
  raise ReservedPluralSlotNameError.new(name, slot_name)
308
309
  end
309
310
 
310
- raise_if_slot_name_uncountable(slot_name)
311
- raise_if_slot_conflicts_with_call(slot_name)
312
- raise_if_slot_ends_with_question_mark(slot_name)
313
- raise_if_slot_registered(slot_name)
311
+ __vc_raise_if_slot_name_uncountable(slot_name)
312
+ __vc_raise_if_slot_conflicts_with_call(slot_name)
313
+ __vc_raise_if_slot_ends_with_question_mark(slot_name)
314
+ __vc_raise_if_slot_registered(slot_name)
314
315
  end
315
316
 
316
- def validate_singular_slot_name(slot_name)
317
+ def __vc_validate_singular_slot_name(slot_name)
317
318
  if slot_name.to_sym == :content
318
319
  raise ContentSlotNameError.new(name)
319
320
  end
@@ -322,28 +323,28 @@ module ViewComponent
322
323
  raise ReservedSingularSlotNameError.new(name, slot_name)
323
324
  end
324
325
 
325
- raise_if_slot_conflicts_with_call(slot_name)
326
- raise_if_slot_ends_with_question_mark(slot_name)
327
- raise_if_slot_registered(slot_name)
326
+ __vc_raise_if_slot_conflicts_with_call(slot_name)
327
+ __vc_raise_if_slot_ends_with_question_mark(slot_name)
328
+ __vc_raise_if_slot_registered(slot_name)
328
329
  end
329
330
 
330
- def raise_if_slot_registered(slot_name)
331
+ def __vc_raise_if_slot_registered(slot_name)
331
332
  if registered_slots.key?(slot_name)
332
333
  raise RedefinedSlotError.new(name, slot_name)
333
334
  end
334
335
  end
335
336
 
336
- def raise_if_slot_ends_with_question_mark(slot_name)
337
+ def __vc_raise_if_slot_ends_with_question_mark(slot_name)
337
338
  raise SlotPredicateNameError.new(name, slot_name) if slot_name.to_s.end_with?("?")
338
339
  end
339
340
 
340
- def raise_if_slot_conflicts_with_call(slot_name)
341
+ def __vc_raise_if_slot_conflicts_with_call(slot_name)
341
342
  if slot_name.start_with?("call_")
342
343
  raise InvalidSlotNameError, "Slot cannot start with 'call_'. Please rename #{slot_name}"
343
344
  end
344
345
  end
345
346
 
346
- def raise_if_slot_name_uncountable(slot_name)
347
+ def __vc_raise_if_slot_name_uncountable(slot_name)
347
348
  slot_name = slot_name.to_s
348
349
  if slot_name.pluralize == slot_name.singularize
349
350
  raise UncountableSlotNameError.new(name, slot_name)
@@ -351,9 +352,9 @@ module ViewComponent
351
352
  end
352
353
  end
353
354
 
354
- def get_slot(slot_name)
355
+ def __vc_get_slot(slot_name)
355
356
  @__vc_set_slots ||= {}
356
- content unless content_evaluated? # ensure content is loaded so slots will be defined
357
+ content unless defined?(@__vc_content_evaluated) && @__vc_content_evaluated # ensure content is loaded so slots will be defined
357
358
 
358
359
  # If the slot is set, return it
359
360
  return @__vc_set_slots[slot_name] if @__vc_set_slots[slot_name]
@@ -376,7 +377,7 @@ module ViewComponent
376
377
  end
377
378
  end
378
379
 
379
- def set_slot(slot_name, slot_definition = nil, *args, **kwargs, &block)
380
+ def __vc_set_slot(slot_name, slot_definition = nil, *args, **kwargs, &block)
380
381
  slot_definition ||= self.class.registered_slots[slot_name]
381
382
  slot = Slot.new(self)
382
383
 
@@ -433,7 +434,7 @@ module ViewComponent
433
434
  slot
434
435
  end
435
436
 
436
- def set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
437
+ def __vc_set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
437
438
  slot_definition = self.class.registered_slots[slot_name]
438
439
 
439
440
  if !slot_definition[:collection] && defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]
@@ -442,7 +443,7 @@ module ViewComponent
442
443
 
443
444
  poly_def = slot_definition[:renderable_hash][poly_type]
444
445
 
445
- set_slot(slot_name, poly_def, *args, **kwargs, &block)
446
+ __vc_set_slot(slot_name, poly_def, *args, **kwargs, &block)
446
447
  end
447
448
  end
448
449
  end
@@ -43,7 +43,7 @@ module ViewComponent
43
43
  attr_reader :source
44
44
 
45
45
  def initialize(component:, inline_template:)
46
- details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, nil, nil)
46
+ details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, DEFAULT_FORMAT, nil)
47
47
 
48
48
  super(
49
49
  component: component,
@@ -63,7 +63,7 @@ module ViewComponent
63
63
  class InlineCall < Template
64
64
  def initialize(component:, method_name:, defined_on_self:)
65
65
  variant = method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil
66
- details = ActionView::TemplateDetails.new(nil, nil, nil, variant)
66
+ details = ActionView::TemplateDetails.new(nil, nil, DEFAULT_FORMAT, variant)
67
67
 
68
68
  super(component: component, details: details)
69
69
 
@@ -82,7 +82,7 @@ module ViewComponent
82
82
  def safe_method_name_call
83
83
  m = safe_method_name
84
84
  proc do
85
- maybe_escape_html(send(m)) do
85
+ __vc_maybe_escape_html(send(m)) do
86
86
  Kernel.warn("WARNING: The #{self.class} component rendered HTML-unsafe output. " \
87
87
  "The output will be automatically escaped, but you may want to investigate.")
88
88
  end
@@ -70,12 +70,12 @@ module ViewComponent
70
70
 
71
71
  def initialize(scope:, load_paths:)
72
72
  @__vc_i18n_scope = scope.split(".").map(&:to_sym)
73
- @load_paths = load_paths
73
+ @__vc_load_paths = load_paths
74
74
  end
75
75
 
76
76
  # Ensure the Simple backend won't load paths from ::I18n.load_path
77
77
  def load_translations
78
- super(@load_paths)
78
+ super(@__vc_load_paths)
79
79
  end
80
80
 
81
81
  def scope_data(data)
@@ -100,7 +100,7 @@ module ViewComponent
100
100
  key = self.class.__vc_i18n_key(key, options.delete(:scope))
101
101
  as_html = HTML_SAFE_TRANSLATION_KEY.match?(key)
102
102
 
103
- html_escape_translation_options!(options) if as_html
103
+ __vc_html_escape_translation_options!(options) if as_html
104
104
 
105
105
  if key.start_with?(__vc_i18n_scope + ".")
106
106
  translated =
@@ -113,7 +113,7 @@ module ViewComponent
113
113
  return @view_context.translate(key, locale: locale, **options)
114
114
  end
115
115
 
116
- translated = html_safe_translation(translated) if as_html
116
+ translated = __vc_html_safe_translation(translated) if as_html
117
117
  translated
118
118
  else
119
119
  @view_context.translate(key, locale: locale, **options)
@@ -127,9 +127,9 @@ module ViewComponent
127
127
 
128
128
  private
129
129
 
130
- def html_safe_translation(translation)
130
+ def __vc_html_safe_translation(translation)
131
131
  if translation.respond_to?(:map)
132
- translation.map { |element| html_safe_translation(element) }
132
+ translation.map { |element| __vc_html_safe_translation(element) }
133
133
  else
134
134
  # It's assumed here that objects loaded by the i18n backend will respond to `#html_safe?`.
135
135
  # It's reasonable that if we're in Rails, `active_support/core_ext/string/output_safety.rb`
@@ -138,7 +138,7 @@ module ViewComponent
138
138
  end
139
139
  end
140
140
 
141
- def html_escape_translation_options!(options)
141
+ def __vc_html_escape_translation_options!(options)
142
142
  options.except(*::I18n::RESERVED_KEYS).each do |name, value|
143
143
  next if name == :count && value.is_a?(Numeric)
144
144
 
@@ -5,7 +5,7 @@ module ViewComponent
5
5
  MAJOR = 4
6
6
  MINOR = 0
7
7
  PATCH = 0
8
- PRE = "alpha6"
8
+ PRE = "alpha7"
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
11
11
  end
@@ -7,7 +7,6 @@ module ViewComponent
7
7
  extend ActiveSupport::Autoload
8
8
 
9
9
  autoload :Base
10
- autoload :CaptureCompatibility
11
10
  autoload :Compiler
12
11
  autoload :CompileCache
13
12
  autoload :Config
@@ -15,11 +14,14 @@ module ViewComponent
15
14
  autoload :InlineTemplate
16
15
  autoload :Instrumentation
17
16
  autoload :Preview
18
- autoload :TestHelpers
19
- autoload :SystemTestHelpers
20
- autoload :TestCase
21
- autoload :SystemTestCase
22
17
  autoload :Translatable
18
+
19
+ if Rails.env.test?
20
+ autoload :TestHelpers
21
+ autoload :SystemTestHelpers
22
+ autoload :TestCase
23
+ autoload :SystemTestCase
24
+ end
23
25
  end
24
26
 
25
27
  require "view_component/engine" if defined?(Rails::Engine)
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.0.0.alpha6
4
+ version: 4.0.0.alpha7
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team
@@ -54,6 +54,7 @@ files:
54
54
  - app/controllers/view_components_system_test_controller.rb
55
55
  - app/views/test_mailer/test_asset_email.html.erb
56
56
  - app/views/test_mailer/test_email.html.erb
57
+ - app/views/test_mailer/test_url_email.html.erb
57
58
  - app/views/view_components/index.html.erb
58
59
  - app/views/view_components/preview.html.erb
59
60
  - app/views/view_components/previews.html.erb
@@ -80,7 +81,6 @@ files:
80
81
  - lib/view_component/test_case.rb
81
82
  - lib/view_component/test_helpers.rb
82
83
  - lib/view_component/translatable.rb
83
- - lib/view_component/use_helpers.rb
84
84
  - lib/view_component/version.rb
85
85
  - lib/view_component/with_content_helper.rb
86
86
  homepage: https://viewcomponent.org
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ViewComponent::UseHelpers
4
- extend ActiveSupport::Concern
5
-
6
- class_methods do
7
- def use_helpers(*args, from: nil, prefix: false)
8
- args.each { |helper_method| use_helper(helper_method, from: from, prefix: prefix) }
9
- end
10
-
11
- def use_helper(helper_method, from: nil, prefix: false)
12
- helper_method_name = full_helper_method_name(helper_method, prefix: prefix, source: from)
13
-
14
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
15
- def #{helper_method_name}(...)
16
- raise HelpersCalledBeforeRenderError if view_context.nil?
17
-
18
- #{define_helper(helper_method: helper_method, source: from)}
19
- end
20
- RUBY
21
- end
22
-
23
- private
24
-
25
- def full_helper_method_name(helper_method, prefix: false, source: nil)
26
- return helper_method unless prefix.present?
27
-
28
- if !!prefix == prefix
29
- "#{source.to_s.underscore}_#{helper_method}"
30
- else
31
- "#{prefix}_#{helper_method}"
32
- end
33
- end
34
-
35
- def define_helper(helper_method:, source:)
36
- return "__vc_original_view_context.#{helper_method}(...)" unless source.present?
37
-
38
- "#{source}.instance_method(:#{helper_method}).bind(self).call(...)"
39
- end
40
- end
41
- end