actionview-component 1.6.2 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE +3 -8
- data/.github/PULL_REQUEST_TEMPLATE +2 -4
- data/.github/workflows/ruby_on_rails.yml +2 -2
- data/.gitignore +1 -0
- data/.rubocop.yml +4 -0
- data/CHANGELOG.md +52 -0
- data/CONTRIBUTING.md +5 -14
- data/Gemfile.lock +1 -1
- data/README.md +258 -26
- data/lib/action_view/component.rb +1 -0
- data/lib/action_view/component/base.rb +89 -31
- data/lib/action_view/component/preview.rb +7 -15
- data/lib/action_view/component/railtie.rb +1 -4
- data/lib/action_view/component/render_monkey_patch.rb +11 -6
- data/lib/action_view/component/template_error.rb +11 -0
- data/lib/action_view/component/test_helpers.rb +1 -1
- data/lib/action_view/component/version.rb +2 -2
- data/lib/rails/generators/component/component_generator.rb +2 -2
- data/lib/rails/generators/rspec/component_generator.rb +1 -1
- data/lib/rails/generators/test_unit/component_generator.rb +1 -1
- data/lib/railties/lib/rails.rb +0 -1
- data/lib/railties/lib/rails/components_controller.rb +5 -1
- data/lib/railties/lib/rails/templates/rails/components/preview.html.erb +1 -1
- metadata +3 -4
- data/lib/railties/lib/rails/component_examples_controller.rb +0 -9
- data/lib/railties/lib/rails/templates/rails/examples/show.html.erb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c20b9ae371123eb58c62aa3c093baafdf7d5745bf8311ff2fb975c70282e6dfd
|
4
|
+
data.tar.gz: caaad9f6e645602efe2faea055ff08dd1f548efe08b503a3048d83eebc824997
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52aec02cba11b482df4c4e4f160ad7602dadca664eb75a4fad579fb5802c9f4efb6a7e07f1082ab634e99c9ed8efad82861daf1fdc9c5108cdf63e3f2b0f80d1
|
7
|
+
data.tar.gz: 4d5fb555f12740dfa62a64b4c9b095879e4ea182a3930f9a725482525c6d5bbeae08d9914dff847e8521a7107f31b13b919f5b9d5cc69d83cbaf53c08422503c
|
data/.github/ISSUE_TEMPLATE
CHANGED
@@ -6,17 +6,12 @@
|
|
6
6
|
### Motivation
|
7
7
|
|
8
8
|
<!-- What would you like to do with this feature? Can you provide
|
9
|
-
context or references to similar behavior in other libraries
|
9
|
+
context or references to similar behavior in other libraries? -->
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
<!-- **** Filing a Bug Report? Include these sections. **** -->
|
11
|
+
<!-- **** Filing a Bug Report? Include these sections: **** -->
|
16
12
|
|
17
13
|
### Steps to reproduce
|
18
|
-
<!-- Provide
|
19
|
-
demonstrate the bug you've identified. -->
|
14
|
+
<!-- Provide a series of steps or, better yet, a link to a repo to demonstrate the bug you've identified. -->
|
20
15
|
|
21
16
|
### Expected behavior
|
22
17
|
<!-- Tell us what should happen -->
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<!-- https://github.com/github/actionview-component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
|
1
|
+
<!-- See https://github.com/github/actionview-component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
|
2
2
|
|
3
3
|
### Summary
|
4
4
|
|
@@ -14,6 +14,4 @@ request, mention that information here. This could include
|
|
14
14
|
benchmarks, or other information.
|
15
15
|
|
16
16
|
If you are updating any of the CHANGELOG files or are asked to update the
|
17
|
-
CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file.
|
18
|
-
|
19
|
-
Thanks for contributing to actionview-component! -->
|
17
|
+
CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file. -->
|
@@ -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.
|
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.
|
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/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,55 @@
|
|
1
|
+
# v1.10.0
|
2
|
+
|
3
|
+
* Deprecate all `render` syntaxes except for `render(MyComponent.new(foo: :bar))`
|
4
|
+
|
5
|
+
# v1.9.0
|
6
|
+
|
7
|
+
* Remove initializer requirement for Ruby 2.7+
|
8
|
+
|
9
|
+
*Dylan Clark*
|
10
|
+
|
11
|
+
# v1.8.1
|
12
|
+
|
13
|
+
* Run validation checks before calling `#render?`.
|
14
|
+
|
15
|
+
*Ash Wilson*
|
16
|
+
|
17
|
+
# v1.8.0
|
18
|
+
|
19
|
+
* Remove the unneeded ComponentExamplesController and simplify preview rendering.
|
20
|
+
|
21
|
+
*Jon Palmer*
|
22
|
+
|
23
|
+
* Add `#render?` hook to easily allow components to be no-ops.
|
24
|
+
|
25
|
+
*Kyle Fox*
|
26
|
+
|
27
|
+
* Don't assume ApplicationController exists.
|
28
|
+
|
29
|
+
*Jon Palmer*
|
30
|
+
|
31
|
+
* Allow some additional checks to overrided render?
|
32
|
+
|
33
|
+
*Sergey Malykh*
|
34
|
+
|
35
|
+
* Fix generator placing namespaced components in the root directory.
|
36
|
+
|
37
|
+
*Asger Behncke Jacobsen*
|
38
|
+
|
39
|
+
* Fix cache test.
|
40
|
+
|
41
|
+
*Sergey Malykh*
|
42
|
+
|
43
|
+
# v1.7.0
|
44
|
+
|
45
|
+
* Simplify validation of templates and compilation.
|
46
|
+
|
47
|
+
*Jon Palmer*
|
48
|
+
|
49
|
+
* Add support for multiple content areas.
|
50
|
+
|
51
|
+
*Jon Palmer*
|
52
|
+
|
1
53
|
# v1.6.2
|
2
54
|
|
3
55
|
* Fix Uninitialized Constant error.
|
data/CONTRIBUTING.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# Contributing
|
2
2
|
|
3
3
|
[fork]: https://github.com/github/actionview-component/fork
|
4
4
|
[pr]: https://github.com/github/actionview-component/compare
|
@@ -15,9 +15,11 @@ Please note that this project is released with a [Contributor Code of Conduct][c
|
|
15
15
|
|
16
16
|
0. [Fork][fork] and clone the repository
|
17
17
|
0. Configure and install the dependencies: `bundle`
|
18
|
-
0. Make sure the tests pass on your machine: `rake`
|
18
|
+
0. Make sure the tests pass on your machine: `bundle exec rake`
|
19
19
|
0. Create a new branch: `git checkout -b my-branch-name`
|
20
20
|
0. Make your change, add tests, and make sure the tests still pass
|
21
|
+
0. Add an entry to the top of `CHANGELOG.md` for your changes
|
22
|
+
0. If it's your first time contributing, add yourself to the contributors at the bottom of `README.md`
|
21
23
|
0. Push to your fork and [submit a pull request][pr]
|
22
24
|
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
23
25
|
|
@@ -32,24 +34,13 @@ Here are a few things you can do that will increase the likelihood of your pull
|
|
32
34
|
If you are the current maintainer of this gem:
|
33
35
|
|
34
36
|
1. Create a branch for the release: `git checkout -b release-vxx.xx.xx`
|
35
|
-
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.
|
36
38
|
1. Add version heading/entries to `CHANGELOG.md`.
|
37
39
|
1. Make sure your local dependencies are up to date: `bundle`
|
38
40
|
1. Ensure that tests are green: `bundle exec rake`
|
39
|
-
1. Build a test gem `GEM_VERSION=$(git describe --tags 2>/dev/null | sed 's/-/./g' | sed 's/v//') gem build actionview-component.gemspec`
|
40
|
-
1. Test the test gem:
|
41
|
-
1. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
|
42
|
-
1. Install the new gem locally
|
43
|
-
1. Test behavior locally, branch deploy, whatever needs to happen
|
44
41
|
1. Make a PR to github/actionview-component.
|
45
42
|
1. Build a local gem: `gem build actionview-component.gemspec`
|
46
43
|
1. Merge github/actionview-component PR
|
47
44
|
1. Tag and push: `git tag vx.xx.xx; git push --tags`
|
48
45
|
1. Create a GitHub release with the pushed tag (https://github.com/github/actionview-component/releases/new) and populate it with a list of the commits from `git log --pretty=format:"- %s" --reverse refs/tags/[OLD TAG]...refs/tags/[NEW TAG]`
|
49
46
|
1. Push to rubygems.org -- `gem push actionview-component-VERSION.gem`
|
50
|
-
|
51
|
-
## Resources
|
52
|
-
|
53
|
-
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
54
|
-
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
55
|
-
- [GitHub Help](https://help.github.com)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -15,7 +15,7 @@ As the goal of this gem is to be upstreamed into Rails, it is designed to integr
|
|
15
15
|
|
16
16
|
## Compatibility
|
17
17
|
|
18
|
-
`actionview-component` is tested for compatibility with combinations of Ruby `2.
|
18
|
+
`actionview-component` is tested for compatibility with combinations of Ruby `2.5`/`2.6` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
|
19
19
|
|
20
20
|
## Installation
|
21
21
|
Add this line to your application's Gemfile:
|
@@ -144,7 +144,7 @@ end
|
|
144
144
|
We can render it in a view as:
|
145
145
|
|
146
146
|
```erb
|
147
|
-
<%= render(TestComponent
|
147
|
+
<%= render(TestComponent.new(title: "my title")) do %>
|
148
148
|
Hello, World!
|
149
149
|
<% end %>
|
150
150
|
```
|
@@ -155,49 +155,237 @@ Which returns:
|
|
155
155
|
<span title="my title">Hello, World!</span>
|
156
156
|
```
|
157
157
|
|
158
|
-
|
158
|
+
#### Error case
|
159
|
+
|
160
|
+
If the component is rendered with a blank title:
|
161
|
+
|
162
|
+
```erb
|
163
|
+
<%= render(TestComponent.new(title: "")) do %>
|
164
|
+
Hello, World!
|
165
|
+
<% end %>
|
166
|
+
```
|
167
|
+
|
168
|
+
An error will be raised:
|
169
|
+
|
170
|
+
`ActiveModel::ValidationError: Validation failed: Title can't be blank`
|
171
|
+
|
172
|
+
#### Content Areas
|
173
|
+
|
174
|
+
A component can declare additional content areas to be rendered in the component. For example:
|
159
175
|
|
160
|
-
|
176
|
+
`app/components/modal_component.rb`:
|
177
|
+
```ruby
|
178
|
+
class ModalComponent < ActionView::Component::Base
|
179
|
+
validates :user, :header, :body, presence: true
|
161
180
|
|
162
|
-
|
181
|
+
with_content_areas :header, :body
|
163
182
|
|
164
|
-
|
183
|
+
def initialize(user:)
|
184
|
+
@user = user
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
`app/components/modal_component.html.erb`:
|
190
|
+
```erb
|
191
|
+
<div class="modal">
|
192
|
+
<div class="header"><%= header %></div>
|
193
|
+
<div class="body"><%= body %>"></div>
|
194
|
+
</div>
|
195
|
+
```
|
165
196
|
|
166
|
-
|
197
|
+
We can render it in a view as:
|
167
198
|
|
168
|
-
|
199
|
+
```erb
|
200
|
+
<%= render(ModalComponent.new(user: {name: 'Jane'})) do |component| %>
|
201
|
+
<% component.with(:header) do %>
|
202
|
+
Hello <%= user[:name] %>
|
203
|
+
<% end %>
|
204
|
+
<% component.with(:body) do %>
|
205
|
+
<p>Have a great day.</p>
|
206
|
+
<% end %>
|
207
|
+
<% end %>
|
208
|
+
```
|
169
209
|
|
170
|
-
|
210
|
+
Which returns:
|
171
211
|
|
172
|
-
|
212
|
+
```html
|
213
|
+
<div class="modal">
|
214
|
+
<div class="header">Hello Jane</div>
|
215
|
+
<div class="body"><p>Have a great day.</p></div>
|
216
|
+
</div>
|
217
|
+
```
|
173
218
|
|
174
|
-
|
219
|
+
Content for content areas can be passed as arguments to the render method or as named blocks passed to the `with` method.
|
220
|
+
This allows a few different combinations of ways to render the component:
|
175
221
|
|
222
|
+
##### Required render argument optionally overridden or wrapped by a named block
|
223
|
+
|
224
|
+
`app/components/modal_component.rb`:
|
176
225
|
```ruby
|
177
|
-
class
|
178
|
-
|
179
|
-
|
226
|
+
class ModalComponent < ActionView::Component::Base
|
227
|
+
validates :header, :body, presence: true
|
228
|
+
|
229
|
+
with_content_areas :header, :body
|
230
|
+
|
231
|
+
def initialize(header:)
|
232
|
+
@header = header
|
180
233
|
end
|
181
234
|
end
|
182
235
|
```
|
183
236
|
|
184
|
-
|
237
|
+
```erb
|
238
|
+
<%= render(ModalComponent.new(header: "Hi!")) do |component| %>
|
239
|
+
<% help_enabled? && component.with(:header) do %>
|
240
|
+
<span class="help_icon"><%= component.header %></span>
|
241
|
+
<% end %>
|
242
|
+
<% component.with(:body) do %>
|
243
|
+
<p>Have a great day.</p>
|
244
|
+
<% end %>
|
245
|
+
<% end %>
|
246
|
+
```
|
185
247
|
|
186
|
-
|
248
|
+
##### Required argument passed by render argument or by named block
|
187
249
|
|
188
|
-
|
250
|
+
`app/components/modal_component.rb`:
|
251
|
+
```ruby
|
252
|
+
class ModalComponent < ActionView::Component::Base
|
253
|
+
validates :header, :body, presence: true
|
189
254
|
|
190
|
-
|
255
|
+
with_content_areas :header, :body
|
191
256
|
|
257
|
+
def initialize(header: nil)
|
258
|
+
@header = header
|
259
|
+
end
|
260
|
+
end
|
261
|
+
```
|
262
|
+
|
263
|
+
`app/views/render_arg.html.erb`:
|
192
264
|
```erb
|
193
|
-
<%= render(
|
194
|
-
|
265
|
+
<%= render(ModalComponent.new(header: "Hi!")) do |component| %>
|
266
|
+
<% component.with(:body) do %>
|
267
|
+
<p>Have a great day.</p>
|
268
|
+
<% end %>
|
195
269
|
<% end %>
|
196
270
|
```
|
197
271
|
|
198
|
-
|
272
|
+
`app/views/with_block.html.erb`:
|
273
|
+
```erb
|
274
|
+
<%= render(ModalComponent) do |component| %>
|
275
|
+
<% component.with(:header) do %>
|
276
|
+
<span class="help_icon">Hello</span>
|
277
|
+
<% end %>
|
278
|
+
<% component.with(:body) do %>
|
279
|
+
<p>Have a great day.</p>
|
280
|
+
<% end %>
|
281
|
+
<% end %>
|
282
|
+
```
|
199
283
|
|
200
|
-
|
284
|
+
##### Optional argument passed by render argument, by named block, or neither
|
285
|
+
|
286
|
+
`app/components/modal_component.rb`:
|
287
|
+
```ruby
|
288
|
+
class ModalComponent < ActionView::Component::Base
|
289
|
+
validates :body, presence: true
|
290
|
+
|
291
|
+
with_content_areas :header, :body
|
292
|
+
|
293
|
+
def initialize(header: nil)
|
294
|
+
@header = header
|
295
|
+
end
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
`app/components/modal_component.html.erb`:
|
300
|
+
```erb
|
301
|
+
<div class="modal">
|
302
|
+
<% if header %>
|
303
|
+
<div class="header"><%= header %></div>
|
304
|
+
<% end %>
|
305
|
+
<div class="body"><%= body %>"></div>
|
306
|
+
</div>
|
307
|
+
```
|
308
|
+
|
309
|
+
`app/views/render_arg.html.erb`:
|
310
|
+
```erb
|
311
|
+
<%= render(ModalComponent.new(header: "Hi!")) do |component| %>
|
312
|
+
<% component.with(:body) do %>
|
313
|
+
<p>Have a great day.</p>
|
314
|
+
<% end %>
|
315
|
+
<% end %>
|
316
|
+
```
|
317
|
+
|
318
|
+
`app/views/with_block.html.erb`:
|
319
|
+
```erb
|
320
|
+
<%= render(ModalComponent.new) do |component| %>
|
321
|
+
<% component.with(:header) do %>
|
322
|
+
<span class="help_icon">Hello</span>
|
323
|
+
<% end %>
|
324
|
+
<% component.with(:body) do %>
|
325
|
+
<p>Have a great day.</p>
|
326
|
+
<% end %>
|
327
|
+
<% end %>
|
328
|
+
```
|
329
|
+
|
330
|
+
`app/views/no_header.html.erb`:
|
331
|
+
```erb
|
332
|
+
<%= render(ModalComponent.new) do |component| %>
|
333
|
+
<% component.with(:body) do %>
|
334
|
+
<p>Have a great day.</p>
|
335
|
+
<% end %>
|
336
|
+
<% end %>
|
337
|
+
```
|
338
|
+
|
339
|
+
### Conditional Rendering
|
340
|
+
|
341
|
+
Components can implement a `#render?` method which indicates if they should be rendered, or not at all.
|
342
|
+
|
343
|
+
For example, you might have a component that displays a "Please confirm your email address" banner to users who haven't confirmed their email address. The logic for rendering the banner would need to go in either the component template:
|
344
|
+
|
345
|
+
```
|
346
|
+
<!-- app/components/confirm_email_component.html.erb -->
|
347
|
+
<% if user.requires_confirmation? %>
|
348
|
+
<div class="alert">
|
349
|
+
Please confirm your email address.
|
350
|
+
</div>
|
351
|
+
<% end %>
|
352
|
+
```
|
353
|
+
|
354
|
+
or the view that renders the component:
|
355
|
+
|
356
|
+
```erb
|
357
|
+
<!-- app/views/_banners.html.erb -->
|
358
|
+
<% if current_user.requires_confirmation? %>
|
359
|
+
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
360
|
+
<% end %>
|
361
|
+
```
|
362
|
+
|
363
|
+
The `#render?` hook allows you to move this logic into the Ruby class, leaving your views more readable and declarative in style:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
# app/components/confirm_email_component.rb
|
367
|
+
class ConfirmEmailComponent < ApplicationComponent
|
368
|
+
def initialize(user:)
|
369
|
+
@user = user
|
370
|
+
end
|
371
|
+
|
372
|
+
def render?
|
373
|
+
@user.requires_confirmation?
|
374
|
+
end
|
375
|
+
end
|
376
|
+
```
|
377
|
+
|
378
|
+
```
|
379
|
+
<!-- app/components/confirm_email_component.html.erb -->
|
380
|
+
<div class="banner">
|
381
|
+
Please confirm your email address.
|
382
|
+
</div>
|
383
|
+
```
|
384
|
+
|
385
|
+
```erb
|
386
|
+
<!-- app/views/_banners.html.erb -->
|
387
|
+
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
388
|
+
```
|
201
389
|
|
202
390
|
### Testing
|
203
391
|
|
@@ -210,7 +398,7 @@ class MyComponentTest < ActionView::Component::TestCase
|
|
210
398
|
test "render component" do
|
211
399
|
assert_equal(
|
212
400
|
%(<span title="my title">Hello, World!</span>),
|
213
|
-
render_inline(TestComponent
|
401
|
+
render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }.to_html
|
214
402
|
)
|
215
403
|
end
|
216
404
|
end
|
@@ -227,7 +415,7 @@ test "render component for tablet" do
|
|
227
415
|
with_variant :tablet do
|
228
416
|
assert_equal(
|
229
417
|
%(<span title="my title">Hello, tablets!</span>),
|
230
|
-
render_inline(TestComponent
|
418
|
+
render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }.css("span").to_html
|
231
419
|
)
|
232
420
|
end
|
233
421
|
end
|
@@ -244,11 +432,11 @@ You can define as many examples as you want:
|
|
244
432
|
|
245
433
|
class TestComponentPreview < ActionView::Component::Preview
|
246
434
|
def with_default_title
|
247
|
-
render(TestComponent
|
435
|
+
render(TestComponent.new(title: "Test component default"))
|
248
436
|
end
|
249
437
|
|
250
438
|
def with_long_title
|
251
|
-
render(TestComponent
|
439
|
+
render(TestComponent.new(title: "This is a really long title to see how the component renders this"))
|
252
440
|
end
|
253
441
|
end
|
254
442
|
```
|
@@ -276,6 +464,16 @@ For example, if you want to use `lib/component_previews`, set the following in `
|
|
276
464
|
config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
|
277
465
|
```
|
278
466
|
|
467
|
+
#### Configuring TestController
|
468
|
+
|
469
|
+
By default components tests and previews expect your Rails project to contain an `ApplicationController` class from which Controller classes inherit.
|
470
|
+
This can be configured using the `test_controller` option.
|
471
|
+
For example, if your controllers inherit from `BaseController`, set the following in `config/application.rb`:
|
472
|
+
|
473
|
+
```ruby
|
474
|
+
config.action_view_component.test_controller = "BaseController"
|
475
|
+
```
|
476
|
+
|
279
477
|
### Setting up RSpec
|
280
478
|
|
281
479
|
If you're using RSpec, you can configure component specs to have access to test helpers. Add the following to
|
@@ -300,6 +498,11 @@ To use component previews, set the following in `config/application.rb`:
|
|
300
498
|
config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
|
301
499
|
```
|
302
500
|
|
501
|
+
### Initializer requirement
|
502
|
+
|
503
|
+
In Ruby 2.6.x and below, ActionView::Component requires the presence of an `initialize` method in each component.
|
504
|
+
However, `initialize` is no longer required for projects using 2.7.x and above.
|
505
|
+
|
303
506
|
## Frequently Asked Questions
|
304
507
|
|
305
508
|
### Can I use other templating languages besides ERB?
|
@@ -331,6 +534,35 @@ Inline templates have been removed (for now) due to concerns raised by [@soutaro
|
|
331
534
|
|
332
535
|
Bug reports and pull requests are welcome on GitHub at https://github.com/github/actionview-component. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
|
333
536
|
|
537
|
+
## Contributors
|
538
|
+
|
539
|
+
`actionview-component` is built by:
|
540
|
+
|
541
|
+
|<img src="https://avatars.githubusercontent.com/joelhawksley?s=256" alt="joelhawksley" width="128" />|<img src="https://avatars.githubusercontent.com/tenderlove?s=256" alt="tenderlove" width="128" />|<img src="https://avatars.githubusercontent.com/jonspalmer?s=256" alt="jonspalmer" width="128" />|<img src="https://avatars.githubusercontent.com/juanmanuelramallo?s=256" alt="juanmanuelramallo" width="128" />|<img src="https://avatars.githubusercontent.com/vinistock?s=256" alt="vinistock" width="128" />|
|
542
|
+
|:---:|:---:|:---:|:---:|:---:|
|
543
|
+
|@joelhawksley|@tenderlove|@jonspalmer|@juanmanuelramallo|@vinistock|
|
544
|
+
|Denver|Seattle|Boston||Toronto|
|
545
|
+
|
546
|
+
|<img src="https://avatars.githubusercontent.com/metade?s=256" alt="metade" width="128" />|<img src="https://avatars.githubusercontent.com/asgerb?s=256" alt="asgerb" width="128" />|<img src="https://avatars.githubusercontent.com/xronos-i-am?s=256" alt="xronos-i-am" width="128" />|<img src="https://avatars.githubusercontent.com/dylnclrk?s=256" alt="dylnclrk" width="128" />|<img src="https://avatars.githubusercontent.com/kaspermeyer?s=256" alt="kaspermeyer" width="128" />|
|
547
|
+
|:---:|:---:|:---:|:---:|:---:|
|
548
|
+
|@metade|@asgerb|@xronos-i-am|@dylnclrk|@kaspermeyer|
|
549
|
+
|London|Copenhagen|Russia, Kirov|Berkeley, CA|Denmark|
|
550
|
+
|
551
|
+
|<img src="https://avatars.githubusercontent.com/rdavid1099?s=256" alt="rdavid1099" width="128" />|<img src="https://avatars.githubusercontent.com/kylefox?s=256" alt="kylefox" width="128" />|<img src="https://avatars.githubusercontent.com/traels?s=256" alt="traels" width="128" />|<img src="https://avatars.githubusercontent.com/rainerborene?s=256" alt="rainerborene" width="128" />|<img src="https://avatars.githubusercontent.com/jcoyne?s=256" alt="jcoyne" width="128" />|
|
552
|
+
|:---:|:---:|:---:|:---:|:---:|
|
553
|
+
|@rdavid1099|@kylefox|@traels|@rainerborene|@jcoyne|
|
554
|
+
|Los Angeles|Edmonton|Odense, Denmark|Brazil|Minneapolis|
|
555
|
+
|
556
|
+
|<img src="https://avatars.githubusercontent.com/elia?s=256" alt="elia" width="128" />|<img src="https://avatars.githubusercontent.com/cesariouy?s=256" alt="cesariouy" width="128" />|<img src="https://avatars.githubusercontent.com/spdawson?s=256" alt="spdawson" width="128" />|<img src="https://avatars.githubusercontent.com/rmacklin?s=256" alt="rmacklin" width="128" />|<img src="https://avatars.githubusercontent.com/michaelem?s=256" alt="michaelem" width="128" />|
|
557
|
+
|:---:|:---:|:---:|:---:|:---:|
|
558
|
+
|@elia|@cesariouy|@spdawson|@rmacklin|@michaelem|
|
559
|
+
|Milan||United Kingdom||Berlin|
|
560
|
+
|
561
|
+
|<img src="https://avatars.githubusercontent.com/mellowfish?s=256" alt="mellowfish" width="128" />|<img src="https://avatars.githubusercontent.com/horacio?s=256" alt="horacio" width="128" />|<img src="https://avatars.githubusercontent.com/dukex?s=256" alt="dukex" width="128" />|<img src="https://avatars.githubusercontent.com/dark-panda?s=256" alt="dark-panda" width="128" />|<img src="https://avatars.githubusercontent.com/smashwilson?s=256" alt="smashwilson" width="128" />|
|
562
|
+
|:---:|:---:|:---:|:---:|:---:|
|
563
|
+
|@mellowfish|@horacio|@dukex|@dark-panda|@smashwilson|
|
564
|
+
|Spring Hill, TN|Buenos Aires|São Paulo||Gambrills, MD|
|
565
|
+
|
334
566
|
## License
|
335
567
|
|
336
568
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -11,6 +11,9 @@ module ActionView
|
|
11
11
|
|
12
12
|
delegate :form_authenticity_token, :protect_against_forgery?, to: :helpers
|
13
13
|
|
14
|
+
class_attribute :content_areas, default: []
|
15
|
+
self.content_areas = [] # default doesn't work until Rails 5.2
|
16
|
+
|
14
17
|
# Entrypoint for rendering components. Called by ActionView::Base#render.
|
15
18
|
#
|
16
19
|
# view_context: ActionView context from calling view
|
@@ -37,24 +40,32 @@ module ActionView
|
|
37
40
|
# <span title="greeting">Hello, world!</span>
|
38
41
|
#
|
39
42
|
def render_in(view_context, *args, &block)
|
40
|
-
self.class.compile
|
43
|
+
self.class.compile!
|
41
44
|
@view_context = view_context
|
42
45
|
@view_renderer ||= view_context.view_renderer
|
43
46
|
@lookup_context ||= view_context.lookup_context
|
44
47
|
@view_flow ||= view_context.view_flow
|
45
48
|
@virtual_path ||= virtual_path
|
46
49
|
@variant = @lookup_context.variants.first
|
50
|
+
|
47
51
|
old_current_template = @current_template
|
48
52
|
@current_template = self
|
49
53
|
|
50
|
-
@content = view_context.capture(&block) if block_given?
|
54
|
+
@content = view_context.capture(self, &block) if block_given?
|
55
|
+
|
51
56
|
validate!
|
52
57
|
|
58
|
+
return "" unless render?
|
59
|
+
|
53
60
|
send(self.class.call_method_name(@variant))
|
54
61
|
ensure
|
55
62
|
@current_template = old_current_template
|
56
63
|
end
|
57
64
|
|
65
|
+
def render?
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
58
69
|
def initialize(*); end
|
59
70
|
|
60
71
|
def render(options = {}, args = {}, &block)
|
@@ -87,6 +98,19 @@ module ActionView
|
|
87
98
|
@variant
|
88
99
|
end
|
89
100
|
|
101
|
+
def with(area, content = nil, &block)
|
102
|
+
unless content_areas.include?(area)
|
103
|
+
raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
|
104
|
+
end
|
105
|
+
|
106
|
+
if block_given?
|
107
|
+
content = view_context.capture(&block)
|
108
|
+
end
|
109
|
+
|
110
|
+
instance_variable_set("@#{area}".to_sym, content)
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
90
114
|
private
|
91
115
|
|
92
116
|
def request
|
@@ -95,6 +119,12 @@ module ActionView
|
|
95
119
|
|
96
120
|
attr_reader :content, :view_context
|
97
121
|
|
122
|
+
# The controller used for testing components.
|
123
|
+
# Defaults to ApplicationController. This should be set early
|
124
|
+
# in the initialization process and should be set to a string.
|
125
|
+
mattr_accessor :test_controller
|
126
|
+
@@test_controller = "ApplicationController"
|
127
|
+
|
98
128
|
class << self
|
99
129
|
def inherited(child)
|
100
130
|
child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
|
@@ -110,34 +140,36 @@ module ActionView
|
|
110
140
|
end
|
111
141
|
end
|
112
142
|
|
113
|
-
def has_initializer?
|
114
|
-
self.instance_method(:initialize).owner == self
|
115
|
-
end
|
116
|
-
|
117
143
|
def source_location
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
instance_method(:initialize).source_location[0]
|
144
|
+
@source_location ||=
|
145
|
+
if const_source_location_supported?
|
146
|
+
const_source_location(self.name)[0]
|
147
|
+
else
|
148
|
+
# Require `#initialize` to be defined so that we can use `method#source_location`
|
149
|
+
# to look up the filename of the component.
|
150
|
+
initialize_method = instance_method(:initialize)
|
151
|
+
initialize_method.source_location[0] if initialize_method.owner == self
|
152
|
+
end
|
128
153
|
end
|
129
154
|
|
130
155
|
def compiled?
|
131
156
|
@compiled && ActionView::Base.cache_template_loading
|
132
157
|
end
|
133
158
|
|
159
|
+
def compile!
|
160
|
+
compile(validate: true)
|
161
|
+
end
|
162
|
+
|
134
163
|
# Compile templates to instance methods, assuming they haven't been compiled already.
|
135
164
|
# We could in theory do this on app boot, at least in production environments.
|
136
165
|
# Right now this just compiles the first time the component is rendered.
|
137
|
-
def compile
|
166
|
+
def compile(validate: false)
|
138
167
|
return if compiled?
|
139
168
|
|
140
|
-
|
169
|
+
if template_errors.present?
|
170
|
+
raise ActionView::Component::TemplateError.new(template_errors) if validate
|
171
|
+
return false
|
172
|
+
end
|
141
173
|
|
142
174
|
templates.each do |template|
|
143
175
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
@@ -164,10 +196,23 @@ module ActionView
|
|
164
196
|
source_location
|
165
197
|
end
|
166
198
|
|
199
|
+
def with_content_areas(*areas)
|
200
|
+
if areas.include?(:content)
|
201
|
+
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
202
|
+
end
|
203
|
+
attr_reader *areas
|
204
|
+
self.content_areas = areas
|
205
|
+
end
|
206
|
+
|
167
207
|
private
|
168
208
|
|
209
|
+
def const_source_location_supported?
|
210
|
+
respond_to? :const_source_location # introduced in Ruby 2.7
|
211
|
+
end
|
212
|
+
|
169
213
|
def matching_views_in_source_location
|
170
|
-
|
214
|
+
return [] unless source_location
|
215
|
+
(Dir["#{source_location.chomp(File.extname(source_location))}.*{#{ActionView::Template.template_handler_extensions.join(',')}}"] - [source_location])
|
171
216
|
end
|
172
217
|
|
173
218
|
def templates
|
@@ -177,26 +222,39 @@ module ActionView
|
|
177
222
|
|
178
223
|
memo << {
|
179
224
|
path: path,
|
180
|
-
variant: pieces.second.split("+")
|
225
|
+
variant: pieces.second.split("+").second&.to_sym,
|
181
226
|
handler: pieces.last
|
182
227
|
}
|
183
228
|
end
|
184
229
|
end
|
185
230
|
|
186
|
-
def
|
187
|
-
|
188
|
-
|
189
|
-
|
231
|
+
def template_errors
|
232
|
+
@template_errors ||=
|
233
|
+
begin
|
234
|
+
errors = []
|
235
|
+
if source_location.nil? && !const_source_location_supported?
|
236
|
+
# Require `#initialize` to be defined so that we can use `method#source_location`
|
237
|
+
# to look up the filename of the component.
|
238
|
+
errors << "#{self} must implement #initialize."
|
239
|
+
end
|
190
240
|
|
191
|
-
|
192
|
-
|
193
|
-
|
241
|
+
errors << "Could not find a template file for #{self}." if templates.empty?
|
242
|
+
|
243
|
+
if templates.count { |template| template[:variant].nil? } > 1
|
244
|
+
errors << "More than one template found for #{self}. There can only be one default template file per component."
|
245
|
+
end
|
194
246
|
|
195
|
-
|
196
|
-
|
247
|
+
invalid_variants = templates
|
248
|
+
.group_by { |template| template[:variant] }
|
249
|
+
.map { |variant, grouped| variant if grouped.length > 1 }
|
250
|
+
.compact
|
251
|
+
.sort
|
197
252
|
|
198
|
-
|
199
|
-
|
253
|
+
unless invalid_variants.empty?
|
254
|
+
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."
|
255
|
+
end
|
256
|
+
errors
|
257
|
+
end
|
200
258
|
end
|
201
259
|
|
202
260
|
def compiled_template(file_path)
|
@@ -6,10 +6,9 @@ module ActionView
|
|
6
6
|
module Component # :nodoc:
|
7
7
|
class Preview
|
8
8
|
extend ActiveSupport::DescendantsTracker
|
9
|
-
include ActionView::Component::TestHelpers
|
10
9
|
|
11
|
-
def render(component,
|
12
|
-
|
10
|
+
def render(component, **args, &block)
|
11
|
+
{ component: component, args: args, block: block }
|
13
12
|
end
|
14
13
|
|
15
14
|
class << self
|
@@ -19,21 +18,14 @@ module ActionView
|
|
19
18
|
descendants
|
20
19
|
end
|
21
20
|
|
22
|
-
# Returns the
|
23
|
-
def
|
24
|
-
|
25
|
-
if layout.nil?
|
26
|
-
layout = @layout.nil? ? "layouts/application" : @layout
|
27
|
-
end
|
28
|
-
|
29
|
-
Rails::ComponentExamplesController.render(template: "examples/show",
|
30
|
-
layout: layout,
|
31
|
-
assigns: { example: example_html })
|
21
|
+
# Returns the arguments for rendering of the component in its layout
|
22
|
+
def render_args(example)
|
23
|
+
new.public_send(example).merge(layout: @layout)
|
32
24
|
end
|
33
25
|
|
34
26
|
# Returns the component object class associated to the preview.
|
35
27
|
def component
|
36
|
-
|
28
|
+
name.chomp("Preview").constantize
|
37
29
|
end
|
38
30
|
|
39
31
|
# Returns all of the available examples for the component preview.
|
@@ -58,7 +50,7 @@ module ActionView
|
|
58
50
|
|
59
51
|
# Returns the underscored name of the component preview without the suffix.
|
60
52
|
def preview_name
|
61
|
-
name.
|
53
|
+
name.chomp("Preview").underscore
|
62
54
|
end
|
63
55
|
|
64
56
|
# Setter for layout name.
|
@@ -24,7 +24,6 @@ module ActionView
|
|
24
24
|
|
25
25
|
initializer "action_view_component.set_autoload_paths" do |app|
|
26
26
|
require "railties/lib/rails/components_controller"
|
27
|
-
require "railties/lib/rails/component_examples_controller"
|
28
27
|
|
29
28
|
options = app.config.action_view_component
|
30
29
|
|
@@ -35,9 +34,7 @@ module ActionView
|
|
35
34
|
|
36
35
|
initializer "action_view_component.eager_load_actions" do
|
37
36
|
ActiveSupport.on_load(:after_initialize) do
|
38
|
-
ActionView::Component::Base.descendants.each
|
39
|
-
descendant.compile if descendant.has_initializer? && config.eager_load
|
40
|
-
end
|
37
|
+
ActionView::Component::Base.descendants.each(&:compile)
|
41
38
|
end
|
42
39
|
end
|
43
40
|
|
@@ -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
|
-
"
|
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
|
@@ -8,7 +8,7 @@ module ActionView
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def controller
|
11
|
-
@controller ||=
|
11
|
+
@controller ||= Base.test_controller.constantize.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
|
12
12
|
end
|
13
13
|
|
14
14
|
def request
|
@@ -10,11 +10,11 @@ module Rails
|
|
10
10
|
check_class_collision suffix: "Component"
|
11
11
|
|
12
12
|
def create_component_file
|
13
|
-
template "component.rb", File.join("app/components",
|
13
|
+
template "component.rb", File.join("app/components", class_path, "#{file_name}_component.rb")
|
14
14
|
end
|
15
15
|
|
16
16
|
def create_template_file
|
17
|
-
template "component.html.erb", File.join("app/components",
|
17
|
+
template "component.html.erb", File.join("app/components", class_path, "#{file_name}_component.html.erb")
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
@@ -6,7 +6,7 @@ module Rspec
|
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
|
8
8
|
def create_test_file
|
9
|
-
template "component_spec.rb", File.join("spec/components", "#{file_name}_component_spec.rb")
|
9
|
+
template "component_spec.rb", File.join("spec/components", class_path, "#{file_name}_component_spec.rb")
|
10
10
|
end
|
11
11
|
|
12
12
|
private
|
@@ -7,7 +7,7 @@ module TestUnit
|
|
7
7
|
check_class_collision suffix: "ComponentTest"
|
8
8
|
|
9
9
|
def create_test_file
|
10
|
-
template "component_test.rb", File.join("test/components", "#{file_name}_component_test.rb")
|
10
|
+
template "component_test.rb", File.join("test/components", class_path, "#{file_name}_component_test.rb")
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
data/lib/railties/lib/rails.rb
CHANGED
@@ -4,6 +4,7 @@ require "rails/application_controller"
|
|
4
4
|
|
5
5
|
class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
|
6
6
|
prepend_view_path File.expand_path("templates/rails", __dir__)
|
7
|
+
prepend_view_path "#{Rails.root}/app/views/" if defined?(Rails.root)
|
7
8
|
|
8
9
|
around_action :set_locale, only: :previews
|
9
10
|
before_action :find_preview, only: :previews
|
@@ -25,7 +26,10 @@ class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
|
|
25
26
|
render template: "components/previews"
|
26
27
|
else
|
27
28
|
@example_name = File.basename(params[:path])
|
28
|
-
|
29
|
+
@render_args = @preview.render_args(@example_name)
|
30
|
+
layout = @render_args[:layout]
|
31
|
+
opts = layout.nil? ? {} : { layout: layout }
|
32
|
+
render template: "components/preview", **opts
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
@@ -1 +1 @@
|
|
1
|
-
<%=
|
1
|
+
<%= render(@render_args[:component], **@render_args[:args], &@render_args[:block])%>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionview-component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.10.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-
|
11
|
+
date: 2020-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -150,6 +150,7 @@ files:
|
|
150
150
|
- lib/action_view/component/previewable.rb
|
151
151
|
- lib/action_view/component/railtie.rb
|
152
152
|
- lib/action_view/component/render_monkey_patch.rb
|
153
|
+
- lib/action_view/component/template_error.rb
|
153
154
|
- lib/action_view/component/test_case.rb
|
154
155
|
- lib/action_view/component/test_helpers.rb
|
155
156
|
- lib/action_view/component/version.rb
|
@@ -162,12 +163,10 @@ files:
|
|
162
163
|
- lib/rails/generators/test_unit/component_generator.rb
|
163
164
|
- lib/rails/generators/test_unit/templates/component_test.rb.tt
|
164
165
|
- lib/railties/lib/rails.rb
|
165
|
-
- lib/railties/lib/rails/component_examples_controller.rb
|
166
166
|
- lib/railties/lib/rails/components_controller.rb
|
167
167
|
- lib/railties/lib/rails/templates/rails/components/index.html.erb
|
168
168
|
- lib/railties/lib/rails/templates/rails/components/preview.html.erb
|
169
169
|
- lib/railties/lib/rails/templates/rails/components/previews.html.erb
|
170
|
-
- lib/railties/lib/rails/templates/rails/examples/show.html.erb
|
171
170
|
- script/bootstrap
|
172
171
|
- script/console
|
173
172
|
- script/install
|
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "rails/application_controller"
|
4
|
-
load "config/application.rb" unless Rails.root
|
5
|
-
|
6
|
-
class Rails::ComponentExamplesController < ActionController::Base # :nodoc:
|
7
|
-
prepend_view_path File.expand_path("templates/rails", __dir__)
|
8
|
-
append_view_path Rails.root.join("app/views")
|
9
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
<%= raw @example %>
|