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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57dd1d0375f05f3a5849670716be000a0c8074f4ae63b1d422278006b07e5b32
4
- data.tar.gz: 42309cc4774f3405e338cd48a84bc6ee6619ffa3306d98ab8432e8a196d6cb5f
3
+ metadata.gz: c20b9ae371123eb58c62aa3c093baafdf7d5745bf8311ff2fb975c70282e6dfd
4
+ data.tar.gz: caaad9f6e645602efe2faea055ff08dd1f548efe08b503a3048d83eebc824997
5
5
  SHA512:
6
- metadata.gz: fdaad351b7db8bb6aab8e94294c221c50993cdd851fac8d43f64c3628627b5652be8fd3bb93f450467f74401433d4d3fffea560ca9ddc8c78fe645fbe0596056
7
- data.tar.gz: f75561cba609fd4eecf3ac958a0cf9ef9ee9300772c3016749cf3ab5bea5e5ac5f4c2005f47623587e5a80fb9c9d71a18aeaad33fc9d89ba8c1123191dc0345b
6
+ metadata.gz: 52aec02cba11b482df4c4e4f160ad7602dadca664eb75a4fad579fb5802c9f4efb6a7e07f1082ab634e99c9ed8efad82861daf1fdc9c5108cdf63e3f2b0f80d1
7
+ data.tar.gz: 4d5fb555f12740dfa62a64b4c9b095879e4ea182a3930f9a725482525c6d5bbeae08d9914dff847e8521a7107f31b13b919f5b9d5cc69d83cbaf53c08422503c
@@ -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 an series of steps or, better yet, a link to a repo to
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.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/.gitignore CHANGED
@@ -11,6 +11,7 @@
11
11
  /tmp/
12
12
  /test/log/*
13
13
  /test/app/tmp/*
14
+ /test/tmp/*
14
15
 
15
16
  # Used by dotenv library to load environment variables.
16
17
  # .env
data/.rubocop.yml CHANGED
@@ -2,3 +2,7 @@ inherit_gem:
2
2
  rubocop-github:
3
3
  - config/default.yml
4
4
  - config/rails.yml
5
+
6
+ AllCops:
7
+ Exclude:
8
+ - "test/tmp/**/*"
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
- ## Contributing
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionview-component (1.6.2)
4
+ actionview-component (1.10.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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.4`/`2.5`/`2.6` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
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, title: "my title") do %>
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
- ##### Supported `render` syntaxes
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
- Components can be rendered via:
176
+ `app/components/modal_component.rb`:
177
+ ```ruby
178
+ class ModalComponent < ActionView::Component::Base
179
+ validates :user, :header, :body, presence: true
161
180
 
162
- `render(TestComponent, foo: :bar)`
181
+ with_content_areas :header, :body
163
182
 
164
- `render(component: TestComponent, locals: { foo: :bar })`
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
- **Rendering components through models**
197
+ We can render it in a view as:
167
198
 
168
- Passing model instances will cause `render` to look for its respective component class.
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
- The component is instantiated with the rendered model instance.
210
+ Which returns:
171
211
 
172
- Example for a `Post` model:
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
- `render(@post)`
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 PostComponent < ActionView::Component::Base
178
- def initialize(post)
179
- @post = post
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
- The following syntax has been deprecated and will be removed in v2.0.0:
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
- `render(TestComponent.new(foo: :bar))`
248
+ ##### Required argument passed by render argument or by named block
187
249
 
188
- #### Error case
250
+ `app/components/modal_component.rb`:
251
+ ```ruby
252
+ class ModalComponent < ActionView::Component::Base
253
+ validates :header, :body, presence: true
189
254
 
190
- If the component is rendered with a blank title:
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(TestComponent, title: "") do %>
194
- Hello, World!
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
- An error will be raised:
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
- `ActiveModel::ValidationError: Validation failed: Title can't be blank`
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, title: "my title") { "Hello, World!" }.to_html
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, title: "my title") { "Hello, tablets!" }.css("span").to_html
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, title: "Test component default")
435
+ render(TestComponent.new(title: "Test component default"))
248
436
  end
249
437
 
250
438
  def with_long_title
251
- render(TestComponent, title: "This is a really long title to see how the component renders this")
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).
@@ -16,6 +16,7 @@ module ActionView
16
16
  autoload :TestHelpers
17
17
  autoload :TestCase
18
18
  autoload :RenderMonkeyPatch
19
+ autoload :TemplateError
19
20
  end
20
21
  end
21
22
 
@@ -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
- # Require #initialize to be defined so that we can use
119
- # method#source_location to look up the file name
120
- # of the component.
121
- #
122
- # If we were able to only support Ruby 2.7+,
123
- # We could just use Module#const_source_location,
124
- # rendering this unnecessary.
125
- raise NotImplementedError.new("#{self} must implement #initialize.") unless has_initializer?
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
- validate_templates
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
- (Dir["#{source_location.sub(/#{File.extname(source_location)}$/, '')}.*{#{ActionView::Template.template_handler_extensions.join(',')}}"] - [source_location])
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("+")[1]&.to_sym,
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 validate_templates
187
- if templates.empty?
188
- raise NotImplementedError.new("Could not find a template file for #{self}.")
189
- end
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
- if templates.select { |template| template[:variant].nil? }.length > 1
192
- raise StandardError.new("More than one template found for #{self}. There can only be one default template file per component.")
193
- end
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
- variants.each_with_object(Hash.new(0)) { |variant, counts| counts[variant] += 1 }.each do |variant, count|
196
- next unless count > 1
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
- raise StandardError.new("More than one template found for variant '#{variant}' in #{self}. There can only be one template file per variant.")
199
- end
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, *locals, &block)
12
- render_inline(component, *locals, &block)
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 html of the component in its layout
23
- def call(example, layout: nil)
24
- example_html = new.public_send(example)
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
- self.name.sub(%r{Preview$}, "").constantize
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.sub(/Preview$/, "").underscore
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 do |descendant|
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
- "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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Component
5
+ class TemplateError < StandardError
6
+ def initialize(errors)
7
+ super(errors.join(", "))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -8,7 +8,7 @@ module ActionView
8
8
  end
9
9
 
10
10
  def controller
11
- @controller ||= ApplicationController.new.tap { |c| c.request = request }
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
@@ -4,8 +4,8 @@ module ActionView
4
4
  module Component
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 6
8
- PATCH = 2
7
+ MINOR = 10
8
+ PATCH = 0
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
11
11
  end
@@ -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", "#{file_name}_component.rb")
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", "#{file_name}_component.html.erb")
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
@@ -2,5 +2,4 @@
2
2
 
3
3
  module Rails
4
4
  autoload :ComponentsController
5
- autoload :ComponentExamplesController
6
5
  end
@@ -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
- render template: "components/preview", layout: false
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
- <%= raw @preview.call(@example_name) %>
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.6.2
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-01-06 00:00:00.000000000 Z
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 %>