view_component 4.0.0.alpha3 → 4.0.0.alpha5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d926054ff51767aa08c688eef809327cbbda35e8b403a574af8baa642aefe7ed
4
- data.tar.gz: ed0a01b7b1fe98080c4c7210c7bdde4b474d08f4b0c5ab0a36c39e9e6631f185
3
+ metadata.gz: 544de384800ad5d0870666b9b1fdb3a74da9fd59d77f78e0d72ee5e134a3c307
4
+ data.tar.gz: 430c3652f5abc6fef9815b937274f66ebac34de297318e1ca6839589fa290ddf
5
5
  SHA512:
6
- metadata.gz: 54aa90e88b7fb9f0773842f0737739c15cfaff4609f62fe733badd14ab8676ec21532c5d00915947ce183aca678f7ed79573f15e8dec06f873067a9585a277ab
7
- data.tar.gz: 3c4187ccc4f70942964fedd232fb387314e5f7cfa47624df5def3006ff9478947d4de9380c0baf29b5102e20217de0eb77a9bfe8a21240a79065cb719d02bf94
6
+ metadata.gz: f30651af979f6c1c5fd43514d04cef3f6430982198e593375d61ce79190e83b8b2d61d7cccb67607248f0b791346b09db33b4705e81cb257b87bd8255c5b2d63
7
+ data.tar.gz: 956af9c486961785821e9401c9c1ce6b187f3f21a0425d5d6d36602f384f3beb79629f9be2e743e13f525bdc15498480565989cbdb37952a7b0e1cbb2aaabbb0
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,34 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
+ ## 4.0.0.alpha5
14
+
15
+ * BREAKING: `config.view_component_path` is now `config.generate.path`, as components have long since been able to exist in any directory.
16
+
17
+ *Joel Hawksley*
18
+
19
+ * BREAKING: Remove broken integration with `rails stats` that ignored components outside of `app/components`.
20
+
21
+ *Joel Hawksley*
22
+
23
+ * Add internal optimization for Ruby object shapes.
24
+
25
+ *Adam Hess*, *Joel Hawksley*
26
+
27
+ ## 4.0.0.alpha4
28
+
29
+ * BREAKING: Remove default initializer from `ViewComponent::Base`. Previously, `ViewComponent::Base` defined a catch-all initializer that allowed components without an initializer defined to be passed arbitrary arguments.
30
+
31
+ *Joel Hawksley*
32
+
33
+ * Graduate `SlotableDefault` to be included by default.
34
+
35
+ *Joel Hawksley*
36
+
37
+ * Fix bug in `SlotableDefault` where default couldn't be overridden when content was passed as a block.
38
+
39
+ *Bill Watts*, *Joel Hawksley*
40
+
13
41
  ## 4.0.0.alpha3
14
42
 
15
43
  * BREAKING: Remove dependency on `ActionView::Base`, eliminating the need for capture compatibility patch.
@@ -12,7 +12,6 @@ require "view_component/inline_template"
12
12
  require "view_component/preview"
13
13
  require "view_component/request_details"
14
14
  require "view_component/slotable"
15
- require "view_component/slotable_default"
16
15
  require "view_component/template"
17
16
  require "view_component/translatable"
18
17
  require "view_component/with_content_helper"
@@ -36,18 +35,51 @@ module ViewComponent
36
35
  class << self
37
36
  delegate(*ViewComponent::Config.defaults.keys, to: :config)
38
37
 
38
+ # Redefine `new` so we can pre-allocate instance variables to optimize
39
+ # for Ruby object shapes.
40
+ def new(...)
41
+ instance = allocate
42
+ instance.__vc_pre_allocate_instance_variables
43
+ instance.send(:initialize, ...)
44
+ instance
45
+ end
46
+
39
47
  # Returns the current config.
40
48
  #
41
49
  # @return [ActiveSupport::OrderedOptions]
42
50
  def config
43
- module_parents.each do |m|
44
- config = m.try(:config).try(:view_component)
45
- return config if config
51
+ module_parents.each do |module_parent|
52
+ next unless module_parent.respond_to?(:config)
53
+ module_parent_config = module_parent.config.try(:view_component)
54
+ return module_parent_config if module_parent_config
46
55
  end
47
56
  ViewComponent::Config.current
48
57
  end
49
58
  end
50
59
 
60
+ def __vc_pre_allocate_instance_variables
61
+ @__vc_parent_render_level = 0
62
+ @__vc_set_slots = {}
63
+ @__vc_content_evaluated = false
64
+ @current_template = nil
65
+ @output_buffer = nil
66
+ @lookup_context = nil
67
+ @view_flow = nil
68
+ @view_context = nil
69
+ @virtual_path = nil
70
+ @__vc_ancestor_calls = nil
71
+ @__vc_controller = nil
72
+ @__vc_content = :unset # some behaviors depend on checking for nil
73
+ @__vc_content_set_by_with_content = nil
74
+ @__vc_helpers = nil
75
+ @__vc_inline_template = nil
76
+ @__vc_inline_template_defined = nil
77
+ @__vc_render_in_block = nil
78
+ @__vc_request = nil
79
+ @__vc_requested_details = nil
80
+ @__vc_original_view_context = nil
81
+ end
82
+
51
83
  include ActionView::Helpers
52
84
  include ERB::Escape
53
85
  include ActiveSupport::CoreExt::ERBUtil
@@ -114,14 +146,12 @@ module ViewComponent
114
146
  @__vc_requested_details ||= @lookup_context.vc_requested_details
115
147
 
116
148
  # For caching, such as #cache_if
117
- @current_template = nil unless defined?(@current_template)
118
149
  old_current_template = @current_template
119
150
 
120
- if block && defined?(@__vc_content_set_by_with_content)
151
+ if block && __vc_content_set_by_with_content?
121
152
  raise DuplicateContentError.new(self.class.name)
122
153
  end
123
154
 
124
- @__vc_content_evaluated = false
125
155
  @__vc_render_in_block = block
126
156
 
127
157
  before_render
@@ -175,16 +205,12 @@ module ViewComponent
175
205
  #
176
206
  # When rendering the parent inside an .erb template, use `#render_parent` instead.
177
207
  def render_parent_to_string
178
- @__vc_parent_render_level ||= 0 # ensure a good starting value
179
-
180
- begin
181
- target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
182
- @__vc_parent_render_level += 1
208
+ target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
209
+ @__vc_parent_render_level += 1
183
210
 
184
- target_render.bind_call(self, @__vc_requested_details)
185
- ensure
186
- @__vc_parent_render_level -= 1
187
- end
211
+ target_render.bind_call(self, @__vc_requested_details)
212
+ ensure
213
+ @__vc_parent_render_level -= 1
188
214
  end
189
215
 
190
216
  # Optional content to be returned before the rendered template.
@@ -216,12 +242,6 @@ module ViewComponent
216
242
  true
217
243
  end
218
244
 
219
- # Override the ActionView::Base initializer so that components
220
- # do not need to define their own initializers.
221
- # @private
222
- def initialize(*)
223
- end
224
-
225
245
  # Re-use original view_context if we're not rendering a component.
226
246
  #
227
247
  # This prevents an exception when rendering a partial inside of a component that has also been rendered outside
@@ -274,7 +294,7 @@ module ViewComponent
274
294
  raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name)
275
295
  #{e.message}
276
296
 
277
- You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'?
297
+ You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}`?
278
298
  MESSAGE
279
299
 
280
300
  raise
@@ -313,12 +333,12 @@ module ViewComponent
313
333
  # @return [String]
314
334
  def content
315
335
  @__vc_content_evaluated = true
316
- return @__vc_content if defined?(@__vc_content)
336
+ return @__vc_content if @__vc_content != :unset
317
337
 
318
338
  @__vc_content =
319
339
  if __vc_render_in_block_provided?
320
340
  view_context.capture(self, &@__vc_render_in_block)
321
- elsif __vc_content_set_by_with_content_defined?
341
+ elsif __vc_content_set_by_with_content?
322
342
  @__vc_content_set_by_with_content
323
343
  end
324
344
  end
@@ -327,7 +347,7 @@ module ViewComponent
327
347
  #
328
348
  # @return [Boolean]
329
349
  def content?
330
- __vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
350
+ __vc_render_in_block_provided? || __vc_content_set_by_with_content?
331
351
  end
332
352
 
333
353
  private
@@ -335,15 +355,15 @@ module ViewComponent
335
355
  attr_reader :view_context
336
356
 
337
357
  def __vc_render_in_block_provided?
338
- defined?(@view_context) && @view_context && @__vc_render_in_block
358
+ @view_context && @__vc_render_in_block
339
359
  end
340
360
 
341
- def __vc_content_set_by_with_content_defined?
342
- defined?(@__vc_content_set_by_with_content)
361
+ def __vc_content_set_by_with_content?
362
+ !@__vc_content_set_by_with_content.nil?
343
363
  end
344
364
 
345
365
  def content_evaluated?
346
- defined?(@__vc_content_evaluated) && @__vc_content_evaluated
366
+ @__vc_content_evaluated
347
367
  end
348
368
 
349
369
  def maybe_escape_html(text)
@@ -380,15 +400,6 @@ module ViewComponent
380
400
  # configured on a per-test basis using `with_controller_class`.
381
401
  #
382
402
 
383
- # Path for component files
384
- #
385
- # ```ruby
386
- # config.view_component.view_component_path = "app/my_components"
387
- # ```
388
- #
389
- # Defaults to `nil`. If this is falsy, `app/components` is used.
390
- #
391
-
392
403
  # Parent class for generated components
393
404
  #
394
405
  # ```ruby
@@ -558,19 +569,13 @@ module ViewComponent
558
569
  # We use `base_label` method here instead of `label` to avoid cases where the method
559
570
  # owner is included in a prefix like `ApplicationComponent.inherited`.
560
571
  child.identifier = caller_locations(1, 10).reject { |l| l.base_label == "inherited" }[0].path
561
-
562
- # If Rails application is loaded, removes the first part of the path and the extension.
563
- if defined?(Rails) && Rails.application
564
- child.virtual_path = child.identifier.gsub(
565
- /(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, ""
566
- )
567
- end
572
+ child.virtual_path = child.name&.underscore
568
573
 
569
574
  # Set collection parameter to the extended component
570
575
  child.with_collection_parameter provided_collection_parameter
571
576
 
572
577
  if instance_methods(false).include?(:render_template_for)
573
- vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
578
+ vc_ancestor_calls = (!@__vc_ancestor_calls.nil?) ? @__vc_ancestor_calls.dup : []
574
579
 
575
580
  vc_ancestor_calls.unshift(instance_method(:render_template_for))
576
581
  child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls)
@@ -16,7 +16,6 @@ module ViewComponent
16
16
  preview_controller: "ViewComponentsController",
17
17
  preview_route: "/rails/view_components",
18
18
  instrumentation_enabled: false,
19
- view_component_path: "app/components",
20
19
  component_parent_class: nil,
21
20
  show_previews: Rails.env.development? || Rails.env.test?,
22
21
  preview_paths: default_preview_paths,
@@ -32,6 +31,12 @@ module ViewComponent
32
31
  # All options under this namespace default to `false` unless otherwise
33
32
  # stated.
34
33
  #
34
+ # #### `#path`
35
+ #
36
+ # Where to put generated components. Defaults to `app/components`:
37
+ #
38
+ # config.view_component.generate.path = "lib/components"
39
+ #
35
40
  # #### `#sidecar`
36
41
  #
37
42
  # Always generate a component with a sidecar directory:
@@ -84,14 +89,14 @@ module ViewComponent
84
89
  #
85
90
  # #### `#use_component_path_for_rspec_tests`
86
91
  #
87
- # Whether to use the `config.view_component_path` when generating new
92
+ # Whether to use `config.generate.path` when generating new
88
93
  # RSpec component tests:
89
94
  #
90
95
  # config.view_component.generate.use_component_path_for_rspec_tests = true
91
96
  #
92
- # When set to `true`, the generator will use the `view_component_path` to
97
+ # When set to `true`, the generator will use the `path` to
93
98
  # decide where to generate the new RSpec component test.
94
- # For example, if the `view_component_path` is
99
+ # For example, if the `path` is
95
100
  # `app/views/components`, then the generator will create a new spec file
96
101
  # in `spec/views/components/` rather than the default `spec/components/`.
97
102
 
@@ -110,12 +115,6 @@ module ViewComponent
110
115
  # Whether ActiveSupport notifications are enabled.
111
116
  # Defaults to `false`.
112
117
 
113
- # @!attribute view_component_path
114
- # @return [String]
115
- # The path in which components, their templates, and their sidecars should
116
- # be stored.
117
- # Defaults to `"app/components"`.
118
-
119
118
  # @!attribute component_parent_class
120
119
  # @return [String]
121
120
  # The parent class from which generated components will inherit.
@@ -171,6 +170,7 @@ module ViewComponent
171
170
  def default_generate_options
172
171
  options = ActiveSupport::OrderedOptions.new(false)
173
172
  options.preview_path = ""
173
+ options.path = "app/components"
174
174
  options
175
175
  end
176
176
  end
@@ -8,24 +8,6 @@ module ViewComponent
8
8
  class Engine < Rails::Engine # :nodoc:
9
9
  config.view_component = ViewComponent::Config.current
10
10
 
11
- if Rails.version.to_f < 8.0
12
- rake_tasks do
13
- load "view_component/rails/tasks/view_component.rake"
14
- end
15
- else
16
- initializer "view_component.stats_directories" do |app|
17
- require "rails/code_statistics"
18
-
19
- if Rails.root.join(ViewComponent::Base.view_component_path).directory?
20
- Rails::CodeStatistics.register_directory("ViewComponents", ViewComponent::Base.view_component_path)
21
- end
22
-
23
- if Rails.root.join("test/components").directory?
24
- Rails::CodeStatistics.register_directory("ViewComponent tests", "test/components", test_directory: true)
25
- end
26
- end
27
- end
28
-
29
11
  initializer "view_component.set_configs" do |app|
30
12
  options = app.config.view_component
31
13
 
@@ -9,7 +9,7 @@ module ViewComponent # :nodoc:
9
9
  def method_missing(method, *args)
10
10
  return super if !method.end_with?("_template")
11
11
 
12
- if defined?(@__vc_inline_template_defined) && @__vc_inline_template_defined
12
+ if @__vc_inline_template_defined
13
13
  raise MultipleInlineTemplatesError
14
14
  end
15
15
 
@@ -38,11 +38,11 @@ module ViewComponent # :nodoc:
38
38
  end
39
39
 
40
40
  def inline_template
41
- @__vc_inline_template if defined?(@__vc_inline_template)
41
+ @__vc_inline_template
42
42
  end
43
43
 
44
44
  def __vc_inline_template_language
45
- @__vc_inline_template_language if defined?(@__vc_inline_template_language)
45
+ @__vc_inline_template_language
46
46
  end
47
47
 
48
48
  def inherited(subclass)
@@ -9,13 +9,18 @@ module ViewComponent
9
9
  attr_writer :__vc_component_instance, :__vc_content_block, :__vc_content
10
10
 
11
11
  def initialize(parent)
12
+ @content = nil
13
+ @__vc_component_instance = nil
14
+ @__vc_content = nil
15
+ @__vc_content_block = nil
16
+ @__vc_content_set_by_with_content = nil
12
17
  @parent = parent
13
18
  end
14
19
 
15
20
  def content?
16
- return true if defined?(@__vc_content) && @__vc_content.present?
17
- return true if defined?(@__vc_content_set_by_with_content) && @__vc_content_set_by_with_content.present?
18
- return true if defined?(@__vc_content_block) && @__vc_content_block.present?
21
+ return true if @__vc_content.present?
22
+ return true if @__vc_content_set_by_with_content.present?
23
+ return true if @__vc_content_block.present?
19
24
  return false if !__vc_component_instance?
20
25
 
21
26
  @__vc_component_instance.content?
@@ -43,11 +48,11 @@ module ViewComponent
43
48
  # If there is no slot renderable, we evaluate the block passed to
44
49
  # the slot and return it.
45
50
  def to_s
46
- return @content if defined?(@content)
51
+ return @content if !@content.nil?
47
52
 
48
53
  view_context = @parent.send(:view_context)
49
54
 
50
- if defined?(@__vc_content_block) && defined?(@__vc_content_set_by_with_content)
55
+ if !@__vc_content_block.nil? && !@__vc_content_set_by_with_content.nil? && !@__vc_content_set_by_with_content.nil?
51
56
  raise DuplicateSlotContentError.new(self.class.name)
52
57
  end
53
58
 
@@ -55,7 +60,7 @@ module ViewComponent
55
60
  if __vc_component_instance?
56
61
  @__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context
57
62
 
58
- if defined?(@__vc_content_block)
63
+ if !@__vc_content_block.nil?
59
64
  # render_in is faster than `parent.render`
60
65
  @__vc_component_instance.render_in(view_context) do |*args|
61
66
  @__vc_content_block.call(*args)
@@ -63,11 +68,11 @@ module ViewComponent
63
68
  else
64
69
  @__vc_component_instance.render_in(view_context)
65
70
  end
66
- elsif defined?(@__vc_content)
71
+ elsif !@__vc_content.nil?
67
72
  @__vc_content
68
- elsif defined?(@__vc_content_block)
73
+ elsif !@__vc_content_block.nil?
69
74
  view_context.capture(&@__vc_content_block)
70
- elsif defined?(@__vc_content_set_by_with_content)
75
+ elsif !@__vc_content_set_by_with_content.nil?
71
76
  @__vc_content_set_by_with_content
72
77
  end
73
78
 
@@ -108,7 +113,7 @@ module ViewComponent
108
113
  private
109
114
 
110
115
  def __vc_component_instance?
111
- defined?(@__vc_component_instance)
116
+ !@__vc_component_instance.nil?
112
117
  end
113
118
  end
114
119
  end
@@ -351,16 +351,26 @@ module ViewComponent
351
351
  end
352
352
 
353
353
  def get_slot(slot_name)
354
+ @__vc_set_slots ||= {}
354
355
  content unless content_evaluated? # ensure content is loaded so slots will be defined
355
356
 
356
- slot = self.class.registered_slots[slot_name]
357
- @__vc_set_slots ||= {}
357
+ # If the slot is set, return it
358
+ return @__vc_set_slots[slot_name] if @__vc_set_slots[slot_name]
358
359
 
359
- if @__vc_set_slots[slot_name]
360
- return @__vc_set_slots[slot_name]
361
- end
360
+ # If there is a default method for the slot, call it
361
+ if (default_method = registered_slots[slot_name][:default_method])
362
+ renderable_value = send(default_method)
363
+ slot = Slot.new(self)
364
+
365
+ if renderable_value.respond_to?(:render_in)
366
+ slot.__vc_component_instance = renderable_value
367
+ else
368
+ slot.__vc_content = renderable_value
369
+ end
362
370
 
363
- if slot[:collection]
371
+ slot
372
+ elsif self.class.registered_slots[slot_name][:collection]
373
+ # If empty slot is a collection, return an empty array
364
374
  []
365
375
  end
366
376
  end
@@ -410,8 +420,6 @@ module ViewComponent
410
420
  end
411
421
  end
412
422
 
413
- @__vc_set_slots ||= {}
414
-
415
423
  if slot_definition[:collection]
416
424
  @__vc_set_slots[slot_name] ||= []
417
425
  @__vc_set_slots[slot_name].push(slot)
@@ -425,7 +433,7 @@ module ViewComponent
425
433
  def set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
426
434
  slot_definition = self.class.registered_slots[slot_name]
427
435
 
428
- if !slot_definition[:collection] && defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]
436
+ if !slot_definition[:collection] && @__vc_set_slots[slot_name]
429
437
  raise ContentAlreadySetForPolymorphicSlotError.new(slot_name)
430
438
  end
431
439
 
@@ -1,8 +1,6 @@
1
1
  module ViewComponent
2
2
  module SlotableDefault
3
3
  def get_slot(slot_name)
4
- @__vc_set_slots ||= {}
5
-
6
4
  return super unless !@__vc_set_slots[slot_name] && (default_method = registered_slots[slot_name][:default_method])
7
5
 
8
6
  renderable_value = send(default_method)
@@ -5,7 +5,7 @@ module ViewComponent
5
5
  MAJOR = 4
6
6
  MINOR = 0
7
7
  PATCH = 0
8
- PRE = "alpha3"
8
+ PRE = "alpha5"
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
11
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.alpha3
4
+ version: 4.0.0.alpha5
5
5
  platform: ruby
6
6
  authors:
7
7
  - ViewComponent Team