view_component 2.23.2 → 2.26.1

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: ae234d64ddfd0c5c6943d58cad1c5ae034c036cd3663680fdbfa01dbb7c0bb26
4
- data.tar.gz: 66ee3f2c510d649be7f4b4bb1ce99d63daa546fd738c70bd19edabe011c5f237
3
+ metadata.gz: 071c4caebc8e0dd567d8850adff146e65f6c8c1fe600b866f072d9bfa94bbc46
4
+ data.tar.gz: 64df3c2eb8d27431da23a2c628de6c9c00d7988130494d37319f4a01d653fd9c
5
5
  SHA512:
6
- metadata.gz: 60101dbc8eee7be6fdbedd45bd414a6788e1e3d734f5126a6f3afb6ebcffb11b131bd5d04aece9eb1d45f3dec99067d2038f7313894031e501773e995b5f77c6
7
- data.tar.gz: b6351313799ac1e85b0668270669677e1646c7d410b0ac14c3fb807a21a8cc53538dfb71934da2cb8a4fe60ef3db1f8edbb06e1651600a8bea4adb66c4d0bfa1
6
+ metadata.gz: 69bc43697d70dfb9b96280f47b80c06d1020fd180c01cbc6ae8c8416c3e1b97ffc6587d00741fa81743ae482a316e653790db5af2584c16baf3839a58b809195
7
+ data.tar.gz: 636d5d91edb07cb90f44e3fc21e7ee63c3c115d1f5fbf7666619205b628761028c4963c65b30a181f27fbbd0bf9539cab0ec3271a48b4d747d7a705528218f11
data/CHANGELOG.md CHANGED
@@ -1,6 +1,60 @@
1
1
  # CHANGELOG
2
2
 
3
- ## master
3
+ ## main
4
+
5
+ ## 2.26.1
6
+
7
+ * Fix bug that raises when trying to use a collection before the component has been compiled.
8
+
9
+ *Blake Williams*
10
+
11
+ ## 2.26.0
12
+
13
+ * Lazily evaluate component `content` in `render?`, preventing the `content` block from being evaluated when `render?` returns false.
14
+
15
+ *Blake Williams*
16
+
17
+ * Do not generate template when using `--inline` flag.
18
+
19
+ *Hans Lemuet*
20
+
21
+ * Add `--inline` option to the Haml and Slim generators
22
+
23
+ *Hans Lemuet*
24
+
25
+ ## 2.25.1
26
+
27
+ * Experimental: call `._after_compile` class method after a component is compiled.
28
+
29
+ *Joel Hawksley*
30
+
31
+ * Fix bug where SlotV2 was rendered as an HTML string when using Slim.
32
+
33
+ *Manuel Puyol*
34
+
35
+ ## 2.25.0
36
+
37
+ * Add `--preview` generator option to create an associated preview file.
38
+
39
+ *Bob Maerten*
40
+
41
+ * Add argument validation to avoid `content` override.
42
+
43
+ *Manuel Puyol*
44
+
45
+ ## 2.24.0
46
+
47
+ * Add `--inline` option to the erb generator. Prevents default erb template from being created and creates a component with a call method.
48
+
49
+ *Nachiket Pusalkar*
50
+
51
+ * Add test case for checking presence of `content` in `#render?`.
52
+
53
+ *Joel Hawksley*
54
+
55
+ * Rename `master` branch to `main`.
56
+
57
+ *Joel Hawksley*
4
58
 
5
59
  ## 2.23.2
6
60
 
@@ -7,6 +7,7 @@ module Rails
7
7
 
8
8
  argument :attributes, type: :array, default: [], banner: "attribute"
9
9
  check_class_collision suffix: "Component"
10
+ class_option :inline, type: :boolean, default: false
10
11
 
11
12
  def create_component_file
12
13
  template "component.rb", File.join("app/components", class_path, "#{file_name}_component.rb")
@@ -14,6 +15,8 @@ module Rails
14
15
 
15
16
  hook_for :test_framework
16
17
 
18
+ hook_for :preview, type: :boolean
19
+
17
20
  hook_for :template_engine do |instance, template_engine|
18
21
  instance.invoke template_engine, [instance.name]
19
22
  end
@@ -37,6 +40,10 @@ module Rails
37
40
  def initialize_body
38
41
  attributes.map { |attr| "@#{attr.name} = #{attr.name}" }.join("\n ")
39
42
  end
43
+
44
+ def initialize_call_method_for_inline?
45
+ options["inline"]
46
+ end
40
47
  end
41
48
  end
42
49
  end
@@ -6,4 +6,10 @@ class <%= class_name %>Component < <%= parent_class %>
6
6
  <%= initialize_body %>
7
7
  end
8
8
  <%- end -%>
9
+ <%- if initialize_call_method_for_inline? -%>
10
+ def call
11
+ content_tag :h1, "Hello world!"
12
+ end
13
+ <%- end -%>
14
+
9
15
  end
@@ -7,9 +7,12 @@ module Erb
7
7
  class ComponentGenerator < Base
8
8
  source_root File.expand_path("templates", __dir__)
9
9
  class_option :sidecar, type: :boolean, default: false
10
+ class_option :inline, type: :boolean, default: false
10
11
 
11
12
  def copy_view_file
12
- template "component.html.erb", destination
13
+ unless options["inline"]
14
+ template "component.html.erb", destination
15
+ end
13
16
  end
14
17
 
15
18
  private
@@ -9,7 +9,9 @@ module Haml
9
9
  class_option :sidecar, type: :boolean, default: false
10
10
 
11
11
  def copy_view_file
12
- template "component.html.haml", destination
12
+ if !options["inline"]
13
+ template "component.html.haml", destination
14
+ end
13
15
  end
14
16
 
15
17
  private
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Preview
4
+ module Generators
5
+ class ComponentGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ check_class_collision suffix: "ComponentPreview"
9
+
10
+ def create_preview_file
11
+ template "component_preview.rb", File.join("test/components/previews", class_path, "#{file_name}_component_preview.rb")
12
+ end
13
+
14
+ private
15
+
16
+ def file_name
17
+ @_file_name ||= super.sub(/_component\z/i, "")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ class <%= class_name %>ComponentPreview < ViewComponent::Preview
2
+ def default
3
+ render(<%= class_name %>Component.new)
4
+ end
5
+ end
@@ -9,7 +9,9 @@ module Slim
9
9
  class_option :sidecar, type: :boolean, default: false
10
10
 
11
11
  def copy_view_file
12
- template "component.html.slim", destination
12
+ if !options["inline"]
13
+ template "component.html.slim", destination
14
+ end
13
15
  end
14
16
 
15
17
  private
@@ -15,12 +15,22 @@ module ViewComponent
15
15
 
16
16
  ViewContextCalledBeforeRenderError = Class.new(StandardError)
17
17
 
18
+ RESERVED_PARAMETER = :content
19
+
18
20
  # For CSRF authenticity tokens in forms
19
21
  delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
20
22
 
21
23
  class_attribute :content_areas
22
24
  self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
23
25
 
26
+ # EXPERIMENTAL: This API is experimental and may be removed at any time.
27
+ # Hook for allowing components to do work as part of the compilation process.
28
+ #
29
+ # For example, one might compile component-specific assets at this point.
30
+ def self._after_compile
31
+ # noop
32
+ end
33
+
24
34
  # Entrypoint for rendering components.
25
35
  #
26
36
  # view_context: ActionView context from calling view
@@ -68,8 +78,8 @@ module ViewComponent
68
78
  old_current_template = @current_template
69
79
  @current_template = self
70
80
 
71
- # Assign captured content passed to component as a block to @content
72
- @content = view_context.capture(self, &block) if block_given?
81
+ @_content_evaluated = false
82
+ @_render_in_block = block
73
83
 
74
84
  before_render
75
85
 
@@ -167,7 +177,20 @@ module ViewComponent
167
177
  @request ||= controller.request
168
178
  end
169
179
 
170
- attr_reader :content, :view_context
180
+ attr_reader :view_context
181
+
182
+ def content
183
+ return @_content if defined?(@_content)
184
+ @_content_evaluated = true
185
+
186
+ @_content = if @view_context && @_render_in_block
187
+ view_context.capture(self, &@_render_in_block)
188
+ end
189
+ end
190
+
191
+ def content_evaluated?
192
+ @_content_evaluated
193
+ end
171
194
 
172
195
  # The controller used for testing components.
173
196
  # Defaults to ApplicationController. This should be set early
@@ -246,7 +269,14 @@ module ViewComponent
246
269
  if areas.include?(:content)
247
270
  raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
248
271
  end
249
- attr_reader(*areas)
272
+
273
+ areas.each do |area|
274
+ define_method area.to_sym do
275
+ content unless content_evaluated? # ensure content is loaded so content_areas will be defined
276
+ instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
277
+ end
278
+ end
279
+
250
280
  self.content_areas = areas
251
281
  end
252
282
 
@@ -264,7 +294,7 @@ module ViewComponent
264
294
  parameter = validate_default ? collection_parameter : provided_collection_parameter
265
295
 
266
296
  return unless parameter
267
- return if initialize_parameters.map(&:last).include?(parameter)
297
+ return if initialize_parameter_names.include?(parameter)
268
298
 
269
299
  # If Ruby cannot parse the component class, then the initalize
270
300
  # parameters will be empty and ViewComponent will not be able to render
@@ -281,8 +311,41 @@ module ViewComponent
281
311
  )
282
312
  end
283
313
 
314
+ # Ensure the component initializer does not define
315
+ # invalid parameters that could override the framework's
316
+ # methods.
317
+ def validate_initialization_parameters!
318
+ return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
319
+
320
+ raise ArgumentError.new(
321
+ "#{self} initializer cannot contain " \
322
+ "`#{RESERVED_PARAMETER}` since it will override a " \
323
+ "public ViewComponent method."
324
+ )
325
+ end
326
+
327
+ def collection_parameter
328
+ if provided_collection_parameter
329
+ provided_collection_parameter
330
+ else
331
+ name && name.demodulize.underscore.chomp("_component").to_sym
332
+ end
333
+ end
334
+
335
+ def collection_counter_parameter
336
+ "#{collection_parameter}_counter".to_sym
337
+ end
338
+
339
+ def counter_argument_present?
340
+ instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
341
+ end
342
+
284
343
  private
285
344
 
345
+ def initialize_parameter_names
346
+ initialize_parameters.map(&:last)
347
+ end
348
+
286
349
  def initialize_parameters
287
350
  instance_method(:initialize).parameters
288
351
  end
@@ -10,7 +10,6 @@ module ViewComponent
10
10
  def render_in(view_context, &block)
11
11
  iterator = ActionView::PartialIteration.new(@collection.size)
12
12
 
13
- component.compile(raise_errors: true)
14
13
  component.validate_collection_parameter!(validate_default: true)
15
14
 
16
15
  @collection.map do |item|
@@ -24,30 +24,11 @@ module ViewComponent
24
24
  )
25
25
  end
26
26
 
27
- # Remove any existing singleton methods,
28
- # as Ruby warns when redefining a method.
29
- component_class.remove_possible_singleton_method(:collection_parameter)
30
- component_class.remove_possible_singleton_method(:collection_counter_parameter)
31
- component_class.remove_possible_singleton_method(:counter_argument_present?)
32
-
33
- component_class.define_singleton_method(:collection_parameter) do
34
- if provided_collection_parameter
35
- provided_collection_parameter
36
- else
37
- name.demodulize.underscore.chomp("_component").to_sym
38
- end
39
- end
40
-
41
- component_class.define_singleton_method(:collection_counter_parameter) do
42
- "#{collection_parameter}_counter".to_sym
27
+ if raise_errors
28
+ component_class.validate_initialization_parameters!
29
+ component_class.validate_collection_parameter!
43
30
  end
44
31
 
45
- component_class.define_singleton_method(:counter_argument_present?) do
46
- instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
47
- end
48
-
49
- component_class.validate_collection_parameter! if raise_errors
50
-
51
32
  templates.each do |template|
52
33
  # Remove existing compiled template methods,
53
34
  # as Ruby warns when redefining a method.
@@ -64,6 +45,8 @@ module ViewComponent
64
45
 
65
46
  define_render_template_for
66
47
 
48
+ component_class._after_compile
49
+
67
50
  CompileCache.register(component_class)
68
51
  end
69
52
 
@@ -163,9 +146,8 @@ module ViewComponent
163
146
  # end
164
147
  #
165
148
  # Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
166
- nested_component_files = if component_class.name.include?("::")
167
- nested_component_path = component_class.name.deconstantize.underscore
168
- Dir["#{directory}/#{nested_component_path}/#{component_name}.*{#{extensions}}"]
149
+ nested_component_files = if component_class.name.include?("::") && component_name != filename
150
+ Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
169
151
  else
170
152
  []
171
153
  end
@@ -205,7 +187,6 @@ module ViewComponent
205
187
  end
206
188
  end
207
189
 
208
- # :nocov:
209
190
  def compiled_template(file_path)
210
191
  handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
211
192
  template = File.read(file_path)
@@ -216,7 +197,6 @@ module ViewComponent
216
197
  handler.call(OpenStruct.new(source: template, identifier: component_class.identifier, type: component_class.type))
217
198
  end
218
199
  end
219
- # :nocov:
220
200
 
221
201
  def call_method_name(variant)
222
202
  if variant.present? && variants.include?(variant)
@@ -22,17 +22,25 @@ module ViewComponent
22
22
  # If there is no slot renderable, we evaluate the block passed to
23
23
  # the slot and return it.
24
24
  def to_s
25
+ return @content if defined?(@content)
26
+
25
27
  view_context = @parent.send(:view_context)
26
- view_context.capture do
28
+ @content = view_context.capture do
27
29
  if defined?(@_component_instance)
28
30
  # render_in is faster than `parent.render`
29
- @_component_instance.render_in(view_context, &@_content_block)
31
+ if defined?(@_content_block)
32
+ @_component_instance.render_in(view_context, &@_content_block)
33
+ else
34
+ @_component_instance.render_in(view_context)
35
+ end
30
36
  elsif defined?(@_content)
31
37
  @_content
32
38
  elsif defined?(@_content_block)
33
39
  @_content_block.call
34
40
  end
35
41
  end
42
+
43
+ @content
36
44
  end
37
45
 
38
46
  # Allow access to public component methods via the wrapper
@@ -58,6 +66,10 @@ module ViewComponent
58
66
  @_component_instance.public_send(symbol, *args, &block)
59
67
  end
60
68
 
69
+ def html_safe?
70
+ to_s.html_safe?
71
+ end
72
+
61
73
  def respond_to_missing?(symbol, include_all = false)
62
74
  defined?(@_component_instance) && @_component_instance.respond_to?(symbol, include_all)
63
75
  end
@@ -51,11 +51,17 @@ module ViewComponent
51
51
  if collection
52
52
  class_eval <<-RUBY
53
53
  def #{accessor_name}
54
+ content unless content_evaluated? # ensure content is loaded so slots will be defined
54
55
  #{instance_variable_name} ||= []
55
56
  end
56
57
  RUBY
57
58
  else
58
- attr_reader accessor_name
59
+ class_eval <<-RUBY
60
+ def #{accessor_name}
61
+ content unless content_evaluated? # ensure content is loaded so slots will be defined
62
+ #{instance_variable_name} if defined?(#{instance_variable_name})
63
+ end
64
+ RUBY
59
65
  end
60
66
 
61
67
  # Default class_name to ViewComponent::Slot
@@ -183,6 +183,8 @@ module ViewComponent
183
183
  end
184
184
 
185
185
  def get_slot(slot_name)
186
+ content unless content_evaluated? # ensure content is loaded so slots will be defined
187
+
186
188
  slot = self.class.registered_slots[slot_name]
187
189
  @_set_slots ||= {}
188
190
 
@@ -3,8 +3,8 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 23
7
- PATCH = 2
6
+ MINOR = 26
7
+ PATCH = 1
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
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.23.2
4
+ version: 2.26.1
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: 2020-12-15 00:00:00.000000000 Z
11
+ date: 2021-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -219,6 +219,8 @@ files:
219
219
  - lib/rails/generators/erb/templates/component.html.erb.tt
220
220
  - lib/rails/generators/haml/component_generator.rb
221
221
  - lib/rails/generators/haml/templates/component.html.haml.tt
222
+ - lib/rails/generators/preview/component_generator.rb
223
+ - lib/rails/generators/preview/templates/component_preview.rb.tt
222
224
  - lib/rails/generators/rspec/component_generator.rb
223
225
  - lib/rails/generators/rspec/templates/component_spec.rb.tt
224
226
  - lib/rails/generators/slim/component_generator.rb
@@ -268,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
268
270
  - !ruby/object:Gem::Version
269
271
  version: '0'
270
272
  requirements: []
271
- rubygems_version: 3.1.2
273
+ rubygems_version: 3.0.3
272
274
  signing_key:
273
275
  specification_version: 4
274
276
  summary: View components for Rails