actionview-component 1.9.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67d62ba2c307963de0523a13f554d1e78b9e76990ace76c36b7d982ec83ef2cb
4
- data.tar.gz: 66682a57cd5504b054c7c21227bbfb6cdc2c14f0ac151f2d6bb136a1f2e7a6cb
3
+ metadata.gz: 0521a37d11215c6cb0c08a78054dd57a4bdf53ebea65935908611f73264a1727
4
+ data.tar.gz: 470a3bdcfb35dcb37162ec708b22491d66f7d1772f40a483a160ed6b49477e3c
5
5
  SHA512:
6
- metadata.gz: 0f5b33cb1dfff517ae58afb9167a5f0370f40902eef1ecfef1d6ee0e9f589a255826b92cbccf3bec5fd0a91d487601ec273f6238ddc029a3daade14aa5cd8a5a
7
- data.tar.gz: f2f58364fba2269e40bf5bd1040872f826365f67e09679996c79999a0755a1ffdbe06f89bb661f80e7a23a2b4791dff6618b28da9a9cf062b1ca41f65ee69dfa
6
+ metadata.gz: d92b3636294971bb68eadbc7bf53799642d2407de53cd2d492fec6f1723e96668db1d030043e6c7a3fa0f04b484b72f70d5104dbb252ffbcf4efad02e82cdf78
7
+ data.tar.gz: 8b24889733157c2c8890362af41881047a81ae3bd0677d9fb6528ced72dd6505ee1fc12607ee120f978ce7933a7a4d2d000e0e4de3d7e76998df341646c2f1f8
@@ -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**:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,51 @@
1
+ # master
2
+
3
+ # v1.13.0
4
+
5
+ * Allow components to be rendered inside controllers.
6
+
7
+ *Joel Hawksley*
8
+
9
+ * Improve backtraces from exceptions raised in templates.
10
+
11
+ *Blake Williams*
12
+
13
+ # v1.12.0
14
+
15
+ * Revert: Remove initializer requirement for Ruby 2.7+
16
+
17
+ *Joel Hawksley*
18
+
19
+ * Restructure Railtie into Engine
20
+
21
+ *Sean Doyle*
22
+
23
+ * Allow components to override before_render_check
24
+
25
+ *Joel Hawksley*
26
+
27
+ # v1.11.1
28
+
29
+ * Relax Capybara requirement.
30
+
31
+ *Joel Hawksley*
32
+
33
+ # v1.11.0
34
+
35
+ * Add support for Capybara matchers.
36
+
37
+ *Joel Hawksley*
38
+
39
+ * Add erb, haml, & slim template generators
40
+
41
+ *Asger Behncke Jacobsen*
42
+
43
+ # v1.10.0
44
+
45
+ * Deprecate all `render` syntaxes except for `render(MyComponent.new(foo: :bar))`
46
+
47
+ *Joel Hawksley*
48
+
1
49
  # v1.9.0
2
50
 
3
51
  * Remove initializer requirement for Ruby 2.7+
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionview-component (1.9.0)
4
+ actionview-component (1.13.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)
@@ -131,7 +143,8 @@ GEM
131
143
  rake (>= 0.8.7)
132
144
  thor (>= 0.20.3, < 2.0)
133
145
  rainbow (3.0.0)
134
- rake (10.5.0)
146
+ rake (13.0.1)
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
@@ -178,7 +193,7 @@ DEPENDENCIES
178
193
  haml (~> 5)
179
194
  minitest (= 5.1.0)
180
195
  rails (= 6.0.0)
181
- rake (~> 10.0)
196
+ rake (~> 13.0)
182
197
  rubocop (= 0.74)
183
198
  rubocop-github (~> 0.13.0)
184
199
  slim (~> 4.0)
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.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
  ```
@@ -530,8 +508,7 @@ config.action_view_component.preview_path = "#{Rails.root}/spec/components/previ
530
508
 
531
509
  ### Initializer requirement
532
510
 
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.
511
+ ActionView::Component requires the presence of an `initialize` method in each component.
535
512
 
536
513
  ## Frequently Asked Questions
537
514
 
@@ -593,6 +570,11 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/github
593
570
  |@mellowfish|@horacio|@dukex|@dark-panda|@smashwilson|
594
571
  |Spring Hill, TN|Buenos Aires|São Paulo||Gambrills, MD|
595
572
 
573
+ |<img src="https://avatars.githubusercontent.com/blakewilliams?s=256" alt="blakewilliams" width="128" />|
574
+ |:---:|
575
+ |@blakewilliams|
576
+ |Boston, MA|
577
+
596
578
  ## License
597
579
 
598
580
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -34,8 +34,9 @@ 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
- spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "rake", "~> 13.0"
39
40
  spec.add_development_dependency "minitest", "= 5.1.0"
40
41
  spec.add_development_dependency "haml", "~> 5"
41
42
  spec.add_development_dependency "slim", "~> 4.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
@@ -16,6 +16,7 @@ module ActionView
16
16
  autoload :TestHelpers
17
17
  autoload :TestCase
18
18
  autoload :RenderMonkeyPatch
19
+ autoload :RenderingMonkeyPatch
19
20
  autoload :TemplateError
20
21
  end
21
22
  end
@@ -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,14 +147,12 @@ module ActionView
142
147
 
143
148
  def source_location
144
149
  @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
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
153
156
  end
154
157
 
155
158
  def compiled?
@@ -172,7 +175,7 @@ module ActionView
172
175
  end
173
176
 
174
177
  templates.each do |template|
175
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
178
+ class_eval <<-RUBY, template[:path], -1
176
179
  def #{call_method_name(template[:variant])}
177
180
  @output_buffer = ActionView::OutputBuffer.new
178
181
  #{compiled_template(template[:path])}
@@ -206,10 +209,6 @@ module ActionView
206
209
 
207
210
  private
208
211
 
209
- def const_source_location_supported?
210
- respond_to? :const_source_location # introduced in Ruby 2.7
211
- end
212
-
213
212
  def matching_views_in_source_location
214
213
  return [] unless source_location
215
214
  (Dir["#{source_location.chomp(File.extname(source_location))}.*{#{ActionView::Template.template_handler_extensions.join(',')}}"] - [source_location])
@@ -232,7 +231,7 @@ module ActionView
232
231
  @template_errors ||=
233
232
  begin
234
233
  errors = []
235
- if source_location.nil? && !const_source_location_supported?
234
+ if source_location.nil?
236
235
  # Require `#initialize` to be defined so that we can use `method#source_location`
237
236
  # to look up the filename of the component.
238
237
  errors << "#{self} must implement #initialize."
@@ -0,0 +1,67 @@
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
+
50
+ ActiveSupport.on_load(:action_controller) do
51
+ ActionController::Base.prepend ActionView::Component::RenderingMonkeyPatch
52
+ end
53
+ end
54
+
55
+ config.after_initialize do |app|
56
+ options = app.config.action_view_component
57
+
58
+ if options.show_previews
59
+ app.routes.prepend do
60
+ get "/rails/components" => "rails/components#index", :internal => true
61
+ get "/rails/components/*path" => "rails/components#previews", :internal => true
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ 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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Component
5
+ module RenderingMonkeyPatch # :nodoc:
6
+ def render(options = {}, args = {})
7
+ if options.respond_to?(:render_in)
8
+ self.response_body = options.render_in(self.view_context)
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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,7 +4,7 @@ module ActionView
4
4
  module Component
5
5
  module VERSION
6
6
  MAJOR = 1
7
- MINOR = 9
7
+ MINOR = 13
8
8
  PATCH = 0
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -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.9.0
4
+ version: 1.13.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-02-14 00:00:00.000000000 Z
11
+ date: 2020-03-02 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
@@ -30,14 +44,14 @@ dependencies:
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '10.0'
47
+ version: '13.0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '10.0'
54
+ version: '13.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: minitest
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -143,27 +157,34 @@ 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
152
168
  - lib/action_view/component/render_monkey_patch.rb
169
+ - lib/action_view/component/rendering_monkey_patch.rb
153
170
  - lib/action_view/component/template_error.rb
154
171
  - lib/action_view/component/test_case.rb
155
172
  - lib/action_view/component/test_helpers.rb
156
173
  - lib/action_view/component/version.rb
157
174
  - lib/rails/generators/component/USAGE
158
175
  - lib/rails/generators/component/component_generator.rb
159
- - lib/rails/generators/component/templates/component.html.erb.tt
160
176
  - lib/rails/generators/component/templates/component.rb.tt
177
+ - lib/rails/generators/erb/component_generator.rb
178
+ - lib/rails/generators/erb/templates/component.html.erb.tt
179
+ - lib/rails/generators/haml/component_generator.rb
180
+ - lib/rails/generators/haml/templates/component.html.haml.tt
161
181
  - lib/rails/generators/rspec/component_generator.rb
162
182
  - lib/rails/generators/rspec/templates/component_spec.rb.tt
183
+ - lib/rails/generators/slim/component_generator.rb
184
+ - lib/rails/generators/slim/templates/component.html.slim
163
185
  - lib/rails/generators/test_unit/component_generator.rb
164
186
  - lib/rails/generators/test_unit/templates/component_test.rb.tt
165
187
  - lib/railties/lib/rails.rb
166
- - lib/railties/lib/rails/components_controller.rb
167
188
  - lib/railties/lib/rails/templates/rails/components/index.html.erb
168
189
  - lib/railties/lib/rails/templates/rails/components/preview.html.erb
169
190
  - lib/railties/lib/rails/templates/rails/components/previews.html.erb