view_component 2.23.2 → 2.26.1

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: 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