view_component 2.39.0 → 2.43.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of view_component might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5f28b38a232a62dbe80033fe0ac5a7b0ee717f933afc76a1e991396ff51a8d6
4
- data.tar.gz: ed6610e4fc417a876aeac464439cc627c5ce6e242a65faa0d7db001e2249035a
3
+ metadata.gz: 3213b43fb64df68370b007c5e90029923c1b35d5893bfb1c57fad904d2df99fc
4
+ data.tar.gz: d13ad45c178645ba3c639cd889ec9f645defe96579f0a94d305b0ce940405bb1
5
5
  SHA512:
6
- metadata.gz: d7e130233a0699c54a1a9664c9a893afc6df8e45795d397ce6592e2edbd719c61ed6ef9fb373b3dc8d8285bc3a2d712c013579303138fb466806e646c2480465
7
- data.tar.gz: 25001a6bff37068aceccfb029d5829cd759f4662f7f0b8eb4ab76a42bf0a31ba55f1ceb97480c4a285dc3e9d9da62e746194f623f6b443c647fdb17bb21b3963
6
+ metadata.gz: 159ff39bb666465d3ae385b9abed08c977c748d4c4c2d2112c1dd87b7c2823a819d253af78a9b14ead1519bbd15c79de4c92518266ebe2fd88fd9de4850bf887
7
+ data.tar.gz: 7a62698b2449485ac139b89706c785f1466b49e98551df4a70897a347f8cc64619a072b860ebdd09869728978699e7926e3a09649abe083b954d4e18c746cc18
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ViewComponent
1
+ <img src="/docs/logo/viewcomponent-color-logo.svg" alt="ViewComponent logo" width="500">
2
2
 
3
3
  A framework for building reusable, testable & encapsulated view components in Ruby on Rails.
4
4
 
data/docs/CHANGELOG.md CHANGED
@@ -7,6 +7,127 @@ title: Changelog
7
7
 
8
8
  ## main
9
9
 
10
+ ## 2.43.0
11
+
12
+ * Add note about tests and instance methods.
13
+
14
+ *Joel Hawksley*
15
+
16
+ * Flesh out `ViewComponents in practice`.
17
+
18
+ *Joel Hawksley*
19
+
20
+ * Add CODEOWNERS entries for feature areas owned by community committers.
21
+
22
+ *Joel Hawksley*
23
+
24
+ * Separate lint and CI workflows.
25
+
26
+ *Blake Williams*
27
+
28
+ * Add support for `image_path` helper in previews.
29
+
30
+ *Tobias Ahlin*, *Joel Hawksley*
31
+
32
+ * Add section to docs listing users of ViewComponent. Please submit a PR to add your team to the list!
33
+
34
+ *Joel Hawksley*
35
+
36
+ * Fix loading issue with Stimulus generator and add specs for Stimulus generator.
37
+
38
+ *Peter Sumskas*
39
+
40
+ * Remove dependency on `ActionDispatch::Static` in Rails middleware stack when enabling statics assets for source code preview.
41
+
42
+ *Gregory Igelmund*
43
+
44
+ * Require `view_component/engine` automatically.
45
+
46
+ *Cameron Dutro*
47
+
48
+ ## 2.42.0
49
+
50
+ * Add logo files and page to docs.
51
+
52
+ *Dylan Smith*
53
+
54
+ * Add `ViewComponents in practice` documentation.
55
+
56
+ *Joel Hawksley*
57
+
58
+ * Fix bug where calling lambda slots without arguments would break in Ruby < 2.7.
59
+
60
+ *Manuel Puyol*
61
+
62
+ * Improve Stimulus controller template to import from `stimulus` or `@hotwired/stimulus`.
63
+
64
+ *Mario Schüttel*
65
+
66
+ * Fix bug where `helpers` would instantiate and use a new `view_context` in each component.
67
+
68
+ *Blake Williams*, *Ian C. Anderson*
69
+
70
+ * Implement polymorphic slots as experimental feature. See the Slots documentation to learn more.
71
+
72
+ *Cameron Dutro*
73
+
74
+ ## 2.41.0
75
+
76
+ * Add `sprockets-rails` development dependency to fix test suite failures when using rails@main.
77
+
78
+ *Blake Williams*
79
+
80
+ * Fix Ruby indentation warning.
81
+
82
+ *Blake Williams*
83
+
84
+ * Add `--parent` generator option to specify the parent class.
85
+ * Add config option `config.view_component.component_parent_class` to change it project-wide.
86
+
87
+ *Hans Lemuet*
88
+
89
+ * Update docs to add example for using Devise helpers in tests.
90
+
91
+ *Matthew Rider*
92
+
93
+ * Fix bug where `with_collection_parameter` did not inherit from parent component.
94
+
95
+ *Will Drexler*, *Christian Campoli*
96
+
97
+ * Allow query parameters in `with_request_url` test helper.
98
+
99
+ *Javi Martín*
100
+
101
+ * Add "how to render a component to a string" to FAQ.
102
+
103
+ *Hans Lemuet*
104
+
105
+ * Add `#render_in` to API docs.
106
+
107
+ *Hans Lemuet*
108
+
109
+ * Forward keyword arguments from slot wrapper to component instance using ruby2_keywords.
110
+
111
+ *Cameron Dutro*
112
+
113
+ ## 2.40.0
114
+
115
+ * Replace antipatterns section in the documentation with best practices.
116
+
117
+ *Blake Williams*
118
+
119
+ * Add components to `rails stats` task.
120
+
121
+ *Nicolas Brousse*
122
+
123
+ * Fix bug when using Slim and writing a slot whose block evaluates to `nil`.
124
+
125
+ *Yousuf Jukaku*
126
+
127
+ * Add documentation for test helpers.
128
+
129
+ *Joel Hawksley*
130
+
10
131
  ## 2.39.0
11
132
 
12
133
  * Clarify documentation of `with_variant` as an override of Action Pack.
@@ -44,7 +165,7 @@ title: Changelog
44
165
 
45
166
  * Add test case for conflict with internal `@variant` variable.
46
167
 
47
- *David Backeus*
168
+ *David Backeus*
48
169
 
49
170
  * Document decision to not change naming convention recommendation to remove `-Component` suffix.
50
171
 
@@ -108,7 +229,7 @@ title: Changelog
108
229
 
109
230
  * Ensure consistent indentation with Rubocop.
110
231
 
111
- *Joel Hawksley
232
+ *Joel Hawksley*
112
233
 
113
234
  * Bump `activesupport` upper bound from `< 7.0` to `< 8.0`.
114
235
 
@@ -12,6 +12,7 @@ module Rails
12
12
  argument :attributes, type: :array, default: [], banner: "attribute"
13
13
  check_class_collision suffix: "Component"
14
14
  class_option :inline, type: :boolean, default: false
15
+ class_option :parent, type: :string, desc: "The parent class for the generated component"
15
16
  class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate_stimulus_controller
16
17
  class_option :sidecar, type: :boolean, default: false
17
18
 
@@ -32,7 +33,9 @@ module Rails
32
33
  private
33
34
 
34
35
  def parent_class
35
- defined?(ApplicationComponent) ? "ApplicationComponent" : "ViewComponent::Base"
36
+ return options[:parent] if options[:parent]
37
+
38
+ ViewComponent::Base.component_parent_class || default_parent_class
36
39
  end
37
40
 
38
41
  def initialize_signature
@@ -48,6 +51,10 @@ module Rails
48
51
  def initialize_call_method_for_inline?
49
52
  options["inline"]
50
53
  end
54
+
55
+ def default_parent_class
56
+ defined?(ApplicationComponent) ? ApplicationComponent : ViewComponent::Base
57
+ end
51
58
  end
52
59
  end
53
60
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rails/generators/abstract_generator"
4
+
3
5
  module Stimulus
4
6
  module Generators
5
7
  class ComponentGenerator < ::Rails::Generators::NamedBase
@@ -12,6 +14,12 @@ module Stimulus
12
14
  template "component_controller.js", destination
13
15
  end
14
16
 
17
+ def stimulus_module
18
+ return "stimulus" if legacy_stimulus?
19
+
20
+ "@hotwired/stimulus"
21
+ end
22
+
15
23
  private
16
24
 
17
25
  def destination
@@ -21,6 +29,11 @@ module Stimulus
21
29
  File.join(component_path, class_path, "#{file_name}_component_controller.js")
22
30
  end
23
31
  end
32
+
33
+ def legacy_stimulus?
34
+ package_json_pathname = Rails.root.join("package.json")
35
+ package_json_pathname.exist? && JSON.parse(package_json_pathname.read).dig("dependencies", "stimulus").present?
36
+ end
24
37
  end
25
38
  end
26
39
  end
@@ -1,4 +1,4 @@
1
- import { Controller } from "stimulus";
1
+ import { Controller } from "<%= stimulus_module %>";
2
2
 
3
3
  export default class extends Controller {
4
4
  connect() {
@@ -5,6 +5,7 @@ require "active_support/configurable"
5
5
  require "view_component/collection"
6
6
  require "view_component/compile_cache"
7
7
  require "view_component/content_areas"
8
+ require "view_component/polymorphic_slots"
8
9
  require "view_component/previewable"
9
10
  require "view_component/slotable"
10
11
  require "view_component/slotable_v2"
@@ -28,6 +29,8 @@ module ViewComponent
28
29
  class_attribute :content_areas
29
30
  self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
30
31
 
32
+ attr_accessor :original_view_context
33
+
31
34
  # EXPERIMENTAL: This API is experimental and may be removed at any time.
32
35
  # Hook for allowing components to do work as part of the compilation process.
33
36
  #
@@ -39,33 +42,18 @@ module ViewComponent
39
42
 
40
43
  # Entrypoint for rendering components.
41
44
  #
42
- # view_context: ActionView context from calling view
43
- # block: optional block to be captured within the view context
44
- #
45
- # returns HTML that has been escaped by the respective template handler
46
- #
47
- # Example subclass:
48
- #
49
- # app/components/my_component.rb:
50
- # class MyComponent < ViewComponent::Base
51
- # def initialize(title:)
52
- # @title = title
53
- # end
54
- # end
45
+ # - `view_context`: ActionView context from calling view
46
+ # - `block`: optional block to be captured within the view context
55
47
  #
56
- # app/components/my_component.html.erb
57
- # <span title="<%= @title %>">Hello, <%= content %>!</span>
48
+ # Returns HTML that has been escaped by the respective template handler.
58
49
  #
59
- # In use:
60
- # <%= render MyComponent.new(title: "greeting") do %>world<% end %>
61
- # returns:
62
- # <span title="greeting">Hello, world!</span>
63
- #
64
- # @private
50
+ # @return [String]
65
51
  def render_in(view_context, &block)
66
52
  self.class.compile(raise_errors: true)
67
53
 
68
54
  @view_context = view_context
55
+ self.original_view_context ||= view_context
56
+
69
57
  @lookup_context ||= view_context.lookup_context
70
58
 
71
59
  # required for path helpers in older Rails versions
@@ -149,9 +137,10 @@ module ViewComponent
149
137
  # @private
150
138
  def render(options = {}, args = {}, &block)
151
139
  if options.is_a? ViewComponent::Base
140
+ options.original_view_context = original_view_context
152
141
  super
153
142
  else
154
- view_context.render(options, args, &block)
143
+ original_view_context.render(options, args, &block)
155
144
  end
156
145
  end
157
146
 
@@ -190,7 +179,14 @@ module ViewComponent
190
179
  )
191
180
  end
192
181
 
193
- @__vc_helpers ||= controller.view_context
182
+ # Attempt to re-use the original view_context passed to the first
183
+ # component rendered in the rendering pipeline. This prevents the
184
+ # instantiation of a new view_context via `controller.view_context` which
185
+ # always returns a new instance of the view context class.
186
+ #
187
+ # This allows ivars to remain persisted when using the same helper via
188
+ # `helpers` across multiple components and partials.
189
+ @__vc_helpers ||= original_view_context || controller.view_context
194
190
  end
195
191
 
196
192
  # Exposes .virtual_path as an instance method
@@ -298,6 +294,14 @@ module ViewComponent
298
294
  # Defaults to "app/components".
299
295
  mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
300
296
 
297
+ # Parent class for generated components
298
+ #
299
+ # config.view_component.component_parent_class = "MyBaseComponent"
300
+ #
301
+ # Defaults to "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
302
+ mattr_accessor :component_parent_class,
303
+ instance_writer: false
304
+
301
305
  class << self
302
306
  # @private
303
307
  attr_accessor :source_location, :virtual_path
@@ -384,6 +388,9 @@ module ViewComponent
384
388
  %r{(.*#{Regexp.quote(ViewComponent::Base.view_component_path)})|(\.rb)}, ""
385
389
  )
386
390
 
391
+ # Set collection parameter to the extended component
392
+ child.with_collection_parameter provided_collection_parameter
393
+
387
394
  super
388
395
  end
389
396
 
@@ -8,6 +8,10 @@ module ViewComponent
8
8
  config.view_component = ActiveSupport::OrderedOptions.new
9
9
  config.view_component.preview_paths ||= []
10
10
 
11
+ rake_tasks do
12
+ load "view_component/rails/tasks/view_component.rake"
13
+ end
14
+
11
15
  initializer "view_component.set_configs" do |app|
12
16
  options = app.config.view_component
13
17
 
@@ -99,7 +103,7 @@ module ViewComponent
99
103
 
100
104
  initializer "static assets" do |app|
101
105
  if app.config.view_component.show_previews
102
- app.middleware.insert_before(::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/app/assets/vendor")
106
+ app.middleware.use(::ActionDispatch::Static, "#{root}/app/assets/vendor")
103
107
  end
104
108
  end
105
109
 
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module PolymorphicSlots
5
+ # In older rails versions, using a concern isn't a good idea here because they appear to not work with
6
+ # Module#prepend and class methods.
7
+ def self.included(base)
8
+ base.singleton_class.prepend(ClassMethods)
9
+ base.include(InstanceMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def renders_one(slot_name, callable = nil)
14
+ return super unless callable.is_a?(Hash) && callable.key?(:types)
15
+
16
+ validate_singular_slot_name(slot_name)
17
+ register_polymorphic_slot(slot_name, callable[:types], collection: false)
18
+ end
19
+
20
+ def renders_many(slot_name, callable = nil)
21
+ return super unless callable.is_a?(Hash) && callable.key?(:types)
22
+
23
+ validate_plural_slot_name(slot_name)
24
+ register_polymorphic_slot(slot_name, callable[:types], collection: true)
25
+ end
26
+
27
+ def register_polymorphic_slot(slot_name, types, collection:)
28
+ renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
29
+ memo[poly_type] = define_slot(
30
+ "#{slot_name}_#{poly_type}", collection: collection, callable: poly_callable
31
+ )
32
+
33
+ getter_name = slot_name
34
+ setter_name =
35
+ if collection
36
+ "#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
37
+ else
38
+ "#{slot_name}_#{poly_type}"
39
+ end
40
+
41
+ define_method(getter_name) do
42
+ get_slot(slot_name)
43
+ end
44
+ ruby2_keywords(getter_name.to_sym) if respond_to?(:ruby2_keywords, true)
45
+
46
+ define_method(setter_name) do |*args, &block|
47
+ set_polymorphic_slot(slot_name, poly_type, *args, &block)
48
+ end
49
+ ruby2_keywords(setter_name.to_sym) if respond_to?(:ruby2_keywords, true)
50
+ end
51
+
52
+ self.registered_slots[slot_name] = {
53
+ collection: collection,
54
+ renderable_hash: renderable_hash
55
+ }
56
+ end
57
+ end
58
+
59
+ module InstanceMethods
60
+ def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block)
61
+ slot_definition = self.class.registered_slots[slot_name]
62
+
63
+ if !slot_definition[:collection] && get_slot(slot_name)
64
+ raise ArgumentError, "content for slot '#{slot_name}' has already been provided"
65
+ end
66
+
67
+ poly_def = slot_definition[:renderable_hash][poly_type]
68
+
69
+ set_slot(slot_name, poly_def, *args, &block)
70
+ end
71
+ ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
72
+ end
73
+ end
74
+ end
@@ -5,6 +5,7 @@ require "active_support/descendants_tracker"
5
5
  module ViewComponent # :nodoc:
6
6
  class Preview
7
7
  include ActionView::Helpers::TagHelper
8
+ include ActionView::Helpers::AssetTagHelper
8
9
  extend ActiveSupport::DescendantsTracker
9
10
 
10
11
  def render(component, **args, &block)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ task stats: "view_component:statsetup"
4
+
5
+ namespace :view_component do
6
+ task statsetup: :environment do
7
+ require "rails/code_statistics"
8
+
9
+ ::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path]
10
+ end
11
+ end
@@ -62,9 +62,9 @@ module ViewComponent
62
62
  view_context.capture(&@__vc_content_block)
63
63
  elsif defined?(@__vc_content_set_by_with_content)
64
64
  @__vc_content_set_by_with_content
65
- end
65
+ end
66
66
 
67
- @content
67
+ @content = @content.to_s
68
68
  end
69
69
 
70
70
  # Allow access to public component methods via the wrapper
@@ -89,6 +89,7 @@ module ViewComponent
89
89
  def method_missing(symbol, *args, &block)
90
90
  @__vc_component_instance.public_send(symbol, *args, &block)
91
91
  end
92
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
92
93
 
93
94
  def html_safe?
94
95
  to_s.html_safe?
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
-
5
4
  require "view_component/slot_v2"
6
5
 
7
6
  module ViewComponent
@@ -67,13 +66,14 @@ module ViewComponent
67
66
  def renders_one(slot_name, callable = nil)
68
67
  validate_singular_slot_name(slot_name)
69
68
 
70
- define_method slot_name do |*args, **kwargs, &block|
71
- if args.empty? && kwargs.empty? && block.nil?
69
+ define_method slot_name do |*args, &block|
70
+ if args.empty? && block.nil?
72
71
  get_slot(slot_name)
73
72
  else
74
- set_slot(slot_name, *args, **kwargs, &block)
73
+ set_slot(slot_name, nil, *args, &block)
75
74
  end
76
75
  end
76
+ ruby2_keywords(slot_name.to_sym) if respond_to?(:ruby2_keywords, true)
77
77
 
78
78
  register_slot(slot_name, collection: false, callable: callable)
79
79
  end
@@ -123,9 +123,10 @@ module ViewComponent
123
123
  # Define setter for singular names
124
124
  # e.g. `renders_many :items` allows fetching all tabs with
125
125
  # `component.tabs` and setting a tab with `component.tab`
126
- define_method singular_name do |*args, **kwargs, &block|
127
- set_slot(slot_name, *args, **kwargs, &block)
126
+ define_method singular_name do |*args, &block|
127
+ set_slot(slot_name, nil, *args, &block)
128
128
  end
129
+ ruby2_keywords(singular_name.to_sym) if respond_to?(:ruby2_keywords, true)
129
130
 
130
131
  # Instantiates and and adds multiple slots forwarding the first
131
132
  # argument to each slot constructor
@@ -134,7 +135,7 @@ module ViewComponent
134
135
  get_slot(slot_name)
135
136
  else
136
137
  collection_args.map do |args|
137
- set_slot(slot_name, **args, &block)
138
+ set_slot(slot_name, nil, **args, &block)
138
139
  end
139
140
  end
140
141
  end
@@ -162,27 +163,37 @@ module ViewComponent
162
163
 
163
164
  private
164
165
 
165
- def register_slot(slot_name, collection:, callable:)
166
+ def register_slot(slot_name, **kwargs)
167
+ self.registered_slots[slot_name] = define_slot(slot_name, **kwargs)
168
+ end
169
+
170
+ def define_slot(slot_name, collection:, callable:)
166
171
  # Setup basic slot data
167
172
  slot = {
168
173
  collection: collection,
169
174
  }
175
+ return slot unless callable
176
+
170
177
  # If callable responds to `render_in`, we set it on the slot as a renderable
171
- if callable && callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
178
+ if callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
172
179
  slot[:renderable] = callable
173
180
  elsif callable.is_a?(String)
174
181
  # If callable is a string, we assume it's referencing an internal class
175
182
  slot[:renderable_class_name] = callable
176
- elsif callable
183
+ elsif callable.respond_to?(:call)
177
184
  # If slot does not respond to `render_in`, we assume it's a proc,
178
185
  # define a method, and save a reference to it to call when setting
179
186
  method_name = :"_call_#{slot_name}"
180
187
  define_method method_name, &callable
181
188
  slot[:renderable_function] = instance_method(method_name)
189
+ else
190
+ raise(
191
+ ArgumentError,
192
+ "invalid slot definition. Please pass a class, string, or callable (i.e. proc, lambda, etc)"
193
+ )
182
194
  end
183
195
 
184
- # Register the slot on the component
185
- self.registered_slots[slot_name] = slot
196
+ slot
186
197
  end
187
198
 
188
199
  def validate_plural_slot_name(slot_name)
@@ -235,9 +246,8 @@ module ViewComponent
235
246
  end
236
247
  end
237
248
 
238
- def set_slot(slot_name, *args, **kwargs, &block)
239
- slot_definition = self.class.registered_slots[slot_name]
240
-
249
+ def set_slot(slot_name, slot_definition = nil, *args, &block)
250
+ slot_definition ||= self.class.registered_slots[slot_name]
241
251
  slot = SlotV2.new(self)
242
252
 
243
253
  # Passing the block to the sub-component wrapper like this has two
@@ -253,23 +263,24 @@ module ViewComponent
253
263
 
254
264
  # If class
255
265
  if slot_definition[:renderable]
256
- slot.__vc_component_instance = slot_definition[:renderable].new(*args, **kwargs)
266
+ slot.__vc_component_instance = slot_definition[:renderable].new(*args)
257
267
  # If class name as a string
258
268
  elsif slot_definition[:renderable_class_name]
259
269
  slot.__vc_component_instance =
260
- self.class.const_get(slot_definition[:renderable_class_name]).new(*args, **kwargs)
270
+ self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
261
271
  # If passed a lambda
262
272
  elsif slot_definition[:renderable_function]
263
273
  # Use `bind(self)` to ensure lambda is executed in the context of the
264
274
  # current component. This is necessary to allow the lambda to access helper
265
275
  # methods like `content_tag` as well as parent component state.
276
+ renderable_function = slot_definition[:renderable_function].bind(self)
266
277
  renderable_value =
267
278
  if block_given?
268
- slot_definition[:renderable_function].bind(self).call(*args, **kwargs) do |*args, **kwargs|
269
- view_context.capture(*args, **kwargs, &block)
279
+ renderable_function.call(*args) do |*args|
280
+ view_context.capture(*args, &block)
270
281
  end
271
282
  else
272
- slot_definition[:renderable_function].bind(self).call(*args, **kwargs)
283
+ renderable_function.call(*args)
273
284
  end
274
285
 
275
286
  # Function calls can return components, so if it's a component handle it specially
@@ -291,5 +302,6 @@ module ViewComponent
291
302
 
292
303
  slot
293
304
  end
305
+ ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
294
306
  end
295
307
  end
@@ -27,8 +27,19 @@ module ViewComponent
27
27
  # :nocov:
28
28
  end
29
29
 
30
+ # @private
30
31
  attr_reader :rendered_component
31
32
 
33
+ # Render a component inline. Internally sets `page` to be a `Capybara::Node::Simple`,
34
+ # allowing for Capybara assertions to be used:
35
+ #
36
+ # ```ruby
37
+ # render_inline(MyComponent.new)
38
+ # assert_text("Hello, World!")
39
+ # ```
40
+ #
41
+ # @param component [ViewComponent::Base] The instance of the component to be rendered.
42
+ # @return [Nokogiri::HTML]
32
43
  def render_inline(component, **args, &block)
33
44
  @rendered_component =
34
45
  if Rails.version.to_f >= 6.1
@@ -40,10 +51,12 @@ module ViewComponent
40
51
  Nokogiri::HTML.fragment(@rendered_component)
41
52
  end
42
53
 
54
+ # @private
43
55
  def controller
44
56
  @controller ||= build_controller(Base.test_controller.constantize)
45
57
  end
46
58
 
59
+ # @private
47
60
  def request
48
61
  @request ||=
49
62
  begin
@@ -53,6 +66,15 @@ module ViewComponent
53
66
  end
54
67
  end
55
68
 
69
+ # Set the Action Pack request variant for the given block:
70
+ #
71
+ # ```ruby
72
+ # with_variant(:phone) do
73
+ # render_inline(MyComponent.new)
74
+ # end
75
+ # ```
76
+ #
77
+ # @param variant [Symbol] The variant to be set for the provided block.
56
78
  def with_variant(variant)
57
79
  old_variants = controller.view_context.lookup_context.variants
58
80
 
@@ -62,6 +84,16 @@ module ViewComponent
62
84
  controller.view_context.lookup_context.variants = old_variants
63
85
  end
64
86
 
87
+ # Set the controller to be used while executing the given block,
88
+ # allowing access to controller-specific methods:
89
+ #
90
+ # ```ruby
91
+ # with_controller_class(UsersController) do
92
+ # render_inline(MyComponent.new)
93
+ # end
94
+ # ```
95
+ #
96
+ # @param klass [ActionController::Base] The controller to be used.
65
97
  def with_controller_class(klass)
66
98
  old_controller = defined?(@controller) && @controller
67
99
 
@@ -71,17 +103,30 @@ module ViewComponent
71
103
  @controller = old_controller
72
104
  end
73
105
 
106
+ # Set the URL for the current request (such as when using request-dependent path helpers):
107
+ #
108
+ # ```ruby
109
+ # with_request_url("/users/42") do
110
+ # render_inline(MyComponent.new)
111
+ # end
112
+ # ```
113
+ #
114
+ # @param path [String] The path to set for the current request.
74
115
  def with_request_url(path)
75
116
  old_request_path_parameters = request.path_parameters
117
+ old_request_query_parameters = request.query_parameters
76
118
  old_controller = defined?(@controller) && @controller
77
119
 
78
120
  request.path_parameters = Rails.application.routes.recognize_path(path)
121
+ request.set_header("action_dispatch.request.query_parameters", Rack::Utils.parse_query(path.split("?")[1]))
79
122
  yield
80
123
  ensure
81
124
  request.path_parameters = old_request_path_parameters
125
+ request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
82
126
  @controller = old_controller
83
127
  end
84
128
 
129
+ # @private
85
130
  def build_controller(klass)
86
131
  klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
87
132
  end
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 39
6
+ MINOR = 43
7
7
  PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -17,3 +17,5 @@ module ViewComponent
17
17
  autoload :TemplateError
18
18
  autoload :Translatable
19
19
  end
20
+
21
+ require "view_component/engine" if defined?(Rails::Engine)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.39.0
4
+ version: 2.43.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-31 00:00:00.000000000 Z
11
+ date: 2021-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -226,6 +226,20 @@ dependencies:
226
226
  - - "~>"
227
227
  - !ruby/object:Gem::Version
228
228
  version: '4.0'
229
+ - !ruby/object:Gem::Dependency
230
+ name: sprockets-rails
231
+ requirement: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - "~>"
234
+ - !ruby/object:Gem::Version
235
+ version: 3.2.2
236
+ type: :development
237
+ prerelease: false
238
+ version_requirements: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - "~>"
241
+ - !ruby/object:Gem::Version
242
+ version: 3.2.2
229
243
  - !ruby/object:Gem::Dependency
230
244
  name: yard
231
245
  requirement: !ruby/object:Gem::Requirement
@@ -300,9 +314,11 @@ files:
300
314
  - lib/view_component/content_areas.rb
301
315
  - lib/view_component/engine.rb
302
316
  - lib/view_component/instrumentation.rb
317
+ - lib/view_component/polymorphic_slots.rb
303
318
  - lib/view_component/preview.rb
304
319
  - lib/view_component/preview_template_error.rb
305
320
  - lib/view_component/previewable.rb
321
+ - lib/view_component/rails/tasks/view_component.rake
306
322
  - lib/view_component/render_component_helper.rb
307
323
  - lib/view_component/render_component_to_string_helper.rb
308
324
  - lib/view_component/render_monkey_patch.rb