actionview-component 1.6.1 → 1.9.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: 642717e715c2f33794d3647e8756af86226bf90a9ee40c28496109288fa63201
4
- data.tar.gz: a2144dc99b735c2e0cd27b86e5f5b95f60031facc7b47af0e35f3ab0ff2562ba
3
+ metadata.gz: 67d62ba2c307963de0523a13f554d1e78b9e76990ace76c36b7d982ec83ef2cb
4
+ data.tar.gz: 66682a57cd5504b054c7c21227bbfb6cdc2c14f0ac151f2d6bb136a1f2e7a6cb
5
5
  SHA512:
6
- metadata.gz: 34973a14a10b96e2905b2fadfa93828de4014c12f97239a367527aabea8218d13281cdc8dbad7808f9575afafb66172202612a65e51506db4f2e8be4a6055354
7
- data.tar.gz: 893531f33aaf0982973df46acc708e42de316464a3860bda12a693e810283d12d7d5a3a5a743149fa406645ff7242f9ef04509f18a0c855379b867f6ef4f9117
6
+ metadata.gz: 0f5b33cb1dfff517ae58afb9167a5f0370f40902eef1ecfef1d6ee0e9f589a255826b92cbccf3bec5fd0a91d487601ec273f6238ddc029a3daade14aa5cd8a5a
7
+ data.tar.gz: f2f58364fba2269e40bf5bd1040872f826365f67e09679996c79999a0755a1ffdbe06f89bb661f80e7a23a2b4791dff6618b28da9a9cf062b1ca41f65ee69dfa
@@ -0,0 +1,25 @@
1
+ <!-- **** Filing a Feature Request? Include these sections. **** -->
2
+
3
+ ### Feature request
4
+ <!-- Provide a summary of the behavior. -->
5
+
6
+ ### Motivation
7
+
8
+ <!-- What would you like to do with this feature? Can you provide
9
+ context or references to similar behavior in other libraries? -->
10
+
11
+ <!-- **** Filing a Bug Report? Include these sections: **** -->
12
+
13
+ ### Steps to reproduce
14
+ <!-- Provide a series of steps or, better yet, a link to a repo to demonstrate the bug you've identified. -->
15
+
16
+ ### Expected behavior
17
+ <!-- Tell us what should happen -->
18
+
19
+ ### Actual behavior
20
+ <!-- Tell us what happens instead -->
21
+
22
+ ### System configuration
23
+ **Rails version**:
24
+
25
+ **Ruby version**:
@@ -0,0 +1,17 @@
1
+ <!-- See https://github.com/github/actionview-component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
2
+
3
+ ### Summary
4
+
5
+ <!-- Provide a general description of the code changes in your pull
6
+ request... were there any bugs you had fixed? If so, mention them. If
7
+ these bugs have open GitHub issues, be sure to tag them here as well,
8
+ to keep the conversation linked together. -->
9
+
10
+ ### Other Information
11
+
12
+ <!-- If there's anything else that's important and relevant to your pull
13
+ request, mention that information here. This could include
14
+ benchmarks, or other information.
15
+
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. -->
@@ -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
@@ -10,6 +10,8 @@
10
10
  /test/version_tmp/
11
11
  /tmp/
12
12
  /test/log/*
13
+ /test/app/tmp/*
14
+ /test/tmp/*
13
15
 
14
16
  # Used by dotenv library to load environment variables.
15
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,65 @@
1
+ # v1.9.0
2
+
3
+ * Remove initializer requirement for Ruby 2.7+
4
+
5
+ *Dylan Clark*
6
+
7
+ # v1.8.1
8
+
9
+ * Run validation checks before calling `#render?`.
10
+
11
+ *Ash Wilson*
12
+
13
+ # v1.8.0
14
+
15
+ * Remove the unneeded ComponentExamplesController and simplify preview rendering.
16
+
17
+ *Jon Palmer*
18
+
19
+ * Add `#render?` hook to easily allow components to be no-ops.
20
+
21
+ *Kyle Fox*
22
+
23
+ * Don't assume ApplicationController exists.
24
+
25
+ *Jon Palmer*
26
+
27
+ * Allow some additional checks to overrided render?
28
+
29
+ *Sergey Malykh*
30
+
31
+ * Fix generator placing namespaced components in the root directory.
32
+
33
+ *Asger Behncke Jacobsen*
34
+
35
+ * Fix cache test.
36
+
37
+ *Sergey Malykh*
38
+
39
+ # v1.7.0
40
+
41
+ * Simplify validation of templates and compilation.
42
+
43
+ *Jon Palmer*
44
+
45
+ * Add support for multiple content areas.
46
+
47
+ *Jon Palmer*
48
+
49
+ # v1.6.2
50
+
51
+ * Fix Uninitialized Constant error.
52
+
53
+ *Jon Palmer*
54
+
55
+ * Add basic github issue and PR templates.
56
+
57
+ *Dylan Clark*
58
+
59
+ * Update readme phrasing around previews.
60
+
61
+ *Justin Coyne*
62
+
1
63
  # v1.6.1
2
64
 
3
65
  * Allow Previews to have no layout.
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.1)
4
+ actionview-component (1.9.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:
@@ -199,6 +199,224 @@ An error will be raised:
199
199
 
200
200
  `ActiveModel::ValidationError: Validation failed: Title can't be blank`
201
201
 
202
+ #### Content Areas
203
+
204
+ A component can declare additional content areas to be rendered in the component. For example:
205
+
206
+ `app/components/modal_component.rb`:
207
+ ```ruby
208
+ class ModalComponent < ActionView::Component::Base
209
+ validates :user, :header, :body, presence: true
210
+
211
+ with_content_areas :header, :body
212
+
213
+ def initialize(user:)
214
+ @user = user
215
+ end
216
+ end
217
+ ```
218
+
219
+ `app/components/modal_component.html.erb`:
220
+ ```erb
221
+ <div class="modal">
222
+ <div class="header"><%= header %></div>
223
+ <div class="body"><%= body %>"></div>
224
+ </div>
225
+ ```
226
+
227
+ We can render it in a view as:
228
+
229
+ ```erb
230
+ <%= render(ModalComponent, user: {name: 'Jane'}) do |component| %>
231
+ <% component.with(:header) do %>
232
+ Hello <%= user[:name] %>
233
+ <% end %>
234
+ <% component.with(:body) do %>
235
+ <p>Have a great day.</p>
236
+ <% end %>
237
+ <% end %>
238
+ ```
239
+
240
+ Which returns:
241
+
242
+ ```html
243
+ <div class="modal">
244
+ <div class="header">Hello Jane</div>
245
+ <div class="body"><p>Have a great day.</p></div>
246
+ </div>
247
+ ```
248
+
249
+ Content for content areas can be passed as arguments to the render method or as named blocks passed to the `with` method.
250
+ This allows a few different combinations of ways to render the component:
251
+
252
+ ##### Required render argument optionally overridden or wrapped by a named block
253
+
254
+ `app/components/modal_component.rb`:
255
+ ```ruby
256
+ class ModalComponent < ActionView::Component::Base
257
+ validates :header, :body, presence: true
258
+
259
+ with_content_areas :header, :body
260
+
261
+ def initialize(header:)
262
+ @header = header
263
+ end
264
+ end
265
+ ```
266
+
267
+ ```erb
268
+ <%= render(ModalComponent, header: "Hi!") do |component| %>
269
+ <% help_enabled? && component.with(:header) do %>
270
+ <span class="help_icon"><%= component.header %></span>
271
+ <% end %>
272
+ <% component.with(:body) do %>
273
+ <p>Have a great day.</p>
274
+ <% end %>
275
+ <% end %>
276
+ ```
277
+
278
+ ##### Required argument passed by render argument or by named block
279
+
280
+ `app/components/modal_component.rb`:
281
+ ```ruby
282
+ class ModalComponent < ActionView::Component::Base
283
+ validates :header, :body, presence: true
284
+
285
+ with_content_areas :header, :body
286
+
287
+ def initialize(header: nil)
288
+ @header = header
289
+ end
290
+ end
291
+ ```
292
+
293
+ `app/views/render_arg.html.erb`:
294
+ ```erb
295
+ <%= render(ModalComponent, header: "Hi!") do |component| %>
296
+ <% component.with(:body) do %>
297
+ <p>Have a great day.</p>
298
+ <% end %>
299
+ <% end %>
300
+ ```
301
+
302
+ `app/views/with_block.html.erb`:
303
+ ```erb
304
+ <%= render(ModalComponent) do |component| %>
305
+ <% component.with(:header) do %>
306
+ <span class="help_icon">Hello</span>
307
+ <% end %>
308
+ <% component.with(:body) do %>
309
+ <p>Have a great day.</p>
310
+ <% end %>
311
+ <% end %>
312
+ ```
313
+
314
+ ##### Optional argument passed by render argument, by named block, or neither
315
+
316
+ `app/components/modal_component.rb`:
317
+ ```ruby
318
+ class ModalComponent < ActionView::Component::Base
319
+ validates :body, presence: true
320
+
321
+ with_content_areas :header, :body
322
+
323
+ def initialize(header: nil)
324
+ @header = header
325
+ end
326
+ end
327
+ ```
328
+
329
+ `app/components/modal_component.html.erb`:
330
+ ```erb
331
+ <div class="modal">
332
+ <% if header %>
333
+ <div class="header"><%= header %></div>
334
+ <% end %>
335
+ <div class="body"><%= body %>"></div>
336
+ </div>
337
+ ```
338
+
339
+ `app/views/render_arg.html.erb`:
340
+ ```erb
341
+ <%= render(ModalComponent, header: "Hi!") do |component| %>
342
+ <% component.with(:body) do %>
343
+ <p>Have a great day.</p>
344
+ <% end %>
345
+ <% end %>
346
+ ```
347
+
348
+ `app/views/with_block.html.erb`:
349
+ ```erb
350
+ <%= render(ModalComponent) do |component| %>
351
+ <% component.with(:header) do %>
352
+ <span class="help_icon">Hello</span>
353
+ <% end %>
354
+ <% component.with(:body) do %>
355
+ <p>Have a great day.</p>
356
+ <% end %>
357
+ <% end %>
358
+ ```
359
+
360
+ `app/views/no_header.html.erb`:
361
+ ```erb
362
+ <%= render(ModalComponent) do |component| %>
363
+ <% component.with(:body) do %>
364
+ <p>Have a great day.</p>
365
+ <% end %>
366
+ <% end %>
367
+ ```
368
+
369
+ ### Conditional Rendering
370
+
371
+ Components can implement a `#render?` method which indicates if they should be rendered, or not at all.
372
+
373
+ 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:
374
+
375
+ ```
376
+ <!-- app/components/confirm_email_component.html.erb -->
377
+ <% if user.requires_confirmation? %>
378
+ <div class="alert">
379
+ Please confirm your email address.
380
+ </div>
381
+ <% end %>
382
+ ```
383
+
384
+ or the view that renders the component:
385
+
386
+ ```erb
387
+ <!-- app/views/_banners.html.erb -->
388
+ <% if current_user.requires_confirmation? %>
389
+ <%= render(ConfirmEmailComponent, user: current_user) %>
390
+ <% end %>
391
+ ```
392
+
393
+ The `#render?` hook allows you to move this logic into the Ruby class, leaving your views more readable and declarative in style:
394
+
395
+ ```ruby
396
+ # app/components/confirm_email_component.rb
397
+ class ConfirmEmailComponent < ApplicationComponent
398
+ def initialize(user:)
399
+ @user = user
400
+ end
401
+
402
+ def render?
403
+ @user.requires_confirmation?
404
+ end
405
+ end
406
+ ```
407
+
408
+ ```
409
+ <!-- app/components/confirm_email_component.html.erb -->
410
+ <div class="banner">
411
+ Please confirm your email address.
412
+ </div>
413
+ ```
414
+
415
+ ```erb
416
+ <!-- app/views/_banners.html.erb -->
417
+ <%= render(ConfirmEmailComponent, user: current_user) %>
418
+ ```
419
+
202
420
  ### Testing
203
421
 
204
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:
@@ -234,7 +452,7 @@ end
234
452
  ```
235
453
 
236
454
  ### Previewing Components
237
- `ActionView::Component::Preview`s provide a way to see how components look by visiting a special URL that renders them.
455
+ `ActionView::Component::Preview` provides a way to see how components look by visiting a special URL that renders them.
238
456
  In the previous example, the preview class for `TestComponent` would be called `TestComponentPreview` and located in `test/components/previews/test_component_preview.rb`.
239
457
  To see the preview of the component with a given title, implement a method that renders the component.
240
458
  You can define as many examples as you want:
@@ -276,6 +494,16 @@ For example, if you want to use `lib/component_previews`, set the following in `
276
494
  config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
277
495
  ```
278
496
 
497
+ #### Configuring TestController
498
+
499
+ By default components tests and previews expect your Rails project to contain an `ApplicationController` class from which Controller classes inherit.
500
+ This can be configured using the `test_controller` option.
501
+ For example, if your controllers inherit from `BaseController`, set the following in `config/application.rb`:
502
+
503
+ ```ruby
504
+ config.action_view_component.test_controller = "BaseController"
505
+ ```
506
+
279
507
  ### Setting up RSpec
280
508
 
281
509
  If you're using RSpec, you can configure component specs to have access to test helpers. Add the following to
@@ -300,6 +528,11 @@ To use component previews, set the following in `config/application.rb`:
300
528
  config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
301
529
  ```
302
530
 
531
+ ### Initializer requirement
532
+
533
+ In Ruby 2.6.x and below, ActionView::Component requires the presence of an `initialize` method in each component.
534
+ However, `initialize` is no longer required for projects using 2.7.x and above.
535
+
303
536
  ## Frequently Asked Questions
304
537
 
305
538
  ### Can I use other templating languages besides ERB?
@@ -331,6 +564,35 @@ Inline templates have been removed (for now) due to concerns raised by [@soutaro
331
564
 
332
565
  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
566
 
567
+ ## Contributors
568
+
569
+ `actionview-component` is built by:
570
+
571
+ |<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" />|
572
+ |:---:|:---:|:---:|:---:|:---:|
573
+ |@joelhawksley|@tenderlove|@jonspalmer|@juanmanuelramallo|@vinistock|
574
+ |Denver|Seattle|Boston||Toronto|
575
+
576
+ |<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" />|
577
+ |:---:|:---:|:---:|:---:|:---:|
578
+ |@metade|@asgerb|@xronos-i-am|@dylnclrk|@kaspermeyer|
579
+ |London|Copenhagen|Russia, Kirov|Berkeley, CA|Denmark|
580
+
581
+ |<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" />|
582
+ |:---:|:---:|:---:|:---:|:---:|
583
+ |@rdavid1099|@kylefox|@traels|@rainerborene|@jcoyne|
584
+ |Los Angeles|Edmonton|Odense, Denmark|Brazil|Minneapolis|
585
+
586
+ |<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" />|
587
+ |:---:|:---:|:---:|:---:|:---:|
588
+ |@elia|@cesariouy|@spdawson|@rmacklin|@michaelem|
589
+ |Milan||United Kingdom||Berlin|
590
+
591
+ |<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" />|
592
+ |:---:|:---:|:---:|:---:|:---:|
593
+ |@mellowfish|@horacio|@dukex|@dark-panda|@smashwilson|
594
+ |Spring Hill, TN|Buenos Aires|São Paulo||Gambrills, MD|
595
+
334
596
  ## License
335
597
 
336
598
  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,40 +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]
128
- end
129
-
130
- def eager_load!
131
- self.descendants.each do |descendant|
132
- descendant.compile if descendant.has_initializer?
133
- end
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
134
153
  end
135
154
 
136
155
  def compiled?
137
156
  @compiled && ActionView::Base.cache_template_loading
138
157
  end
139
158
 
159
+ def compile!
160
+ compile(validate: true)
161
+ end
162
+
140
163
  # Compile templates to instance methods, assuming they haven't been compiled already.
141
164
  # We could in theory do this on app boot, at least in production environments.
142
165
  # Right now this just compiles the first time the component is rendered.
143
- def compile
166
+ def compile(validate: false)
144
167
  return if compiled?
145
168
 
146
- validate_templates
169
+ if template_errors.present?
170
+ raise ActionView::Component::TemplateError.new(template_errors) if validate
171
+ return false
172
+ end
147
173
 
148
174
  templates.each do |template|
149
175
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -170,10 +196,23 @@ module ActionView
170
196
  source_location
171
197
  end
172
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
+
173
207
  private
174
208
 
209
+ def const_source_location_supported?
210
+ respond_to? :const_source_location # introduced in Ruby 2.7
211
+ end
212
+
175
213
  def matching_views_in_source_location
176
- (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])
177
216
  end
178
217
 
179
218
  def templates
@@ -183,26 +222,39 @@ module ActionView
183
222
 
184
223
  memo << {
185
224
  path: path,
186
- variant: pieces.second.split("+")[1]&.to_sym,
225
+ variant: pieces.second.split("+").second&.to_sym,
187
226
  handler: pieces.last
188
227
  }
189
228
  end
190
229
  end
191
230
 
192
- def validate_templates
193
- if templates.empty?
194
- raise NotImplementedError.new("Could not find a template file for #{self}.")
195
- 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
196
240
 
197
- if templates.select { |template| template[:variant].nil? }.length > 1
198
- raise StandardError.new("More than one template found for #{self}. There can only be one default template file per component.")
199
- end
241
+ errors << "Could not find a template file for #{self}." if templates.empty?
200
242
 
201
- variants.each_with_object(Hash.new(0)) { |variant, counts| counts[variant] += 1 }.each do |variant, count|
202
- next unless count > 1
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
203
246
 
204
- raise StandardError.new("More than one template found for variant '#{variant}' in #{self}. There can only be one template file per variant.")
205
- end
247
+ invalid_variants = templates
248
+ .group_by { |template| template[:variant] }
249
+ .map { |variant, grouped| variant if grouped.length > 1 }
250
+ .compact
251
+ .sort
252
+
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
206
258
  end
207
259
 
208
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.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails"
4
+ require "action_view/component"
4
5
 
5
6
  module ActionView
6
7
  module Component
@@ -23,9 +24,7 @@ module ActionView
23
24
 
24
25
  initializer "action_view_component.set_autoload_paths" do |app|
25
26
  require "railties/lib/rails/components_controller"
26
- require "railties/lib/rails/component_examples_controller"
27
27
 
28
- app.config.eager_load_namespaces << ActionView::Component::Base
29
28
  options = app.config.action_view_component
30
29
 
31
30
  if options.show_previews && options.preview_path
@@ -33,6 +32,12 @@ module ActionView
33
32
  end
34
33
  end
35
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
+
36
41
  initializer "action_view_component.compile_config_methods" do
37
42
  ActiveSupport.on_load(:action_view_component) do
38
43
  config.compile_methods! if config.respond_to?(:compile_methods!)
@@ -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 = 1
7
+ MINOR = 9
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.1
4
+ version: 1.9.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: 2019-12-25 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
@@ -129,6 +129,8 @@ executables: []
129
129
  extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
+ - ".github/ISSUE_TEMPLATE"
133
+ - ".github/PULL_REQUEST_TEMPLATE"
132
134
  - ".github/workflows/ruby_on_rails.yml"
133
135
  - ".gitignore"
134
136
  - ".rubocop.yml"
@@ -148,6 +150,7 @@ files:
148
150
  - lib/action_view/component/previewable.rb
149
151
  - lib/action_view/component/railtie.rb
150
152
  - lib/action_view/component/render_monkey_patch.rb
153
+ - lib/action_view/component/template_error.rb
151
154
  - lib/action_view/component/test_case.rb
152
155
  - lib/action_view/component/test_helpers.rb
153
156
  - lib/action_view/component/version.rb
@@ -160,12 +163,10 @@ files:
160
163
  - lib/rails/generators/test_unit/component_generator.rb
161
164
  - lib/rails/generators/test_unit/templates/component_test.rb.tt
162
165
  - lib/railties/lib/rails.rb
163
- - lib/railties/lib/rails/component_examples_controller.rb
164
166
  - lib/railties/lib/rails/components_controller.rb
165
167
  - lib/railties/lib/rails/templates/rails/components/index.html.erb
166
168
  - lib/railties/lib/rails/templates/rails/components/preview.html.erb
167
169
  - lib/railties/lib/rails/templates/rails/components/previews.html.erb
168
- - lib/railties/lib/rails/templates/rails/examples/show.html.erb
169
170
  - script/bootstrap
170
171
  - script/console
171
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 %>