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