view_component 2.23.1 → 2.26.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: b2282359cc779d83fa6aa65b912fe4e4e70d2dbd6c8939b2bd0f3e21bd2eb9e7
4
- data.tar.gz: c97fffc33ed1ffb6cd4598136c30c1b5990f1f150e8a7fc04ecfae933ae6c271
3
+ metadata.gz: 5a1fb023bfaa4a1ea26166ddd22b5fcd4a7e5f5da1bc7f10039c2bc557f1a94c
4
+ data.tar.gz: 5274b49f0e18f521b15181c70aeb4edcf3458cba13dc8c8ea59b9331d26681c5
5
5
  SHA512:
6
- metadata.gz: ec8397db82e97af474d4d25c8fde9346c73de00fdc37cd108640983a3230c08eae4c95566e8c86a96ad78c7e0c04aabf028f3f9c3e3040111d2b84d43c92bd53
7
- data.tar.gz: 152e91807eff69a99066d20d977ec9092c71f44754fa2f2fd76cd69be4f221e47cdfd01c413aaac60f7334c4d852f86a2a8514f395c8ed773407203c82d77fe0
6
+ metadata.gz: fc60826197c5dbd54ebd53517ff59eab097449913802a4e17df9db33ab54d1daf8ff2e00d1629058962c267274ec7fe6e362aa036206d379e27c7c832db7d05c
7
+ data.tar.gz: b3a65f7a829d9e9fc675904d61c6d1c1015c194c5a7abe596961b3cb20a3a4c2045f1ce49b1b98a551c4b745688c38610d3a86040c1f7f0229c3b247a51f9c65
data/CHANGELOG.md CHANGED
@@ -1,6 +1,60 @@
1
1
  # CHANGELOG
2
2
 
3
- ## master
3
+ ## main
4
+
5
+ ## 2.26.0
6
+
7
+ * Lazily evaluate component `content` in `render?`, preventing the `content` block from being evaluated when `render?` returns false.
8
+
9
+ *Blake Williams*
10
+
11
+ * Do not generate template when using `--inline` flag.
12
+
13
+ *Hans Lemuet*
14
+
15
+ * Add `--inline` option to the Haml and Slim generators
16
+
17
+ *Hans Lemuet*
18
+
19
+ ## 2.25.1
20
+
21
+ * Experimental: call `._after_compile` class method after a component is compiled.
22
+
23
+ *Joel Hawksley*
24
+
25
+ * Fix bug where SlotV2 was rendered as an HTML string when using Slim.
26
+
27
+ *Manuel Puyol*
28
+
29
+ ## 2.25.0
30
+
31
+ * Add `--preview` generator option to create an associated preview file.
32
+
33
+ *Bob Maerten*
34
+
35
+ * Add argument validation to avoid `content` override.
36
+
37
+ *Manuel Puyol*
38
+
39
+ ## 2.24.0
40
+
41
+ * Add `--inline` option to the erb generator. Prevents default erb template from being created and creates a component with a call method.
42
+
43
+ *Nachiket Pusalkar*
44
+
45
+ * Add test case for checking presence of `content` in `#render?`.
46
+
47
+ *Joel Hawksley*
48
+
49
+ * Rename `master` branch to `main`.
50
+
51
+ *Joel Hawksley*
52
+
53
+ ## 2.23.2
54
+
55
+ * Fix bug where rendering a component `with_collection` from a controller raised an error.
56
+
57
+ *Joel Hawksley*
4
58
 
5
59
  ## 2.23.1
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,25 @@ 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
+
284
327
  private
285
328
 
329
+ def initialize_parameter_names
330
+ initialize_parameters.map(&:last)
331
+ end
332
+
286
333
  def initialize_parameters
287
334
  instance_method(:initialize).parameters
288
335
  end
@@ -4,14 +4,16 @@ require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1
4
4
 
5
5
  module ViewComponent
6
6
  class Collection
7
+ attr_reader :component
8
+ delegate :format, to: :component
9
+
7
10
  def render_in(view_context, &block)
8
11
  iterator = ActionView::PartialIteration.new(@collection.size)
9
12
 
10
- @component.compile(raise_errors: true)
11
- @component.validate_collection_parameter!(validate_default: true)
13
+ component.validate_collection_parameter!(validate_default: true)
12
14
 
13
15
  @collection.map do |item|
14
- content = @component.new(**component_options(item, iterator)).render_in(view_context, &block)
16
+ content = component.new(**component_options(item, iterator)).render_in(view_context, &block)
15
17
  iterator.iterate!
16
18
  content
17
19
  end.join.html_safe
@@ -34,8 +36,8 @@ module ViewComponent
34
36
  end
35
37
 
36
38
  def component_options(item, iterator)
37
- item_options = { @component.collection_parameter => item }
38
- item_options[@component.collection_counter_parameter] = iterator.index + 1 if @component.counter_argument_present?
39
+ item_options = { component.collection_parameter => item }
40
+ item_options[component.collection_counter_parameter] = iterator.index + 1 if component.counter_argument_present?
39
41
 
40
42
  @options.merge(item_options)
41
43
  end
@@ -46,7 +46,10 @@ module ViewComponent
46
46
  instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
47
47
  end
48
48
 
49
- component_class.validate_collection_parameter! if raise_errors
49
+ if raise_errors
50
+ component_class.validate_initialization_parameters!
51
+ component_class.validate_collection_parameter!
52
+ end
50
53
 
51
54
  templates.each do |template|
52
55
  # Remove existing compiled template methods,
@@ -64,6 +67,8 @@ module ViewComponent
64
67
 
65
68
  define_render_template_for
66
69
 
70
+ component_class._after_compile
71
+
67
72
  CompileCache.register(component_class)
68
73
  end
69
74
 
@@ -163,9 +168,8 @@ module ViewComponent
163
168
  # end
164
169
  #
165
170
  # 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}}"]
171
+ nested_component_files = if component_class.name.include?("::") && component_name != filename
172
+ Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
169
173
  else
170
174
  []
171
175
  end
@@ -205,7 +209,6 @@ module ViewComponent
205
209
  end
206
210
  end
207
211
 
208
- # :nocov:
209
212
  def compiled_template(file_path)
210
213
  handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
211
214
  template = File.read(file_path)
@@ -216,7 +219,6 @@ module ViewComponent
216
219
  handler.call(OpenStruct.new(source: template, identifier: component_class.identifier, type: component_class.type))
217
220
  end
218
221
  end
219
- # :nocov:
220
222
 
221
223
  def call_method_name(variant)
222
224
  if variant.present? && variants.include?(variant)
@@ -22,8 +22,10 @@ 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
31
  @_component_instance.render_in(view_context, &@_content_block)
@@ -33,6 +35,8 @@ module ViewComponent
33
35
  @_content_block.call
34
36
  end
35
37
  end
38
+
39
+ @content
36
40
  end
37
41
 
38
42
  # Allow access to public component methods via the wrapper
@@ -58,6 +62,10 @@ module ViewComponent
58
62
  @_component_instance.public_send(symbol, *args, &block)
59
63
  end
60
64
 
65
+ def html_safe?
66
+ to_s.html_safe?
67
+ end
68
+
61
69
  def respond_to_missing?(symbol, include_all = false)
62
70
  defined?(@_component_instance) && @_component_instance.respond_to?(symbol, include_all)
63
71
  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 = 1
6
+ MINOR = 26
7
+ PATCH = 0
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.1
4
+ version: 2.26.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: 2020-12-10 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -198,7 +198,7 @@ dependencies:
198
198
  - - "~>"
199
199
  - !ruby/object:Gem::Version
200
200
  version: '0.13'
201
- description:
201
+ description:
202
202
  email:
203
203
  - opensource+view_component@github.com
204
204
  executables: []
@@ -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
@@ -253,7 +255,7 @@ licenses:
253
255
  - MIT
254
256
  metadata:
255
257
  allowed_push_host: https://rubygems.org
256
- post_install_message:
258
+ post_install_message:
257
259
  rdoc_options: []
258
260
  require_paths:
259
261
  - lib
@@ -268,8 +270,8 @@ 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
272
- signing_key:
273
+ rubygems_version: 3.0.3
274
+ signing_key:
273
275
  specification_version: 4
274
276
  summary: View components for Rails
275
277
  test_files: []