actionview-component 1.8.1 → 1.12.0

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: d236db5b36182b7b47553a9d5edcba890edcda2c1dc09e12a483f59fcad1e43c
4
- data.tar.gz: 83d47a9e35480a4d30f2f8ba2e2759ecd17423513a8500e3a656c5e83c600735
3
+ metadata.gz: cfaa667b7c02706e492cb8f92221bcc2ec39137b525faccdaadbef76435c9880
4
+ data.tar.gz: b9bf382421e68d742e5427b67b5b84163cbca8c47fca9e6789391ceea4bc5f2e
5
5
  SHA512:
6
- metadata.gz: d437983458628e9b66587bcecd015bece3728391275b82453e14085f2461806c97501353725d9866c8aae62a75481aa7cd1a2db46206e1cdaf15ffff824b8a30
7
- data.tar.gz: 6573fe5fe6ca95259145e37e12011c10e3956df14a31597a5ebbf59f1114f85854049c1e7e98af6f176f422c452c49da05449be509a2ec5fe8d2e5a9c9adcd07
6
+ metadata.gz: 8563f9ef8e370a5a0dfdc15813e72b2dd35c6f0b37de6fa81ba84a50b20427049d0e00fc5ecbb72ff4f15bb50e0793e5afcaf59ac85d3d5b78e4091b7080ca79
7
+ data.tar.gz: 5447df2d42fddbb35b90d05e328e3de3c78d7db2a44d92a29de49986630b9dbc2203012bc9fdc48812422b8a761e881f0d106e0d8abb55ed412b07687c00d0dd
@@ -23,3 +23,5 @@ context or references to similar behavior in other libraries? -->
23
23
  **Rails version**:
24
24
 
25
25
  **Ruby version**:
26
+
27
+ **Gem version**:
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  matrix:
10
10
  rails_version: [5.0.0, 5.2.3, 6.0.0, master]
11
- ruby_version: [2.4.x, 2.5.x, 2.6.x]
11
+ ruby_version: [2.5.x, 2.6.x, 2.7.x]
12
12
  exclude:
13
13
  - rails_version: master
14
14
  ruby_version: 2.4.x
@@ -22,7 +22,7 @@ jobs:
22
22
  ruby-version: ${{ matrix.ruby_version }}
23
23
  - name: Build and test with Rake
24
24
  run: |
25
- gem install bundler:1.14.0
25
+ gem install bundler:1.17.3
26
26
  bundle update
27
27
  bundle install --jobs 4 --retry 3
28
28
  bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,3 +1,47 @@
1
+ # master
2
+
3
+ # v1.12.0
4
+
5
+ * Revert: Remove initializer requirement for Ruby 2.7+
6
+
7
+ *Joel Hawksley*
8
+
9
+ * Restructure Railtie into Engine
10
+
11
+ *Sean Doyle*
12
+
13
+ * Allow components to override before_render_check
14
+
15
+ *Joel Hawksley*
16
+
17
+ # v1.11.1
18
+
19
+ * Relax Capybara requirement.
20
+
21
+ *Joel Hawksley*
22
+
23
+ # v1.11.0
24
+
25
+ * Add support for Capybara matchers.
26
+
27
+ *Joel Hawksley*
28
+
29
+ * Add erb, haml, & slim template generators
30
+
31
+ *Asger Behncke Jacobsen*
32
+
33
+ # v1.10.0
34
+
35
+ * Deprecate all `render` syntaxes except for `render(MyComponent.new(foo: :bar))`
36
+
37
+ *Joel Hawksley*
38
+
39
+ # v1.9.0
40
+
41
+ * Remove initializer requirement for Ruby 2.7+
42
+
43
+ *Dylan Clark*
44
+
1
45
  # v1.8.1
2
46
 
3
47
  * Run validation checks before calling `#render?`.
data/CONTRIBUTING.md CHANGED
@@ -34,7 +34,7 @@ Here are a few things you can do that will increase the likelihood of your pull
34
34
  If you are the current maintainer of this gem:
35
35
 
36
36
  1. Create a branch for the release: `git checkout -b release-vxx.xx.xx`
37
- 1. Bump gem version in `lib/action_view/component/version.rb`.
37
+ 1. Bump gem version in `lib/action_view/component/version.rb`. Try to adhere to SemVer.
38
38
  1. Add version heading/entries to `CHANGELOG.md`.
39
39
  1. Make sure your local dependencies are up to date: `bundle`
40
40
  1. Ensure that tests are green: `bundle exec rake`
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionview-component (1.8.1)
4
+ actionview-component (1.12.0)
5
+ capybara (>= 3)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
@@ -61,6 +62,8 @@ GEM
61
62
  minitest (~> 5.1)
62
63
  tzinfo (~> 1.1)
63
64
  zeitwerk (~> 2.1, >= 2.1.8)
65
+ addressable (2.7.0)
66
+ public_suffix (>= 2.0.2, < 5.0)
64
67
  ast (2.4.0)
65
68
  better_html (1.0.14)
66
69
  actionview (>= 4.0)
@@ -71,6 +74,14 @@ GEM
71
74
  parser (>= 2.4)
72
75
  smart_properties
73
76
  builder (3.2.3)
77
+ capybara (3.31.0)
78
+ addressable
79
+ mini_mime (>= 0.1.3)
80
+ nokogiri (~> 1.8)
81
+ rack (>= 1.6.0)
82
+ rack-test (>= 0.6.3)
83
+ regexp_parser (~> 1.5)
84
+ xpath (~> 3.2)
74
85
  concurrent-ruby (1.1.5)
75
86
  crass (1.0.5)
76
87
  erubi (1.8.0)
@@ -96,11 +107,12 @@ GEM
96
107
  mini_portile2 (2.4.0)
97
108
  minitest (5.1.0)
98
109
  nio4r (2.5.2)
99
- nokogiri (1.10.5)
110
+ nokogiri (1.10.8)
100
111
  mini_portile2 (~> 2.4.0)
101
112
  parallel (1.17.0)
102
113
  parser (2.6.3.0)
103
114
  ast (~> 2.4.0)
115
+ public_suffix (4.0.3)
104
116
  rack (2.0.8)
105
117
  rack-test (1.1.0)
106
118
  rack (>= 1.0, < 3)
@@ -132,6 +144,7 @@ GEM
132
144
  thor (>= 0.20.3, < 2.0)
133
145
  rainbow (3.0.0)
134
146
  rake (10.5.0)
147
+ regexp_parser (1.7.0)
135
148
  rubocop (0.74.0)
136
149
  jaro_winkler (~> 1.5.1)
137
150
  parallel (~> 1.10)
@@ -166,6 +179,8 @@ GEM
166
179
  websocket-driver (0.7.1)
167
180
  websocket-extensions (>= 0.1.0)
168
181
  websocket-extensions (0.1.4)
182
+ xpath (3.2.0)
183
+ nokogiri (~> 1.8)
169
184
  zeitwerk (2.1.10)
170
185
 
171
186
  PLATFORMS
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ _Note: This gem is in the process of a name / API change, see https://github.com/github/actionview-component/issues/206_
2
+
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
+
1
5
  # ActionView::Component
2
6
  `ActionView::Component` is a framework for building view components in Rails.
3
7
 
@@ -5,17 +9,17 @@
5
9
 
6
10
  ## Roadmap
7
11
 
8
- This gem is meant to serve as a precursor to upstreaming the `ActionView::Component` class into Rails. It also serves to enable the usage of `ActionView::Component` in older versions of Rails.
12
+ Support for third-party component frameworks was merged into Rails `6.1.0.alpha` in https://github.com/rails/rails/pull/36388 and https://github.com/rails/rails/pull/37919. Our goal with this project is to provide a first-class component framework for this new capability in Rails.
9
13
 
10
- Preliminary support for rendering components was merged into Rails `6.1.0.alpha` in https://github.com/rails/rails/pull/36388. Assuming `ActionView::Component` makes it into Rails, this gem will then exist to serve as a backport.
14
+ This gem includes a backport of those changes for Rails `5.0.0` through `6.1.0.alpha`.
11
15
 
12
16
  ## Design philosophy
13
17
 
14
- As the goal of this gem is to be upstreamed into Rails, it is designed to integrate as seamlessly as possible, with the [least surprise](https://www.artima.com/intv/ruby4.html).
18
+ This library is designed to integrate as seamlessly as possible with Rails, with the [least surprise](https://www.artima.com/intv/ruby4.html).
15
19
 
16
20
  ## Compatibility
17
21
 
18
- `actionview-component` is tested for compatibility with combinations of Ruby `2.4`/`2.5`/`2.6` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
22
+ `actionview-component` is tested for compatibility with combinations of Ruby `2.5`/`2.6`/`2.7` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
19
23
 
20
24
  ## Installation
21
25
  Add this line to your application's Gemfile:
@@ -75,24 +79,14 @@ Our views often fail even the most basic standards of code quality we expect out
75
79
 
76
80
  By clearly defining the context necessary to render a component, we’ve found them to be easier to reuse than partials.
77
81
 
78
- #### Performance
79
-
80
- In early benchmarks, we’ve seen performance improvements over the existing rendering pipeline. For a test page with nested renders 10 levels deep, we’re seeing around a 5x increase in speed over partials:
81
-
82
- ```
83
- Comparison:
84
- component: 6515.4 i/s
85
- partial: 1251.2 i/s - 5.21x slower
86
- ```
87
-
88
- _Rails 6.1.0.alpha, [joelhawksley/actionview-component-demo](https://github.com/joelhawksley/actionview-component-demo), /benchmark route, via `RAILS_ENV=production rails s`, measured with [evanphx/benchmark-ips](https://github.com/evanphx/benchmark-ips)_
89
-
90
82
  ### When should I use components?
91
83
 
92
84
  Components are most effective in cases where view code is reused or needs to be tested directly.
93
85
 
94
86
  ### Building components
95
87
 
88
+ #### Conventions
89
+
96
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.
97
91
 
98
92
  Component class names end in -`Component`.
@@ -117,6 +111,18 @@ bin/rails generate component Example title content
117
111
  create app/components/example_component.html.erb
118
112
  ```
119
113
 
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.
115
+
116
+ If you want to override this behavior, you can pass the template engine as an option to the generator:
117
+
118
+ ```bash
119
+ bin/rails generate component Example title content --template-engine slim
120
+ invoke test_unit
121
+ create test/components/example_component_test.rb
122
+ create app/components/example_component.rb
123
+ create app/components/example_component.html.slim
124
+ ```
125
+
120
126
  #### Implementation
121
127
 
122
128
  An `ActionView::Component` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name:
@@ -144,7 +150,7 @@ end
144
150
  We can render it in a view as:
145
151
 
146
152
  ```erb
147
- <%= render(TestComponent, title: "my title") do %>
153
+ <%= render(TestComponent.new(title: "my title")) do %>
148
154
  Hello, World!
149
155
  <% end %>
150
156
  ```
@@ -155,42 +161,12 @@ Which returns:
155
161
  <span title="my title">Hello, World!</span>
156
162
  ```
157
163
 
158
- ##### Supported `render` syntaxes
159
-
160
- Components can be rendered via:
161
-
162
- `render(TestComponent, foo: :bar)`
163
-
164
- `render(component: TestComponent, locals: { foo: :bar })`
165
-
166
- **Rendering components through models**
167
-
168
- Passing model instances will cause `render` to look for its respective component class.
169
-
170
- The component is instantiated with the rendered model instance.
171
-
172
- Example for a `Post` model:
173
-
174
- `render(@post)`
175
-
176
- ```ruby
177
- class PostComponent < ActionView::Component::Base
178
- def initialize(post)
179
- @post = post
180
- end
181
- end
182
- ```
183
-
184
- The following syntax has been deprecated and will be removed in v2.0.0:
185
-
186
- `render(TestComponent.new(foo: :bar))`
187
-
188
164
  #### Error case
189
165
 
190
166
  If the component is rendered with a blank title:
191
167
 
192
168
  ```erb
193
- <%= render(TestComponent, title: "") do %>
169
+ <%= render(TestComponent.new(title: "")) do %>
194
170
  Hello, World!
195
171
  <% end %>
196
172
  ```
@@ -213,6 +189,8 @@ class ModalComponent < ActionView::Component::Base
213
189
  def initialize(user:)
214
190
  @user = user
215
191
  end
192
+
193
+ attr_reader :user
216
194
  end
217
195
  ```
218
196
 
@@ -220,16 +198,16 @@ end
220
198
  ```erb
221
199
  <div class="modal">
222
200
  <div class="header"><%= header %></div>
223
- <div class="body"><%= body %>"></div>
201
+ <div class="body"><%= body %></div>
224
202
  </div>
225
203
  ```
226
204
 
227
205
  We can render it in a view as:
228
206
 
229
207
  ```erb
230
- <%= render(ModalComponent, user: {name: 'Jane'}) do |component| %>
208
+ <%= render(ModalComponent.new(user: {name: 'Jane'})) do |component| %>
231
209
  <% component.with(:header) do %>
232
- Hello <%= user[:name] %>
210
+ Hello <%= component.user[:name] %>
233
211
  <% end %>
234
212
  <% component.with(:body) do %>
235
213
  <p>Have a great day.</p>
@@ -265,8 +243,8 @@ end
265
243
  ```
266
244
 
267
245
  ```erb
268
- <%= render(ModalComponent, header: "Hi!") do |component| %>
269
- <% help_enabled? && component.with(:header) do %>
246
+ <%= render(ModalComponent.new(header: "Hi!")) do |component| %>
247
+ <% component.with(:header) do %>
270
248
  <span class="help_icon"><%= component.header %></span>
271
249
  <% end %>
272
250
  <% component.with(:body) do %>
@@ -292,7 +270,7 @@ end
292
270
 
293
271
  `app/views/render_arg.html.erb`:
294
272
  ```erb
295
- <%= render(ModalComponent, header: "Hi!") do |component| %>
273
+ <%= render(ModalComponent.new(header: "Hi!")) do |component| %>
296
274
  <% component.with(:body) do %>
297
275
  <p>Have a great day.</p>
298
276
  <% end %>
@@ -332,13 +310,13 @@ end
332
310
  <% if header %>
333
311
  <div class="header"><%= header %></div>
334
312
  <% end %>
335
- <div class="body"><%= body %>"></div>
313
+ <div class="body"><%= body %></div>
336
314
  </div>
337
315
  ```
338
316
 
339
317
  `app/views/render_arg.html.erb`:
340
318
  ```erb
341
- <%= render(ModalComponent, header: "Hi!") do |component| %>
319
+ <%= render(ModalComponent.new(header: "Hi!")) do |component| %>
342
320
  <% component.with(:body) do %>
343
321
  <p>Have a great day.</p>
344
322
  <% end %>
@@ -347,7 +325,7 @@ end
347
325
 
348
326
  `app/views/with_block.html.erb`:
349
327
  ```erb
350
- <%= render(ModalComponent) do |component| %>
328
+ <%= render(ModalComponent.new) do |component| %>
351
329
  <% component.with(:header) do %>
352
330
  <span class="help_icon">Hello</span>
353
331
  <% end %>
@@ -359,7 +337,7 @@ end
359
337
 
360
338
  `app/views/no_header.html.erb`:
361
339
  ```erb
362
- <%= render(ModalComponent) do |component| %>
340
+ <%= render(ModalComponent.new) do |component| %>
363
341
  <% component.with(:body) do %>
364
342
  <p>Have a great day.</p>
365
343
  <% end %>
@@ -386,7 +364,7 @@ or the view that renders the component:
386
364
  ```erb
387
365
  <!-- app/views/_banners.html.erb -->
388
366
  <% if current_user.requires_confirmation? %>
389
- <%= render(ConfirmEmailComponent, user: current_user) %>
367
+ <%= render(ConfirmEmailComponent.new(user: current_user)) %>
390
368
  <% end %>
391
369
  ```
392
370
 
@@ -394,7 +372,7 @@ The `#render?` hook allows you to move this logic into the Ruby class, leaving y
394
372
 
395
373
  ```ruby
396
374
  # app/components/confirm_email_component.rb
397
- class ConfirmEmailComponent < ApplicationComponent
375
+ class ConfirmEmailComponent < ActionView::Component::Base
398
376
  def initialize(user:)
399
377
  @user = user
400
378
  end
@@ -402,6 +380,8 @@ class ConfirmEmailComponent < ApplicationComponent
402
380
  def render?
403
381
  @user.requires_confirmation?
404
382
  end
383
+
384
+ attr_reader :user
405
385
  end
406
386
  ```
407
387
 
@@ -414,22 +394,21 @@ end
414
394
 
415
395
  ```erb
416
396
  <!-- app/views/_banners.html.erb -->
417
- <%= render(ConfirmEmailComponent, user: current_user) %>
397
+ <%= render(ConfirmEmailComponent.new(user: current_user)) %>
418
398
  ```
419
399
 
420
400
  ### Testing
421
401
 
422
- Components are unit tested directly. The `render_inline` test helper wraps the result in `Nokogiri.HTML`, allowing us to test the component above as:
402
+ Components are unit tested directly. The `render_inline` test helper is compatible with Capybara matchers, allowing us to test the component above as:
423
403
 
424
404
  ```ruby
425
405
  require "action_view/component/test_case"
426
406
 
427
407
  class MyComponentTest < ActionView::Component::TestCase
428
408
  test "render component" do
429
- assert_equal(
430
- %(<span title="my title">Hello, World!</span>),
431
- render_inline(TestComponent, title: "my title") { "Hello, World!" }.to_html
432
- )
409
+ render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
410
+
411
+ assert_selector("span[title='my title']", "Hello, World!")
433
412
  end
434
413
  end
435
414
  ```
@@ -443,10 +422,9 @@ To test a specific variant you can wrap your test with the `with_variant` helper
443
422
  ```ruby
444
423
  test "render component for tablet" do
445
424
  with_variant :tablet do
446
- assert_equal(
447
- %(<span title="my title">Hello, tablets!</span>),
448
- render_inline(TestComponent, title: "my title") { "Hello, tablets!" }.css("span").to_html
449
- )
425
+ render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }
426
+
427
+ assert_selector("span[title='my title']", "Hello, tablets!")
450
428
  end
451
429
  end
452
430
  ```
@@ -462,11 +440,11 @@ You can define as many examples as you want:
462
440
 
463
441
  class TestComponentPreview < ActionView::Component::Preview
464
442
  def with_default_title
465
- render(TestComponent, title: "Test component default")
443
+ render(TestComponent.new(title: "Test component default"))
466
444
  end
467
445
 
468
446
  def with_long_title
469
- render(TestComponent, title: "This is a really long title to see how the component renders this")
447
+ render(TestComponent.new(title: "This is a really long title to see how the component renders this"))
470
448
  end
471
449
  end
472
450
  ```
@@ -528,6 +506,10 @@ To use component previews, set the following in `config/application.rb`:
528
506
  config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
529
507
  ```
530
508
 
509
+ ### Initializer requirement
510
+
511
+ ActionView::Component requires the presence of an `initialize` method in each component.
512
+
531
513
  ## Frequently Asked Questions
532
514
 
533
515
  ### Can I use other templating languages besides ERB?
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.required_ruby_version = ">= 2.3.0"
36
36
 
37
+ spec.add_runtime_dependency "capybara", ">= 3"
37
38
  spec.add_development_dependency "bundler", ">= 1.14"
38
39
  spec.add_development_dependency "rake", "~> 10.0"
39
40
  spec.add_development_dependency "minitest", "= 5.1.0"
@@ -3,7 +3,7 @@
3
3
  require "rails/application_controller"
4
4
 
5
5
  class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
6
- prepend_view_path File.expand_path("templates/rails", __dir__)
6
+ prepend_view_path File.expand_path("../../../lib/railties/lib/rails/templates/rails", __dir__)
7
7
  prepend_view_path "#{Rails.root}/app/views/" if defined?(Rails.root)
8
8
 
9
9
  around_action :set_locale, only: :previews
@@ -17,19 +17,25 @@ class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
17
17
  def index
18
18
  @previews = ActionView::Component::Preview.all
19
19
  @page_title = "Component Previews"
20
- render template: "components/index"
20
+ # rubocop:disable GitHub/RailsControllerRenderPathsExist
21
+ render "components/index"
22
+ # rubocop:enable GitHub/RailsControllerRenderPathsExist
21
23
  end
22
24
 
23
25
  def previews
24
26
  if params[:path] == @preview.preview_name
25
27
  @page_title = "Component Previews for #{@preview.preview_name}"
26
- render template: "components/previews"
28
+ # rubocop:disable GitHub/RailsControllerRenderPathsExist
29
+ render "components/previews"
30
+ # rubocop:enable GitHub/RailsControllerRenderPathsExist
27
31
  else
28
32
  @example_name = File.basename(params[:path])
29
33
  @render_args = @preview.render_args(@example_name)
30
34
  layout = @render_args[:layout]
31
35
  opts = layout.nil? ? {} : { layout: layout }
32
- render template: "components/preview", **opts
36
+ # rubocop:disable GitHub/RailsControllerRenderPathsExist
37
+ render "components/preview", **opts
38
+ # rubocop:enable GitHub/RailsControllerRenderPathsExist
33
39
  end
34
40
  end
35
41
 
@@ -3,7 +3,7 @@
3
3
  require "active_model"
4
4
  require "action_view"
5
5
  require "active_support/dependencies/autoload"
6
- require "action_view/component/railtie"
6
+ require "action_view/component/engine"
7
7
 
8
8
  module ActionView
9
9
  module Component
@@ -14,10 +14,9 @@ module ActionView
14
14
  class_attribute :content_areas, default: []
15
15
  self.content_areas = [] # default doesn't work until Rails 5.2
16
16
 
17
- # Entrypoint for rendering components. Called by ActionView::Base#render.
17
+ # Entrypoint for rendering components.
18
18
  #
19
19
  # view_context: ActionView context from calling view
20
- # args(hash): params to be passed to component being rendered
21
20
  # block: optional block to be captured within the view context
22
21
  #
23
22
  # returns HTML that has been escaped by the respective template handler
@@ -35,11 +34,11 @@ module ActionView
35
34
  # <span title="<%= @title %>">Hello, <%= content %>!</span>
36
35
  #
37
36
  # In use:
38
- # <%= render MyComponent, title: "greeting" do %>world<% end %>
37
+ # <%= render MyComponent.new(title: "greeting") do %>world<% end %>
39
38
  # returns:
40
39
  # <span title="greeting">Hello, world!</span>
41
40
  #
42
- def render_in(view_context, *args, &block)
41
+ def render_in(view_context, &block)
43
42
  self.class.compile!
44
43
  @view_context = view_context
45
44
  @view_renderer ||= view_context.view_renderer
@@ -53,15 +52,21 @@ module ActionView
53
52
 
54
53
  @content = view_context.capture(self, &block) if block_given?
55
54
 
56
- validate!
57
-
58
- return "" unless render?
55
+ before_render_check
59
56
 
60
- send(self.class.call_method_name(@variant))
57
+ if render?
58
+ send(self.class.call_method_name(@variant))
59
+ else
60
+ ""
61
+ end
61
62
  ensure
62
63
  @current_template = old_current_template
63
64
  end
64
65
 
66
+ def before_render_check
67
+ validate!
68
+ end
69
+
65
70
  def render?
66
71
  true
67
72
  end
@@ -142,18 +147,12 @@ module ActionView
142
147
 
143
148
  def source_location
144
149
  @source_location ||=
145
- begin
146
- # Require #initialize to be defined so that we can use
147
- # method#source_location to look up the file name
148
- # of the component.
149
- #
150
- # If we were able to only support Ruby 2.7+,
151
- # We could just use Module#const_source_location,
152
- # rendering this unnecessary.
153
- #
154
- initialize_method = instance_method(:initialize)
155
- initialize_method.source_location[0] if initialize_method.owner == self
156
- end
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
157
156
  end
158
157
 
159
158
  def compiled?
@@ -232,7 +231,12 @@ module ActionView
232
231
  @template_errors ||=
233
232
  begin
234
233
  errors = []
235
- errors << "#{self} must implement #initialize." if source_location.nil?
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
+
236
240
  errors << "Could not find a template file for #{self}." if templates.empty?
237
241
 
238
242
  if templates.count { |template| template[:variant].nil? } > 1
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "action_view/component"
5
+
6
+ module ActionView
7
+ module Component
8
+ class Engine < Rails::Engine # :nodoc:
9
+ config.action_view_component = ActiveSupport::OrderedOptions.new
10
+
11
+ initializer "action_view_component.set_configs" do |app|
12
+ options = app.config.action_view_component
13
+
14
+ options.show_previews = Rails.env.development? if options.show_previews.nil?
15
+
16
+ if options.show_previews
17
+ options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/components/previews" : nil
18
+ end
19
+
20
+ ActiveSupport.on_load(:action_view_component) do
21
+ options.each { |k, v| send("#{k}=", v) }
22
+ end
23
+ end
24
+
25
+ initializer "action_view_component.set_autoload_paths" do |app|
26
+ options = app.config.action_view_component
27
+
28
+ if options.show_previews && options.preview_path
29
+ ActiveSupport::Dependencies.autoload_paths << options.preview_path
30
+ end
31
+ end
32
+
33
+ initializer "action_view_component.eager_load_actions" do
34
+ ActiveSupport.on_load(:after_initialize) do
35
+ ActionView::Component::Base.descendants.each(&:compile)
36
+ end
37
+ end
38
+
39
+ initializer "action_view_component.compile_config_methods" do
40
+ ActiveSupport.on_load(:action_view_component) do
41
+ config.compile_methods! if config.respond_to?(:compile_methods!)
42
+ end
43
+ end
44
+
45
+ initializer "action_view_component.monkey_patch_render" do
46
+ ActiveSupport.on_load(:action_view) do
47
+ ActionView::Base.prepend ActionView::Component::RenderMonkeyPatch
48
+ end
49
+ end
50
+
51
+ config.after_initialize do |app|
52
+ options = app.config.action_view_component
53
+
54
+ if options.show_previews
55
+ app.routes.prepend do
56
+ get "/rails/components" => "rails/components#index", :internal => true
57
+ get "/rails/components/*path" => "rails/components#previews", :internal => true
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,65 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails"
4
- require "action_view/component"
5
-
6
- module ActionView
7
- module Component
8
- class Railtie < Rails::Railtie # :nodoc:
9
- config.action_view_component = ActiveSupport::OrderedOptions.new
10
-
11
- initializer "action_view_component.set_configs" do |app|
12
- options = app.config.action_view_component
13
-
14
- options.show_previews = Rails.env.development? if options.show_previews.nil?
15
-
16
- if options.show_previews
17
- options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/components/previews" : nil
18
- end
19
-
20
- ActiveSupport.on_load(:action_view_component) do
21
- options.each { |k, v| send("#{k}=", v) }
22
- end
23
- end
24
-
25
- initializer "action_view_component.set_autoload_paths" do |app|
26
- require "railties/lib/rails/components_controller"
27
-
28
- options = app.config.action_view_component
29
-
30
- if options.show_previews && options.preview_path
31
- ActiveSupport::Dependencies.autoload_paths << options.preview_path
32
- end
33
- end
34
-
35
- initializer "action_view_component.eager_load_actions" do
36
- ActiveSupport.on_load(:after_initialize) do
37
- ActionView::Component::Base.descendants.each(&:compile)
38
- end
39
- end
40
-
41
- initializer "action_view_component.compile_config_methods" do
42
- ActiveSupport.on_load(:action_view_component) do
43
- config.compile_methods! if config.respond_to?(:compile_methods!)
44
- end
45
- end
46
-
47
- initializer "action_view_component.monkey_patch_render" do
48
- ActiveSupport.on_load(:action_view) do
49
- ActionView::Base.prepend ActionView::Component::RenderMonkeyPatch
50
- end
51
- end
52
-
53
- config.after_initialize do |app|
54
- options = app.config.action_view_component
55
-
56
- if options.show_previews
57
- app.routes.prepend do
58
- get "/rails/components" => "rails/components#index", :internal => true
59
- get "/rails/components/*path" => "rails/components#previews", :internal => true
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
3
+ require "action_view/component/engine"
@@ -1,24 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Monkey patch ActionView::Base#render to support ActionView::Component
4
- #
5
- # A version of this monkey patch was upstreamed in https://github.com/rails/rails/pull/36388
6
- # We'll need to upstream an updated version of this eventually.
7
4
  module ActionView
8
5
  module Component
9
6
  module RenderMonkeyPatch # :nodoc:
10
7
  def render(options = {}, args = {}, &block)
11
8
  if options.respond_to?(:render_in)
9
+ options.render_in(self, &block)
10
+ elsif options.is_a?(Class) && options < ActionView::Component::Base
12
11
  ActiveSupport::Deprecation.warn(
13
- "passing component instances (`render MyComponent.new(foo: :bar)`) has been deprecated and will be removed in v2.0.0. Use `render MyComponent, foo: :bar` instead."
12
+ "`render MyComponent, foo: :bar` has been deprecated and will be removed in v2.0.0. Use `render MyComponent.new(foo: :bar)` instead."
14
13
  )
15
14
 
16
- options.render_in(self, &block)
17
- elsif options.is_a?(Class) && options < ActionView::Component::Base
18
15
  options.new(args).render_in(self, &block)
19
16
  elsif options.is_a?(Hash) && options.has_key?(:component)
17
+ ActiveSupport::Deprecation.warn(
18
+ "`render component: MyComponent, locals: { foo: :bar }` has been deprecated and will be removed in v2.0.0. Use `render MyComponent.new(foo: :bar)` instead."
19
+ )
20
+
20
21
  options[:component].new(options[:locals]).render_in(self, &block)
21
22
  elsif options.respond_to?(:to_component_class) && !options.to_component_class.nil?
23
+ ActiveSupport::Deprecation.warn(
24
+ "rendering objects that respond_to `to_component_class` has been deprecated and will be removed in v2.0.0. Use `render MyComponent.new(foo: :bar)` instead."
25
+ )
26
+
22
27
  options.to_component_class.new(options).render_in(self, &block)
23
28
  else
24
29
  super
@@ -1,10 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "capybara/minitest"
4
+
3
5
  module ActionView
4
6
  module Component
5
7
  module TestHelpers
8
+ include Capybara::Minitest::Assertions
9
+
10
+ def page
11
+ Capybara::Node::Simple.new(@raw)
12
+ end
13
+
6
14
  def render_inline(component, **args, &block)
7
- Nokogiri::HTML.fragment(controller.view_context.render(component, args, &block))
15
+ @raw = controller.view_context.render(component, args, &block)
16
+
17
+ Nokogiri::HTML.fragment(@raw)
8
18
  end
9
19
 
10
20
  def controller
@@ -4,8 +4,8 @@ module ActionView
4
4
  module Component
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 8
8
- PATCH = 1
7
+ MINOR = 12
8
+ PATCH = 0
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
11
11
  end
@@ -6,15 +6,18 @@ module Rails
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
8
  argument :attributes, type: :array, default: [], banner: "attribute"
9
- hook_for :test_framework
10
9
  check_class_collision suffix: "Component"
11
10
 
11
+ class_option :require_content, type: :boolean, default: false
12
+
12
13
  def create_component_file
13
14
  template "component.rb", File.join("app/components", class_path, "#{file_name}_component.rb")
14
15
  end
15
16
 
16
- def create_template_file
17
- template "component.html.erb", File.join("app/components", class_path, "#{file_name}_component.html.erb")
17
+ hook_for :test_framework
18
+
19
+ hook_for :template_engine do |instance, template_engine|
20
+ instance.invoke template_engine, [instance.name], require_content: instance.send(:requires_content?)
18
21
  end
19
22
 
20
23
  private
@@ -24,6 +27,7 @@ module Rails
24
27
  end
25
28
 
26
29
  def requires_content?
30
+ return if behavior == :revoke
27
31
  return @requires_content if @asked
28
32
 
29
33
  @asked = true
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/erb"
4
+
5
+ module Erb
6
+ module Generators
7
+ class ComponentGenerator < Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :require_content, type: :boolean, default: false
11
+
12
+ def copy_view_file
13
+ template "component.html.erb", File.join("app/components", class_path, "#{file_name}_component.html.erb")
14
+ end
15
+
16
+ private
17
+
18
+ def requires_content?
19
+ options["require_content"]
20
+ end
21
+
22
+ def file_name
23
+ @_file_name ||= super.sub(/_component\z/i, "")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/erb/component_generator"
4
+
5
+ module Haml
6
+ module Generators
7
+ class ComponentGenerator < Erb::Generators::ComponentGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :require_content, type: :boolean, default: false
11
+
12
+ def copy_view_file
13
+ template "component.html.haml", File.join("app/components", class_path, "#{file_name}_component.html.haml")
14
+ end
15
+
16
+ private
17
+
18
+ def requires_content?
19
+ options["require_content"]
20
+ end
21
+
22
+ def file_name
23
+ @_file_name ||= super.sub(/_component\z/i, "")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ <%- if requires_content? -%>
2
+ <%= "= content" %>
3
+ <%- else -%>
4
+ %div Add <%= class_name %> template here
5
+ <%- end -%>
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/erb/component_generator"
4
+
5
+ module Slim
6
+ module Generators
7
+ class ComponentGenerator < Erb::Generators::ComponentGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :require_content, type: :boolean, default: false
11
+
12
+ def copy_view_file
13
+ template "component.html.slim", File.join("app/components", class_path, "#{file_name}_component.html.slim")
14
+ end
15
+
16
+ private
17
+
18
+ def requires_content?
19
+ options["require_content"]
20
+ end
21
+
22
+ def file_name
23
+ @_file_name ||= super.sub(/_component\z/i, "")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ <%- if requires_content? -%>
2
+ = content
3
+ <%- else -%>
4
+ div Add <%= class_name %> template here
5
+ <%- end -%>
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionview-component
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.1
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-24 00:00:00.000000000 Z
11
+ date: 2020-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capybara
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -143,9 +157,11 @@ files:
143
157
  - README.md
144
158
  - Rakefile
145
159
  - actionview-component.gemspec
160
+ - app/controllers/rails/components_controller.rb
146
161
  - lib/action_view/component.rb
147
162
  - lib/action_view/component/base.rb
148
163
  - lib/action_view/component/conversion.rb
164
+ - lib/action_view/component/engine.rb
149
165
  - lib/action_view/component/preview.rb
150
166
  - lib/action_view/component/previewable.rb
151
167
  - lib/action_view/component/railtie.rb
@@ -156,14 +172,18 @@ files:
156
172
  - lib/action_view/component/version.rb
157
173
  - lib/rails/generators/component/USAGE
158
174
  - lib/rails/generators/component/component_generator.rb
159
- - lib/rails/generators/component/templates/component.html.erb.tt
160
175
  - lib/rails/generators/component/templates/component.rb.tt
176
+ - lib/rails/generators/erb/component_generator.rb
177
+ - lib/rails/generators/erb/templates/component.html.erb.tt
178
+ - lib/rails/generators/haml/component_generator.rb
179
+ - lib/rails/generators/haml/templates/component.html.haml.tt
161
180
  - lib/rails/generators/rspec/component_generator.rb
162
181
  - lib/rails/generators/rspec/templates/component_spec.rb.tt
182
+ - lib/rails/generators/slim/component_generator.rb
183
+ - lib/rails/generators/slim/templates/component.html.slim
163
184
  - lib/rails/generators/test_unit/component_generator.rb
164
185
  - lib/rails/generators/test_unit/templates/component_test.rb.tt
165
186
  - lib/railties/lib/rails.rb
166
- - lib/railties/lib/rails/components_controller.rb
167
187
  - lib/railties/lib/rails/templates/rails/components/index.html.erb
168
188
  - lib/railties/lib/rails/templates/rails/components/preview.html.erb
169
189
  - lib/railties/lib/rails/templates/rails/components/previews.html.erb