view_component 2.45.0 → 2.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a209f06f513d226dba5ddcfefe8c08d16f84da6d8664fd9a4f2f70fe1ef48d96
4
- data.tar.gz: cc11c7e358b6fa3b07474a257202be93b0283c7c6189ce8d939770a805a28ef6
3
+ metadata.gz: 8feda954b97fc366da967bee72554a1975a40f50aef0cbd1cde6f83f97547d49
4
+ data.tar.gz: 92312db0824d58d24ee8f74f56c3a14e961f03fc71bb1669b1a529fedfb225f3
5
5
  SHA512:
6
- metadata.gz: 2420eadefc54d1045c432b7b53010281bd923b556b72d4ed924b49a0a88b1841800d6e04f25bb043913f5c648693d19ad56117e441c838e7eb09e0ab5cf21315
7
- data.tar.gz: 4deb4c925810fcc2e3918e8735dd957003536e5533289d48caf69100dcb5ed58af6bb7af88fdc0cdf3c73e9905bb0222de18d9fd6cba65ca05e6bc3ed1f9964f
6
+ metadata.gz: 1ff56022e2212300d6ae5f8ea8700364fc5675354476b1830e05885c6f4beff3f7b75d6733531eea10aa1b0830917e025686bf17af89606ed2e457aaf1004f73
7
+ data.tar.gz: 5d8dddd267a4a627af6ba0a07507d9d0c2dd2ae38da92f542a690e2fa3052bdc56aa37488e1fde440c9a064b825f0ef1db3ec838f06eba4a4598b4e8c95e315f
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- <img src="/docs/logo/github-readme-logo.svg" alt="ViewComponent logo" width="400">
1
+ ![ViewComponent logo](/docs/logo/readme-light.svg#gh-light-mode-only)
2
+ ![ViewComponent logo](/docs/logo/readme-dark.svg#gh-dark-mode-only)
2
3
 
3
4
  A framework for building reusable, testable & encapsulated view components in Ruby on Rails.
4
5
 
@@ -29,12 +29,11 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc:
29
29
  @example_name = File.basename(params[:path])
30
30
  @render_args = @preview.render_args(@example_name, params: params.permit!)
31
31
  layout = determine_layout(@render_args[:layout], prepend_views: false)[:layout]
32
- template = @render_args[:template]
33
32
  locals = @render_args[:locals]
34
33
  opts = {}
35
34
  opts[:layout] = layout if layout.present? || layout == false
36
35
  opts[:locals] = locals if locals.present?
37
- render template, opts # rubocop:disable GitHub/RailsControllerRenderLiteral
36
+ render "view_components/preview", opts # rubocop:disable GitHub/RailsControllerRenderLiteral
38
37
  end
39
38
  end
40
39
 
@@ -4,16 +4,64 @@ module PreviewHelper
4
4
  AVAILABLE_PRISM_LANGUAGES = ["ruby", "erb", "haml"]
5
5
  FALLBACK_LANGUAGE = "ruby"
6
6
 
7
- def prism_language_name(template:)
7
+ def preview_source
8
+ return if @render_args.nil?
9
+
10
+ render "preview_source" # rubocop:disable GitHub/RailsViewRenderPathsExist
11
+ end
12
+
13
+ def find_template_data(lookup_context:, template_identifier:)
14
+ template = lookup_context.find_template(template_identifier)
15
+
16
+ if Rails.version.to_f >= 6.1 || template.source.present?
17
+ return {
18
+ source: template.source,
19
+ prism_language_name: prism_language_name_by_template(template: template)
20
+ }
21
+ else
22
+ # Fetch template source via finding it through preview paths
23
+ # to accomodate source view when exclusively using templates
24
+ # for previews for Rails < 6.1.
25
+ all_template_paths = ViewComponent::Base.preview_paths.map do |preview_path|
26
+ Dir.glob("#{preview_path}/**/*")
27
+ end.flatten
28
+
29
+ # Search for templates the contain `html`.
30
+ matching_templates = all_template_paths.find_all do |template|
31
+ template =~ /#{template_identifier}*.(html)/
32
+ end
33
+
34
+ # In-case of a conflict due to multiple template files with
35
+ # the same name
36
+ raise "found 0 matches for templates for #{template_identifier}." if matching_templates.empty?
37
+ raise "found multiple templates for #{template_identifier}." if matching_templates.size > 1
38
+
39
+ template_file_path = matching_templates.first
40
+ template_source = File.read(template_file_path)
41
+ prism_language_name = prism_language_name_by_template_path(template_file_path: template_file_path)
42
+
43
+ return {
44
+ source: template_source,
45
+ prism_language_name: prism_language_name
46
+ }
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def prism_language_name_by_template(template:)
8
53
  language = template.identifier.split(".").last
54
+
9
55
  return FALLBACK_LANGUAGE unless AVAILABLE_PRISM_LANGUAGES.include? language
10
56
 
11
57
  language
12
58
  end
13
59
 
14
- def preview_source
15
- return if @render_args.nil?
60
+ def prism_language_name_by_template_path(template_file_path:)
61
+ language = template_file_path.gsub(".html", "").split(".").last
16
62
 
17
- render "preview_source" # rubocop:disable GitHub/RailsViewRenderPathsExist
63
+ return FALLBACK_LANGUAGE unless AVAILABLE_PRISM_LANGUAGES.include? language
64
+
65
+ language
18
66
  end
19
67
  end
@@ -7,9 +7,9 @@
7
7
  <%= h @preview.preview_source(@example_name) %>
8
8
  </code>
9
9
  <% else %>
10
- <% template = @view_renderer.lookup_context.find_template(@render_args[:template]) %>
11
- <code class="language-<%= prism_language_name(template: template) %>">
12
- <%= h template.source %>
10
+ <% template_data = find_template_data(lookup_context: @view_renderer.lookup_context, template_identifier: @render_args[:template]) %>
11
+ <code class="language-<%= template_data[:prism_language_name] %>">
12
+ <%= h template_data[:source] %>
13
13
  </code>
14
14
  <% end %>
15
15
  </pre>
@@ -1,7 +1,11 @@
1
- <% if ViewComponent::Base.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
2
- <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
1
+ <% if @render_args[:component] %>
2
+ <% if ViewComponent::Base.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
3
+ <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
4
+ <% else %>
5
+ <%= render_component(@render_args[:component], &@render_args[:block]) %>
6
+ <% end %>
3
7
  <% else %>
4
- <%= render_component(@render_args[:component], &@render_args[:block]) %>
8
+ <%= render template: @render_args[:template], locals: @render_args[:locals] || {} %>
5
9
  <% end %>
6
10
 
7
11
  <% if ViewComponent::Base.show_previews_source %>
data/docs/CHANGELOG.md CHANGED
@@ -7,6 +7,195 @@ title: Changelog
7
7
 
8
8
  ## main
9
9
 
10
+ ## 2.49.0
11
+
12
+ * Fix path handling for evaluated view components that broke in Ruby 3.1.
13
+
14
+ *Adam Hess*
15
+
16
+ * Fix support for the `default:` option for a global translation.
17
+
18
+ *Elia Schito*
19
+
20
+ * Ensure i18n scope is a symbol to protect lookups.
21
+
22
+ *Simon Fish*
23
+
24
+ * Small update to preview docs to include rspec mention.
25
+
26
+ *Leigh Halliday*
27
+
28
+ * Small improvements to collection iteration docs.
29
+
30
+ *Brian O'Rourke*
31
+
32
+ * Add good and bad examples to `ViewComponents in practice`.
33
+
34
+ *Joel Hawksley*
35
+
36
+ * Add Ruby 3.1 and Rails 7.0 to CI
37
+
38
+ *Peter Goldstein*
39
+
40
+ ## 2.48.0
41
+
42
+ * Correct path in example test command in Contributing docs.
43
+
44
+ *Mark Wilkinson*
45
+
46
+ * Update link to GOV.UK Components library in the resources list.
47
+
48
+ *Peter Yates*
49
+
50
+ * Add Lookbook to Resources docs page.
51
+
52
+ *Mark Perkins*
53
+
54
+ * Add blocking compiler mode for use in Rails development and testing modes, improving thread safety.
55
+
56
+ *Horia Radu*
57
+
58
+ * Add generators to support `tailwindcss-rails`.
59
+
60
+ *Dino Maric*, *Hans Lemuet*
61
+
62
+ * Add a namespaced component example to docs.
63
+
64
+ *Hans Lemuet*
65
+
66
+ * Setup `Appraisal` to add flexibility when testing ViewComponent against multiple Rails versions.
67
+
68
+ *Hans Lemuet*
69
+
70
+ * Return correct values for `request.path` and `request.query_string` methods when using the `with_request_url` test helper.
71
+
72
+ *Vasiliy Matyushin*
73
+
74
+ * Improve style in generators docs.
75
+
76
+ *Hans Lemuet*
77
+
78
+ * Correctly type Ruby version strings and update Rails versions used in CI configuration.
79
+
80
+ *Hans Lemuet*
81
+
82
+ * Make `ViewComponent::Collection` act like a collection of view components.
83
+
84
+ *Sammy Henningsson*
85
+
86
+ * Update `@param` of `#render_inline` to include `ViewComponent::Collection`.
87
+
88
+ *Yutaka Kamei*
89
+
90
+ * Add Wecasa to users list.
91
+
92
+ *Mohamed Ziata*
93
+
94
+ ## 2.47.0
95
+
96
+ * Display preview source on previews that exclusively use templates.
97
+
98
+ *Edwin Mak*
99
+
100
+ * Add a test to ensure trailing newlines are stripped when rendering with `#render_in`.
101
+
102
+ *Simon Fish*
103
+
104
+ * Add WEBrick as a depenency to the application.
105
+
106
+ *Connor McQuillan*
107
+
108
+ * Update Ruby version in `.tool-versions`.
109
+
110
+ *Connor McQuillan*
111
+
112
+ * Add a test to ensure blocks can be passed into lambda slots without the need for any other arguments.
113
+
114
+ *Simon Fish*
115
+
116
+ * Add linters for file consistency.
117
+
118
+ *Simon Fish*
119
+
120
+ * Add @boardfish to docs/index.md and sort contributors.
121
+
122
+ *Simon Fish*
123
+
124
+ * Set up Codespaces for bug replication.
125
+
126
+ *Simon Fish*
127
+
128
+ * Add instructions for replicating bugs and failures.
129
+
130
+ *Simon Fish*
131
+
132
+ * Make @boardfish a committer.
133
+
134
+ *Joel Hawksley*
135
+
136
+ * Validate collection parameter with Active Model attribute names.
137
+
138
+ *Simon Fish*
139
+
140
+ * Fix `helpers` not working with component slots when rendered more than 2 component levels deep.
141
+
142
+ *Blake Williams*
143
+
144
+ * Update ruby to the latest versions
145
+
146
+ *Pedro Paiva*
147
+
148
+ * Fix `vale` linter config options.
149
+
150
+ *Hans Lemuet*
151
+
152
+ * Improve Contributing docs to include how to run tests for a specific version on Rails.
153
+
154
+ *Hans Lemuet*
155
+
156
+ * Add failing test for default form builder and documentation around known issue.
157
+
158
+ *Simon Fish*
159
+
160
+ * Add `--locale` flag to the component generator. Generates as many locale files as defined in `I18n.available_locales`, alongside the component.
161
+ * Add config option `config.view_component.generate_locale` to enable project-wide locale generation.
162
+ * Add config option `config.view_component.generate_distinct_locale_files` to enable project-wide per-locale translations file generation.
163
+
164
+ *Bob Maerten*
165
+
166
+ * Add config option `config.view_component.generate_sidecar` to always generate in the sidecar directory.
167
+
168
+ *Gleydson Tavares*
169
+
170
+ ## 2.46.0
171
+
172
+ * Add thread safety to the compiler.
173
+
174
+ *Horia Radu*
175
+
176
+ * Add theme-specific logo images to readme.
177
+
178
+ *Dylan Smith*
179
+
180
+ * Add Orbit to users list.
181
+
182
+ *Nicolas Goutay*
183
+
184
+ * Increase clarity around purpose and use of slots.
185
+
186
+ *Simon Fish*
187
+
188
+ * Deprecate loading `view_component/engine` directly.
189
+
190
+ **Upgrade notice**: You should update your `Gemfile` like this:
191
+
192
+ ```diff
193
+ - gem "view_component", require: "view_component/engine"`
194
+ + gem "view_component"
195
+ ```
196
+
197
+ *Yoshiyuki Hirano*
198
+
10
199
  ## 2.45.0
11
200
 
12
201
  * Remove internal APIs from API documentation, fix link to license.
@@ -15,7 +15,7 @@ module ViewComponent
15
15
  end
16
16
 
17
17
  def destination_directory
18
- if options["sidecar"]
18
+ if sidecar?
19
19
  File.join(component_path, class_path, destination_file_name)
20
20
  else
21
21
  File.join(component_path, class_path)
@@ -42,5 +42,9 @@ module ViewComponent
42
42
  gsub("/", "--")
43
43
  end
44
44
  end
45
+
46
+ def sidecar?
47
+ options["sidecar"] || ViewComponent::Base.generate_sidecar
48
+ end
45
49
  end
46
50
  end
@@ -15,6 +15,7 @@ module Rails
15
15
  class_option :parent, type: :string, desc: "The parent class for the generated component"
16
16
  class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate_stimulus_controller
17
17
  class_option :sidecar, type: :boolean, default: false
18
+ class_option :locale, type: :boolean, default: ViewComponent::Base.generate_locale
18
19
 
19
20
  def create_component_file
20
21
  template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
@@ -26,6 +27,8 @@ module Rails
26
27
 
27
28
  hook_for :stimulus, type: :boolean
28
29
 
30
+ hook_for :locale, type: :boolean
31
+
29
32
  hook_for :template_engine do |instance, template_engine|
30
33
  instance.invoke template_engine, [instance.name]
31
34
  end
@@ -6,7 +6,7 @@ class <%= class_name %>Component < <%= parent_class %>
6
6
  <%= initialize_body %>
7
7
  end
8
8
  <%- end -%>
9
- <%- if initialize_call_method_for_inline? -%>
9
+ <%- if initialize_call_method_for_inline? -%>
10
10
  def call
11
11
  content_tag :h1, "Hello world!"<%= ", data: { controller: \"#{stimulus_controller}\" }" if options["stimulus"] %>
12
12
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/abstract_generator"
4
+
5
+ module Locale
6
+ module Generators
7
+ class ComponentGenerator < ::Rails::Generators::NamedBase
8
+ include ViewComponent::AbstractGenerator
9
+
10
+ source_root File.expand_path("templates", __dir__)
11
+ argument :attributes, type: :array, default: [], banner: "attribute"
12
+ class_option :sidecar, type: :boolean, default: false
13
+
14
+ def create_locale_file
15
+ if ViewComponent::Base.generate_distinct_locale_files
16
+ I18n.available_locales.each do |locale|
17
+ create_file destination(locale), translations_hash([locale]).to_yaml
18
+ end
19
+ else
20
+ create_file destination, translations_hash(I18n.available_locales).to_yaml
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def translations_hash(locales = [:en])
27
+ locales.map { |locale| [locale.to_s, translation_keys] }.to_h
28
+ end
29
+
30
+ def translation_keys
31
+ keys = attributes.map(&:name)
32
+ keys = %w[hello] if keys.empty?
33
+ keys.map { |name| [name, name.capitalize] }.to_h
34
+ end
35
+
36
+ def destination(locale = nil)
37
+ extention = ".#{locale}" if locale
38
+ if sidecar?
39
+ File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component#{extention}.yml")
40
+ else
41
+ File.join(component_path, class_path, "#{file_name}_component#{extention}.yml")
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -23,7 +23,7 @@ module Stimulus
23
23
  private
24
24
 
25
25
  def destination
26
- if options["sidecar"]
26
+ if sidecar?
27
27
  File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component_controller.js")
28
28
  else
29
29
  File.join(component_path, class_path, "#{file_name}_component_controller.js")
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/erb/component_generator"
4
+
5
+ module Tailwindcss
6
+ module Generators
7
+ class ComponentGenerator < Erb::Generators::ComponentGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1 @@
1
+ <div<%= data_attributes %>>Add <%= class_name %> template here</div>
@@ -279,11 +279,31 @@ module ViewComponent
279
279
  #
280
280
  mattr_accessor :generate_stimulus_controller, instance_writer: false, default: false
281
281
 
282
+ # Always generate translations file alongside the component:
283
+ #
284
+ # config.view_component.generate_locale = true
285
+ #
286
+ # Defaults to `false`.
287
+ #
288
+ mattr_accessor :generate_locale, instance_writer: false, default: false
289
+
290
+ # Always generate as many translations files as available locales:
291
+ #
292
+ # config.view_component.generate_distinct_locale_files = true
293
+ #
294
+ # Defaults to `false`.
295
+ #
296
+ # One file will be generated for each configured `I18n.available_locales`.
297
+ # Fallback on `[:en]` when no available_locales is defined.
298
+ #
299
+ mattr_accessor :generate_distinct_locale_files, instance_writer: false, default: false
300
+
282
301
  # Path for component files
283
302
  #
284
303
  # config.view_component.view_component_path = "app/my_components"
285
304
  #
286
305
  # Defaults to `app/components`.
306
+ #
287
307
  mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
288
308
 
289
309
  # Parent class for generated components
@@ -291,8 +311,16 @@ module ViewComponent
291
311
  # config.view_component.component_parent_class = "MyBaseComponent"
292
312
  #
293
313
  # Defaults to "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
294
- mattr_accessor :component_parent_class,
295
- instance_writer: false
314
+ #
315
+ mattr_accessor :component_parent_class, instance_writer: false
316
+
317
+ # Always generate a component with a sidecar directory:
318
+ #
319
+ # config.view_component.generate_sidecar = true
320
+ #
321
+ # Defaults to `false`.
322
+ #
323
+ mattr_accessor :generate_sidecar, instance_writer: false, default: false
296
324
 
297
325
  class << self
298
326
  # @private
@@ -373,7 +401,7 @@ module ViewComponent
373
401
  # Derive the source location of the component Ruby file from the call stack.
374
402
  # We need to ignore `inherited` frames here as they indicate that `inherited`
375
403
  # has been re-defined by the consuming application, likely in ApplicationComponent.
376
- child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
404
+ child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].path
377
405
 
378
406
  # Removes the first part of the path and the extension.
379
407
  child.virtual_path = child.source_location.gsub(
@@ -507,6 +535,10 @@ module ViewComponent
507
535
  private
508
536
 
509
537
  def initialize_parameter_names
538
+ return attribute_names.map(&:to_sym) if respond_to?(:attribute_names)
539
+
540
+ return attribute_types.keys.map(&:to_sym) if Rails::VERSION::MAJOR <= 5 && respond_to?(:attribute_types)
541
+
510
542
  initialize_parameters.map(&:last)
511
543
  end
512
544
 
@@ -4,20 +4,34 @@ require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1
4
4
 
5
5
  module ViewComponent
6
6
  class Collection
7
+ include Enumerable
7
8
  attr_reader :component
8
9
 
9
10
  delegate :format, to: :component
11
+ delegate :size, to: :@collection
10
12
 
11
13
  def render_in(view_context, &block)
14
+ components.map do |component|
15
+ component.render_in(view_context, &block)
16
+ end.join.html_safe # rubocop:disable Rails/OutputSafety
17
+ end
18
+
19
+ def components
20
+ return @components if defined? @components
21
+
12
22
  iterator = ActionView::PartialIteration.new(@collection.size)
13
23
 
14
24
  component.validate_collection_parameter!(validate_default: true)
15
25
 
16
- @collection.map do |item|
17
- content = component.new(**component_options(item, iterator)).render_in(view_context, &block)
18
- iterator.iterate!
19
- content
20
- end.join.html_safe # rubocop:disable Rails/OutputSafety
26
+ @components = @collection.map do |item|
27
+ component.new(**component_options(item, iterator)).tap do |component|
28
+ iterator.iterate!
29
+ end
30
+ end
31
+ end
32
+
33
+ def each(&block)
34
+ components.each(&block)
21
35
  end
22
36
 
23
37
  private
@@ -42,7 +56,7 @@ module ViewComponent
42
56
  def component_options(item, iterator)
43
57
  item_options = { component.collection_parameter => item }
44
58
  item_options[component.collection_counter_parameter] = iterator.index + 1 if component.counter_argument_present?
45
- item_options[component.collection_iteration_parameter] = iterator if component.iteration_argument_present?
59
+ item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
46
60
 
47
61
  @options.merge(item_options)
48
62
  end
@@ -18,6 +18,10 @@ module ViewComponent
18
18
  cache.include? klass
19
19
  end
20
20
 
21
+ def invalidate_class!(klass)
22
+ cache.delete(klass)
23
+ end
24
+
21
25
  def invalidate!
22
26
  cache.clear
23
27
  end
@@ -2,66 +2,95 @@
2
2
 
3
3
  module ViewComponent
4
4
  class Compiler
5
+ # Lock required to be obtained before compiling the component
6
+ attr_reader :__vc_compiler_lock
7
+
8
+ # Compiler mode. Can be either:
9
+ # * development (a blocking mode which ensures thread safety when redefining the `call` method for components,
10
+ # default in Rails development and test mode)
11
+ # * production (a non-blocking mode, default in Rails production mode)
12
+ DEVELOPMENT_MODE = :development
13
+ PRODUCTION_MODE = :production
14
+
15
+ class_attribute :mode, default: PRODUCTION_MODE
16
+
5
17
  def initialize(component_class)
6
18
  @component_class = component_class
19
+ @__vc_compiler_lock = Monitor.new
7
20
  end
8
21
 
9
22
  def compiled?
10
23
  CompileCache.compiled?(component_class)
11
24
  end
12
25
 
26
+ def development?
27
+ self.class.mode == DEVELOPMENT_MODE
28
+ end
29
+
13
30
  def compile(raise_errors: false)
14
31
  return if compiled?
15
32
 
16
- subclass_instance_methods = component_class.instance_methods(false)
33
+ with_lock do
34
+ CompileCache.invalidate_class!(component_class)
17
35
 
18
- if subclass_instance_methods.include?(:with_content) && raise_errors
19
- raise ViewComponent::ComponentError.new(
20
- "#{component_class} implements a reserved method, `#with_content`.\n\n" \
21
- "To fix this issue, change the name of the method."
22
- )
23
- end
24
-
25
- if template_errors.present?
26
- raise ViewComponent::TemplateError.new(template_errors) if raise_errors
36
+ subclass_instance_methods = component_class.instance_methods(false)
27
37
 
28
- return false
29
- end
38
+ if subclass_instance_methods.include?(:with_content) && raise_errors
39
+ raise ViewComponent::ComponentError.new(
40
+ "#{component_class} implements a reserved method, `#with_content`.\n\n" \
41
+ "To fix this issue, change the name of the method."
42
+ )
43
+ end
30
44
 
31
- if subclass_instance_methods.include?(:before_render_check)
32
- ActiveSupport::Deprecation.warn(
33
- "`#before_render_check` will be removed in v3.0.0.\n\n" \
34
- "To fix this issue, use `#before_render` instead."
35
- )
36
- end
45
+ if template_errors.present?
46
+ raise ViewComponent::TemplateError.new(template_errors) if raise_errors
37
47
 
38
- if raise_errors
39
- component_class.validate_initialization_parameters!
40
- component_class.validate_collection_parameter!
41
- end
48
+ return false
49
+ end
42
50
 
43
- templates.each do |template|
44
- # Remove existing compiled template methods,
45
- # as Ruby warns when redefining a method.
46
- method_name = call_method_name(template[:variant])
51
+ if subclass_instance_methods.include?(:before_render_check)
52
+ ActiveSupport::Deprecation.warn(
53
+ "`#before_render_check` will be removed in v3.0.0.\n\n" \
54
+ "To fix this issue, use `#before_render` instead."
55
+ )
56
+ end
47
57
 
48
- if component_class.instance_methods.include?(method_name.to_sym)
49
- component_class.send(:undef_method, method_name.to_sym)
58
+ if raise_errors
59
+ component_class.validate_initialization_parameters!
60
+ component_class.validate_collection_parameter!
50
61
  end
51
62
 
52
- component_class.class_eval <<-RUBY, template[:path], -1
63
+ templates.each do |template|
64
+ # Remove existing compiled template methods,
65
+ # as Ruby warns when redefining a method.
66
+ method_name = call_method_name(template[:variant])
67
+
68
+ if component_class.instance_methods.include?(method_name.to_sym)
69
+ component_class.send(:undef_method, method_name.to_sym)
70
+ end
71
+
72
+ component_class.class_eval <<-RUBY, template[:path], -1
53
73
  def #{method_name}
54
74
  @output_buffer = ActionView::OutputBuffer.new
55
75
  #{compiled_template(template[:path])}
56
76
  end
57
- RUBY
58
- end
77
+ RUBY
78
+ end
79
+
80
+ define_render_template_for
59
81
 
60
- define_render_template_for
82
+ component_class._after_compile
61
83
 
62
- component_class._after_compile
84
+ CompileCache.register(component_class)
85
+ end
86
+ end
63
87
 
64
- CompileCache.register(component_class)
88
+ def with_lock(&block)
89
+ if development?
90
+ __vc_compiler_lock.synchronize(&block)
91
+ else
92
+ block.call
93
+ end
65
94
  end
66
95
 
67
96
  private
@@ -77,16 +106,30 @@ module ViewComponent
77
106
  "elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
78
107
  end.join("\n")
79
108
 
80
- component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
109
+ body = <<-RUBY
110
+ if variant.nil?
111
+ call
112
+ #{variant_elsifs}
113
+ else
114
+ call
115
+ end
116
+ RUBY
117
+
118
+ if development?
119
+ component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
81
120
  def render_template_for(variant = nil)
82
- if variant.nil?
83
- call
84
- #{variant_elsifs}
85
- else
86
- call
121
+ self.class.compiler.with_lock do
122
+ #{body}
87
123
  end
88
124
  end
89
- RUBY
125
+ RUBY
126
+ else
127
+ component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
128
+ def render_template_for(variant = nil)
129
+ #{body}
130
+ end
131
+ RUBY
132
+ end
90
133
  end
91
134
 
92
135
  def template_errors
@@ -115,6 +115,14 @@ module ViewComponent
115
115
  end
116
116
  end
117
117
 
118
+ initializer "compiler mode" do |app|
119
+ ViewComponent::Compiler.mode = if Rails.env.development? || Rails.env.test?
120
+ ViewComponent::Compiler::DEVELOPMENT_MODE
121
+ else
122
+ ViewComponent::Compiler::PRODUCTION_MODE
123
+ end
124
+ end
125
+
118
126
  config.after_initialize do |app|
119
127
  options = app.config.view_component
120
128
 
@@ -145,6 +153,13 @@ module ViewComponent
145
153
  end
146
154
  end
147
155
 
148
- # In the case of automatic loading, "view_component" is loaded first,
149
- # so there is no need to load it.
150
- require "view_component" unless defined?(ViewComponent::Base)
156
+ # :nocov:
157
+ unless defined?(ViewComponent::Base)
158
+ ActiveSupport::Deprecation.warn(
159
+ "This manually engine loading is deprecated and will be removed in v3.0.0. " \
160
+ "Remove `require \"view_component/engine\"`."
161
+ )
162
+
163
+ require "view_component"
164
+ end
165
+ # :nocov:
@@ -5,14 +5,16 @@ require "active_support/notifications"
5
5
  module ViewComponent # :nodoc:
6
6
  module Instrumentation
7
7
  def self.included(mod)
8
- mod.prepend(self)
8
+ mod.prepend(self) unless ancestors.include?(ViewComponent::Instrumentation)
9
9
  end
10
10
 
11
11
  def render_in(view_context, &block)
12
12
  ActiveSupport::Notifications.instrument(
13
13
  "!render.view_component",
14
- name: self.class.name,
15
- identifier: self.class.identifier
14
+ {
15
+ name: self.class.name,
16
+ identifier: self.class.identifier
17
+ }
16
18
  ) do
17
19
  super(view_context, &block)
18
20
  end
@@ -40,6 +40,8 @@ module ViewComponent
40
40
 
41
41
  @content =
42
42
  if defined?(@__vc_component_instance)
43
+ @__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context
44
+
43
45
  if defined?(@__vc_content_set_by_with_content)
44
46
  @__vc_component_instance.with_content(@__vc_content_set_by_with_content)
45
47
 
@@ -38,7 +38,7 @@ module ViewComponent
38
38
  # assert_text("Hello, World!")
39
39
  # ```
40
40
  #
41
- # @param component [ViewComponent::Base] The instance of the component to be rendered.
41
+ # @param component [ViewComponent::Base, ViewComponent::Collection] The instance of the component to be rendered.
42
42
  # @return [Nokogiri::HTML]
43
43
  def render_inline(component, **args, &block)
44
44
  @rendered_component =
@@ -113,16 +113,22 @@ module ViewComponent
113
113
  #
114
114
  # @param path [String] The path to set for the current request.
115
115
  def with_request_url(path)
116
+ old_request_path_info = request.path_info
116
117
  old_request_path_parameters = request.path_parameters
117
118
  old_request_query_parameters = request.query_parameters
119
+ old_request_query_string = request.query_string
118
120
  old_controller = defined?(@controller) && @controller
119
121
 
122
+ request.path_info = path
120
123
  request.path_parameters = Rails.application.routes.recognize_path(path)
121
124
  request.set_header("action_dispatch.request.query_parameters", Rack::Utils.parse_query(path.split("?")[1]))
125
+ request.set_header(Rack::QUERY_STRING, path.split("?")[1])
122
126
  yield
123
127
  ensure
128
+ request.path_info = old_request_path_info
124
129
  request.path_parameters = old_request_path_parameters
125
130
  request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
131
+ request.set_header(Rack::QUERY_STRING, old_request_query_string)
126
132
  @controller = old_controller
127
133
  end
128
134
 
@@ -41,7 +41,7 @@ module ViewComponent
41
41
  EMPTY_HASH = {}.freeze
42
42
 
43
43
  def initialize(i18n_scope:, load_paths:)
44
- @i18n_scope = i18n_scope.split(".")
44
+ @i18n_scope = i18n_scope.split(".").map(&:to_sym)
45
45
  @load_paths = load_paths
46
46
  end
47
47
 
@@ -70,21 +70,25 @@ module ViewComponent
70
70
  key = key&.to_s unless key.is_a?(String)
71
71
  key = "#{i18n_scope}#{key}" if key.start_with?(".")
72
72
 
73
- translated =
74
- catch(:exception) do
75
- i18n_backend.translate(locale, key, options)
73
+ if key.start_with?(i18n_scope + ".")
74
+ translated =
75
+ catch(:exception) do
76
+ i18n_backend.translate(locale, key, options)
77
+ end
78
+
79
+ # Fallback to the global translations
80
+ if translated.is_a? ::I18n::MissingTranslation
81
+ return super(key, locale: locale, **options)
76
82
  end
77
83
 
78
- # Fallback to the global translations
79
- if translated.is_a? ::I18n::MissingTranslation
80
- return super(key, locale: locale, **options)
81
- end
84
+ if HTML_SAFE_TRANSLATION_KEY.match?(key)
85
+ translated = translated.html_safe # rubocop:disable Rails/OutputSafety
86
+ end
82
87
 
83
- if HTML_SAFE_TRANSLATION_KEY.match?(key)
84
- translated = translated.html_safe # rubocop:disable Rails/OutputSafety
88
+ translated
89
+ else
90
+ super(key, locale: locale, **options)
85
91
  end
86
-
87
- translated
88
92
  end
89
93
  alias :t :translate
90
94
 
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 45
6
+ MINOR = 49
7
7
  PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -19,6 +19,13 @@ module ViewComponent
19
19
  autoload :Translatable
20
20
  end
21
21
 
22
- # In the case of manually loading, "view_component/engine" is loaded first,
23
- # so there is no need to load it.
24
- require "view_component/engine" if defined?(Rails::Engine) && !defined?(ViewComponent::Engine)
22
+ # :nocov:
23
+ if defined?(ViewComponent::Engine)
24
+ ActiveSupport::Deprecation.warn(
25
+ "This manually engine loading is deprecated and will be removed in v3.0.0. " \
26
+ "Remove `require \"view_component/engine\"`."
27
+ )
28
+ elsif defined?(Rails::Engine)
29
+ require "view_component/engine"
30
+ end
31
+ # :nocov:
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.45.0
4
+ version: 2.49.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-23 00:00:00.000000000 Z
11
+ date: 2022-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '1.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: appraisal
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.4'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.4'
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: benchmark-ips
49
63
  requirement: !ruby/object:Gem::Requirement
@@ -268,7 +282,7 @@ dependencies:
268
282
  - - ">="
269
283
  - !ruby/object:Gem::Version
270
284
  version: '0'
271
- description:
285
+ description:
272
286
  email:
273
287
  - opensource+view_component@github.com
274
288
  executables: []
@@ -295,6 +309,7 @@ files:
295
309
  - lib/rails/generators/erb/templates/component.html.erb.tt
296
310
  - lib/rails/generators/haml/component_generator.rb
297
311
  - lib/rails/generators/haml/templates/component.html.haml.tt
312
+ - lib/rails/generators/locale/component_generator.rb
298
313
  - lib/rails/generators/preview/component_generator.rb
299
314
  - lib/rails/generators/preview/templates/component_preview.rb.tt
300
315
  - lib/rails/generators/rspec/component_generator.rb
@@ -303,6 +318,8 @@ files:
303
318
  - lib/rails/generators/slim/templates/component.html.slim.tt
304
319
  - lib/rails/generators/stimulus/component_generator.rb
305
320
  - lib/rails/generators/stimulus/templates/component_controller.js.tt
321
+ - lib/rails/generators/tailwindcss/component_generator.rb
322
+ - lib/rails/generators/tailwindcss/templates/component.html.erb.tt
306
323
  - lib/rails/generators/test_unit/component_generator.rb
307
324
  - lib/rails/generators/test_unit/templates/component_test.rb.tt
308
325
  - lib/view_component.rb
@@ -341,7 +358,7 @@ licenses:
341
358
  - MIT
342
359
  metadata:
343
360
  allowed_push_host: https://rubygems.org
344
- post_install_message:
361
+ post_install_message:
345
362
  rdoc_options: []
346
363
  require_paths:
347
364
  - lib
@@ -356,8 +373,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
356
373
  - !ruby/object:Gem::Version
357
374
  version: '0'
358
375
  requirements: []
359
- rubygems_version: 3.1.2
360
- signing_key:
376
+ rubygems_version: 3.3.3
377
+ signing_key:
361
378
  specification_version: 4
362
379
  summary: View components for Rails
363
380
  test_files: []