actionview-component 1.13.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +43 -55
  5. data/actionview-component.gemspec +2 -2
  6. data/app/controllers/rails/components_controller.rb +4 -4
  7. data/lib/action_view/component.rb +1 -21
  8. data/lib/action_view/component/base.rb +1 -262
  9. data/lib/action_view/component/preview.rb +1 -72
  10. data/lib/action_view/component/railtie.rb +1 -1
  11. data/lib/action_view/component/test_case.rb +1 -4
  12. data/lib/rails/generators/component/component_generator.rb +1 -1
  13. data/lib/rails/generators/test_unit/templates/component_test.rb.tt +1 -1
  14. data/lib/view_component.rb +29 -0
  15. data/lib/view_component/base.rb +273 -0
  16. data/lib/view_component/conversion.rb +9 -0
  17. data/lib/view_component/engine.rb +65 -0
  18. data/lib/view_component/preview.rb +77 -0
  19. data/lib/view_component/previewable.rb +25 -0
  20. data/lib/view_component/render_monkey_patch.rb +31 -0
  21. data/lib/view_component/rendering_monkey_patch.rb +13 -0
  22. data/lib/view_component/template_error.rb +9 -0
  23. data/lib/view_component/test_case.rb +9 -0
  24. data/lib/view_component/test_helpers.rb +43 -0
  25. data/lib/view_component/version.rb +11 -0
  26. data/script/console +1 -1
  27. metadata +14 -10
  28. data/lib/action_view/component/conversion.rb +0 -11
  29. data/lib/action_view/component/engine.rb +0 -67
  30. data/lib/action_view/component/previewable.rb +0 -27
  31. data/lib/action_view/component/render_monkey_patch.rb +0 -34
  32. data/lib/action_view/component/rendering_monkey_patch.rb +0 -15
  33. data/lib/action_view/component/template_error.rb +0 -11
  34. data/lib/action_view/component/test_helpers.rb +0 -45
  35. data/lib/action_view/component/version.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0521a37d11215c6cb0c08a78054dd57a4bdf53ebea65935908611f73264a1727
4
- data.tar.gz: 470a3bdcfb35dcb37162ec708b22491d66f7d1772f40a483a160ed6b49477e3c
3
+ metadata.gz: 20f4f3d429b7cd0741316064cdfd5318a4ccd17f97dbaa00853a2976fc1b8901
4
+ data.tar.gz: '086d91730bd9cfa7f75eb3afff4f97d142a1f968b8e5d7d769024c5599e1c22b'
5
5
  SHA512:
6
- metadata.gz: d92b3636294971bb68eadbc7bf53799642d2407de53cd2d492fec6f1723e96668db1d030043e6c7a3fa0f04b484b72f70d5104dbb252ffbcf4efad02e82cdf78
7
- data.tar.gz: 8b24889733157c2c8890362af41881047a81ae3bd0677d9fb6528ced72dd6505ee1fc12607ee120f978ce7933a7a4d2d000e0e4de3d7e76998df341646c2f1f8
6
+ metadata.gz: a6c458998089d44a6b3f0daa920c5c3b5e19d73d8e7e7fa13b96c1cc0df8abf9f27a61ea1c43bd840eff4aec0327fad1109cd15996ca84304698d772754d0d85
7
+ data.tar.gz: 578b279ca15f8d436676a1f579bfa2ffb32d7cf97a0302668bc9142c89256c4124a653256c6daa9cb0d3a2b368166116e0b0b6427e854e06ca193555ab149bb4
@@ -1,5 +1,11 @@
1
1
  # master
2
2
 
3
+ # v1.14.0
4
+
5
+ * Rename ActionView::Component::Base to ViewComponent::Base
6
+
7
+ *Joel Hawksley*
8
+
3
9
  # v1.13.0
4
10
 
5
11
  * Allow components to be rendered inside controllers.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionview-component (1.13.0)
4
+ actionview-component (1.14.0)
5
5
  capybara (>= 3)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,11 +1,25 @@
1
- _Note: This gem is in the process of a name / API change, see https://github.com/github/actionview-component/issues/206_
1
+ # ViewComponent
2
+ A view component framework for Rails.
2
3
 
3
- _You are viewing the README for the development version of ActionView::Component. If you are using the current release version you can find the README at https://github.com/github/actionview-component/blob/v1.11.1/README.md_
4
+ **Current Status**: Used in production at GitHub. Because of this, all changes will be thoroughly vetted, which could slow down the process of contributing. We will do our best to actively communicate status of pull requests with any contributors. If you have any substantial changes that you would like to make, it would be great to first [open an issue](http://github.com/github/actionview-component/issues/new) to discuss them with us.
4
5
 
5
- # ActionView::Component
6
- `ActionView::Component` is a framework for building view components in Rails.
6
+ ## Migration in progress
7
7
 
8
- **Current Status**: Used in production at GitHub. Because of this, all changes will be thoroughly vetted, which could slow down the process of contributing. We will do our best to actively communicate status of pull requests with any contributors. If you have any substantial changes that you would like to make, it would be great to first [open an issue](http://github.com/github/actionview-component/issues/new) to discuss them with us.
8
+ This gem is in the process of a name / API change from `ActionView::Component` to `ViewComponent`, see https://github.com/github/actionview-component/issues/206.
9
+
10
+ ### What's changing in the migration
11
+
12
+ 1. `ActionView::Component::Base` is now `ViewComponent::Base`.
13
+ 1. Components can only be rendered with `render(MyComponent.new)` syntax.
14
+ 1. Validations are no longer supported by default.
15
+
16
+ ### How to migrate to ViewComponent
17
+
18
+ 1. In `application.rb`, require `view_component/engine`
19
+ 1. Update components to inherit from `ViewComponent::Base`.
20
+ 1. Update component tests to inherit from `ViewComponent::TestCase`.
21
+ 1. Update component previews to inherit from `ViewComponent::Preview`.
22
+ 1. Include `ViewComponent::TestHelpers` in your test suite.
9
23
 
10
24
  ## Roadmap
11
25
 
@@ -36,14 +50,14 @@ $ bundle
36
50
  In `config/application.rb`, add:
37
51
 
38
52
  ```bash
39
- require "action_view/component/railtie"
53
+ require "view_component/engine"
40
54
  ```
41
55
 
42
56
  ## Guide
43
57
 
44
58
  ### What are components?
45
59
 
46
- `ActionView::Component`s are Ruby classes that are used to render views. They take data as input and return output-safe HTML. Think of them as an evolution of the presenter/decorator/view model pattern, inspired by [React Components](https://reactjs.org/docs/react-component.html).
60
+ `ViewComponent`s are Ruby classes that are used to render views. They take data as input and return output-safe HTML. Think of them as an evolution of the presenter/decorator/view model pattern, inspired by [React Components](https://reactjs.org/docs/react-component.html).
47
61
 
48
62
  ### Why components?
49
63
 
@@ -69,11 +83,11 @@ Our views often fail even the most basic standards of code quality we expect out
69
83
 
70
84
  #### Testing
71
85
 
72
- `ActionView::Component` allows views to be unit-tested. In the main GitHub codebase, our unit tests run in around 25ms/test, vs. ~6s/test for integration tests.
86
+ `ViewComponent` allows views to be unit-tested. In the main GitHub codebase, our unit tests run in around 25ms/test, vs. ~6s/test for integration tests.
73
87
 
74
88
  #### Code Coverage
75
89
 
76
- `ActionView::Component` is at least partially compatible with code coverage tools. We’ve seen some success with SimpleCov.
90
+ `ViewComponent` is at least partially compatible with code coverage tools. We’ve seen some success with SimpleCov.
77
91
 
78
92
  #### Data flow
79
93
 
@@ -87,19 +101,17 @@ Components are most effective in cases where view code is reused or needs to be
87
101
 
88
102
  #### Conventions
89
103
 
90
- Components are subclasses of `ActionView::Component::Base` and live in `app/components`. You may wish to create an `ApplicationComponent` that is a subclass of `ActionView::Component::Base` and inherit from that instead.
104
+ Components are subclasses of `ViewComponent::Base` and live in `app/components`. You may wish to create an `ApplicationComponent` that is a subclass of `ViewComponent::Base` and inherit from that instead.
91
105
 
92
106
  Component class names end in -`Component`.
93
107
 
94
108
  Component module names are plural, as they are for controllers. (`Users::AvatarComponent`)
95
109
 
96
- Components support ActiveModel validations. Components are validated after initialization, but before rendering.
97
-
98
- Content passed to an `ActionView::Component` as a block is captured and assigned to the `content` accessor.
110
+ Content passed to a `ViewComponent` as a block is captured and assigned to the `content` accessor.
99
111
 
100
112
  #### Quick start
101
113
 
102
- Use the component generator to create a new `ActionView::Component`.
114
+ Use the component generator to create a new `ViewComponent`.
103
115
 
104
116
  The generator accepts the component name and the list of accepted properties as arguments:
105
117
 
@@ -111,7 +123,7 @@ bin/rails generate component Example title content
111
123
  create app/components/example_component.html.erb
112
124
  ```
113
125
 
114
- `ActionView::Component` includes template generators for the `erb`, `haml`, and `slim` template engines and will use the template engine specified in your Rails config (`config.generators.template_engine`) by default.
126
+ `ViewComponent` includes template generators for the `erb`, `haml`, and `slim` template engines and will use the template engine specified in your Rails config (`config.generators.template_engine`) by default.
115
127
 
116
128
  If you want to override this behavior, you can pass the template engine as an option to the generator:
117
129
 
@@ -125,13 +137,11 @@ bin/rails generate component Example title content --template-engine slim
125
137
 
126
138
  #### Implementation
127
139
 
128
- An `ActionView::Component` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name:
140
+ A `ViewComponent` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name:
129
141
 
130
142
  `app/components/test_component.rb`:
131
143
  ```ruby
132
- class TestComponent < ActionView::Component::Base
133
- validates :content, :title, presence: true
134
-
144
+ class TestComponent < ViewComponent::Base
135
145
  def initialize(title:)
136
146
  @title = title
137
147
  end
@@ -161,29 +171,13 @@ Which returns:
161
171
  <span title="my title">Hello, World!</span>
162
172
  ```
163
173
 
164
- #### Error case
165
-
166
- If the component is rendered with a blank title:
167
-
168
- ```erb
169
- <%= render(TestComponent.new(title: "")) do %>
170
- Hello, World!
171
- <% end %>
172
- ```
173
-
174
- An error will be raised:
175
-
176
- `ActiveModel::ValidationError: Validation failed: Title can't be blank`
177
-
178
174
  #### Content Areas
179
175
 
180
176
  A component can declare additional content areas to be rendered in the component. For example:
181
177
 
182
178
  `app/components/modal_component.rb`:
183
179
  ```ruby
184
- class ModalComponent < ActionView::Component::Base
185
- validates :user, :header, :body, presence: true
186
-
180
+ class ModalComponent < ViewComponent::Base
187
181
  with_content_areas :header, :body
188
182
 
189
183
  def initialize(user:)
@@ -231,9 +225,7 @@ This allows a few different combinations of ways to render the component:
231
225
 
232
226
  `app/components/modal_component.rb`:
233
227
  ```ruby
234
- class ModalComponent < ActionView::Component::Base
235
- validates :header, :body, presence: true
236
-
228
+ class ModalComponent < ViewComponent::Base
237
229
  with_content_areas :header, :body
238
230
 
239
231
  def initialize(header:)
@@ -257,9 +249,7 @@ end
257
249
 
258
250
  `app/components/modal_component.rb`:
259
251
  ```ruby
260
- class ModalComponent < ActionView::Component::Base
261
- validates :header, :body, presence: true
262
-
252
+ class ModalComponent < ViewComponent::Base
263
253
  with_content_areas :header, :body
264
254
 
265
255
  def initialize(header: nil)
@@ -293,9 +283,7 @@ end
293
283
 
294
284
  `app/components/modal_component.rb`:
295
285
  ```ruby
296
- class ModalComponent < ActionView::Component::Base
297
- validates :body, presence: true
298
-
286
+ class ModalComponent < ViewComponent::Base
299
287
  with_content_areas :header, :body
300
288
 
301
289
  def initialize(header: nil)
@@ -372,7 +360,7 @@ The `#render?` hook allows you to move this logic into the Ruby class, leaving y
372
360
 
373
361
  ```ruby
374
362
  # app/components/confirm_email_component.rb
375
- class ConfirmEmailComponent < ActionView::Component::Base
363
+ class ConfirmEmailComponent < ViewComponent::Base
376
364
  def initialize(user:)
377
365
  @user = user
378
366
  end
@@ -402,9 +390,9 @@ end
402
390
  Components are unit tested directly. The `render_inline` test helper is compatible with Capybara matchers, allowing us to test the component above as:
403
391
 
404
392
  ```ruby
405
- require "action_view/component/test_case"
393
+ require "view_component/test_case"
406
394
 
407
- class MyComponentTest < ActionView::Component::TestCase
395
+ class MyComponentTest < ViewComponent::TestCase
408
396
  test "render component" do
409
397
  render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
410
398
 
@@ -430,7 +418,7 @@ end
430
418
  ```
431
419
 
432
420
  ### Previewing Components
433
- `ActionView::Component::Preview` provides a way to see how components look by visiting a special URL that renders them.
421
+ `ViewComponent::Preview` provides a way to see how components look by visiting a special URL that renders them.
434
422
  In the previous example, the preview class for `TestComponent` would be called `TestComponentPreview` and located in `test/components/previews/test_component_preview.rb`.
435
423
  To see the preview of the component with a given title, implement a method that renders the component.
436
424
  You can define as many examples as you want:
@@ -438,7 +426,7 @@ You can define as many examples as you want:
438
426
  ```ruby
439
427
  # test/components/previews/test_component_preview.rb
440
428
 
441
- class TestComponentPreview < ActionView::Component::Preview
429
+ class TestComponentPreview < ViewComponent::Preview
442
430
  def with_default_title
443
431
  render(TestComponent.new(title: "Test component default"))
444
432
  end
@@ -457,7 +445,7 @@ Previews use the application layout by default, but you can also use other layou
457
445
  ```ruby
458
446
  # test/components/previews/test_component_preview.rb
459
447
 
460
- class TestComponentPreview < ActionView::Component::Preview
448
+ class TestComponentPreview < ViewComponent::Preview
461
449
  layout "admin"
462
450
 
463
451
  ...
@@ -488,13 +476,13 @@ If you're using RSpec, you can configure component specs to have access to test
488
476
  `spec/rails_helper.rb`:
489
477
 
490
478
  ```ruby
491
- require "action_view/component/test_helpers"
479
+ require "view_component/test_helpers"
492
480
 
493
481
  RSpec.configure do |config|
494
482
  # ...
495
483
 
496
484
  # Ensure that the test helpers are available in component specs
497
- config.include ActionView::Component::TestHelpers, type: :component
485
+ config.include ViewComponent::TestHelpers, type: :component
498
486
  end
499
487
  ```
500
488
 
@@ -508,7 +496,7 @@ config.action_view_component.preview_path = "#{Rails.root}/spec/components/previ
508
496
 
509
497
  ### Initializer requirement
510
498
 
511
- ActionView::Component requires the presence of an `initialize` method in each component.
499
+ `ViewComponent` requires the presence of an `initialize` method in each component.
512
500
 
513
501
  ## Frequently Asked Questions
514
502
 
@@ -522,7 +510,7 @@ Inline templates have been removed (for now) due to concerns raised by [@soutaro
522
510
 
523
511
  ### Isn't this just like X library?
524
512
 
525
- `ActionView::Component` is far from a novel idea! Popular implementations of view components in Ruby include, but are not limited to:
513
+ `ViewComponent` is far from a novel idea! Popular implementations of view components in Ruby include, but are not limited to:
526
514
 
527
515
  - [trailblazer/cells](https://github.com/trailblazer/cells)
528
516
  - [dry-rb/dry-view](https://github.com/dry-rb/dry-view)
@@ -3,11 +3,11 @@
3
3
 
4
4
  lib = File.expand_path("../lib", __FILE__)
5
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
- require "action_view/component/version"
6
+ require "view_component/version"
7
7
 
8
8
  Gem::Specification.new do |spec|
9
9
  spec.name = "actionview-component"
10
- spec.version = ActionView::Component::VERSION::STRING
10
+ spec.version = ViewComponent::VERSION::STRING
11
11
  spec.authors = ["GitHub Open Source"]
12
12
  spec.email = ["opensource+actionview-component@github.com"]
13
13
 
@@ -15,7 +15,7 @@ class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
15
15
  end
16
16
 
17
17
  def index
18
- @previews = ActionView::Component::Preview.all
18
+ @previews = ViewComponent::Preview.all
19
19
  @page_title = "Component Previews"
20
20
  # rubocop:disable GitHub/RailsControllerRenderPathsExist
21
21
  render "components/index"
@@ -42,16 +42,16 @@ class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
42
42
  private
43
43
 
44
44
  def show_previews? # :doc:
45
- ActionView::Component::Base.show_previews
45
+ ViewComponent::Base.show_previews
46
46
  end
47
47
 
48
48
  def find_preview # :doc:
49
49
  candidates = []
50
50
  params[:path].to_s.scan(%r{/|$}) { candidates << $` }
51
- preview = candidates.detect { |candidate| ActionView::Component::Preview.exists?(candidate) }
51
+ preview = candidates.detect { |candidate| ViewComponent::Preview.exists?(candidate) }
52
52
 
53
53
  if preview
54
- @preview = ActionView::Component::Preview.find(preview)
54
+ @preview = ViewComponent::Preview.find(preview)
55
55
  else
56
56
  raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found"
57
57
  end
@@ -1,24 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_model"
4
- require "action_view"
5
- require "active_support/dependencies/autoload"
6
- require "action_view/component/engine"
7
-
8
- module ActionView
9
- module Component
10
- extend ActiveSupport::Autoload
11
-
12
- autoload :Base
13
- autoload :Conversion
14
- autoload :Preview
15
- autoload :Previewable
16
- autoload :TestHelpers
17
- autoload :TestCase
18
- autoload :RenderMonkeyPatch
19
- autoload :RenderingMonkeyPatch
20
- autoload :TemplateError
21
- end
22
- end
23
-
24
- ActiveModel::Conversion.include ActionView::Component::Conversion
4
+ require "view_component"
@@ -1,274 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/configurable"
4
-
5
3
  module ActionView
6
4
  module Component
7
- class Base < ActionView::Base
5
+ class Base < ViewComponent::Base
8
6
  include ActiveModel::Validations
9
- include ActiveSupport::Configurable
10
- include ActionView::Component::Previewable
11
-
12
- delegate :form_authenticity_token, :protect_against_forgery?, to: :helpers
13
-
14
- class_attribute :content_areas, default: []
15
- self.content_areas = [] # default doesn't work until Rails 5.2
16
-
17
- # Entrypoint for rendering components.
18
- #
19
- # view_context: ActionView context from calling view
20
- # block: optional block to be captured within the view context
21
- #
22
- # returns HTML that has been escaped by the respective template handler
23
- #
24
- # Example subclass:
25
- #
26
- # app/components/my_component.rb:
27
- # class MyComponent < ActionView::Component::Base
28
- # def initialize(title:)
29
- # @title = title
30
- # end
31
- # end
32
- #
33
- # app/components/my_component.html.erb
34
- # <span title="<%= @title %>">Hello, <%= content %>!</span>
35
- #
36
- # In use:
37
- # <%= render MyComponent.new(title: "greeting") do %>world<% end %>
38
- # returns:
39
- # <span title="greeting">Hello, world!</span>
40
- #
41
- def render_in(view_context, &block)
42
- self.class.compile!
43
- @view_context = view_context
44
- @view_renderer ||= view_context.view_renderer
45
- @lookup_context ||= view_context.lookup_context
46
- @view_flow ||= view_context.view_flow
47
- @virtual_path ||= virtual_path
48
- @variant = @lookup_context.variants.first
49
-
50
- old_current_template = @current_template
51
- @current_template = self
52
-
53
- @content = view_context.capture(self, &block) if block_given?
54
-
55
- before_render_check
56
-
57
- if render?
58
- send(self.class.call_method_name(@variant))
59
- else
60
- ""
61
- end
62
- ensure
63
- @current_template = old_current_template
64
- end
65
7
 
66
8
  def before_render_check
67
9
  validate!
68
10
  end
69
-
70
- def render?
71
- true
72
- end
73
-
74
- def initialize(*); end
75
-
76
- def render(options = {}, args = {}, &block)
77
- if options.is_a?(String) || (options.is_a?(Hash) && options.has_key?(:partial))
78
- view_context.render(options, args, &block)
79
- else
80
- super
81
- end
82
- end
83
-
84
- def controller
85
- @controller ||= view_context.controller
86
- end
87
-
88
- # Provides a proxy to access helper methods through
89
- def helpers
90
- @helpers ||= view_context
91
- end
92
-
93
- # Removes the first part of the path and the extension.
94
- def virtual_path
95
- self.class.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
96
- end
97
-
98
- def view_cache_dependencies
99
- []
100
- end
101
-
102
- def format # :nodoc:
103
- @variant
104
- end
105
-
106
- def with(area, content = nil, &block)
107
- unless content_areas.include?(area)
108
- raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
109
- end
110
-
111
- if block_given?
112
- content = view_context.capture(&block)
113
- end
114
-
115
- instance_variable_set("@#{area}".to_sym, content)
116
- nil
117
- end
118
-
119
- private
120
-
121
- def request
122
- @request ||= controller.request
123
- end
124
-
125
- attr_reader :content, :view_context
126
-
127
- # The controller used for testing components.
128
- # Defaults to ApplicationController. This should be set early
129
- # in the initialization process and should be set to a string.
130
- mattr_accessor :test_controller
131
- @@test_controller = "ApplicationController"
132
-
133
- class << self
134
- def inherited(child)
135
- child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
136
-
137
- super
138
- end
139
-
140
- def call_method_name(variant)
141
- if variant.present? && variants.include?(variant)
142
- "call_#{variant}"
143
- else
144
- "call"
145
- end
146
- end
147
-
148
- def source_location
149
- @source_location ||=
150
- begin
151
- # Require `#initialize` to be defined so that we can use `method#source_location`
152
- # to look up the filename of the component.
153
- initialize_method = instance_method(:initialize)
154
- initialize_method.source_location[0] if initialize_method.owner == self
155
- end
156
- end
157
-
158
- def compiled?
159
- @compiled && ActionView::Base.cache_template_loading
160
- end
161
-
162
- def compile!
163
- compile(validate: true)
164
- end
165
-
166
- # Compile templates to instance methods, assuming they haven't been compiled already.
167
- # We could in theory do this on app boot, at least in production environments.
168
- # Right now this just compiles the first time the component is rendered.
169
- def compile(validate: false)
170
- return if compiled?
171
-
172
- if template_errors.present?
173
- raise ActionView::Component::TemplateError.new(template_errors) if validate
174
- return false
175
- end
176
-
177
- templates.each do |template|
178
- class_eval <<-RUBY, template[:path], -1
179
- def #{call_method_name(template[:variant])}
180
- @output_buffer = ActionView::OutputBuffer.new
181
- #{compiled_template(template[:path])}
182
- end
183
- RUBY
184
- end
185
-
186
- @compiled = true
187
- end
188
-
189
- def variants
190
- templates.map { |template| template[:variant] }
191
- end
192
-
193
- # we'll eventually want to update this to support other types
194
- def type
195
- "text/html"
196
- end
197
-
198
- def identifier
199
- source_location
200
- end
201
-
202
- def with_content_areas(*areas)
203
- if areas.include?(:content)
204
- raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
205
- end
206
- attr_reader *areas
207
- self.content_areas = areas
208
- end
209
-
210
- private
211
-
212
- def matching_views_in_source_location
213
- return [] unless source_location
214
- (Dir["#{source_location.chomp(File.extname(source_location))}.*{#{ActionView::Template.template_handler_extensions.join(',')}}"] - [source_location])
215
- end
216
-
217
- def templates
218
- @templates ||=
219
- matching_views_in_source_location.each_with_object([]) do |path, memo|
220
- pieces = File.basename(path).split(".")
221
-
222
- memo << {
223
- path: path,
224
- variant: pieces.second.split("+").second&.to_sym,
225
- handler: pieces.last
226
- }
227
- end
228
- end
229
-
230
- def template_errors
231
- @template_errors ||=
232
- begin
233
- errors = []
234
- if source_location.nil?
235
- # Require `#initialize` to be defined so that we can use `method#source_location`
236
- # to look up the filename of the component.
237
- errors << "#{self} must implement #initialize."
238
- end
239
-
240
- errors << "Could not find a template file for #{self}." if templates.empty?
241
-
242
- if templates.count { |template| template[:variant].nil? } > 1
243
- errors << "More than one template found for #{self}. There can only be one default template file per component."
244
- end
245
-
246
- invalid_variants = templates
247
- .group_by { |template| template[:variant] }
248
- .map { |variant, grouped| variant if grouped.length > 1 }
249
- .compact
250
- .sort
251
-
252
- unless invalid_variants.empty?
253
- errors << "More than one template found for #{'variant'.pluralize(invalid_variants.count)} #{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{self}. There can only be one template file per variant."
254
- end
255
- errors
256
- end
257
- end
258
-
259
- def compiled_template(file_path)
260
- handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
261
- template = File.read(file_path)
262
-
263
- if handler.method(:call).parameters.length > 1
264
- handler.call(self, template)
265
- else # remove before upstreaming into Rails
266
- handler.call(OpenStruct.new(source: template, identifier: identifier, type: type))
267
- end
268
- end
269
- end
270
-
271
- ActiveSupport.run_load_hooks(:action_view_component, self)
272
11
  end
273
12
  end
274
13
  end