view_component 2.45.0 → 2.49.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: 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: []