actionview-component 1.4.0 → 1.6.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 +4 -4
- data/CHANGELOG.md +72 -0
- data/Gemfile.lock +15 -4
- data/README.md +103 -2
- data/actionview-component.gemspec +1 -0
- data/lib/action_view/component.rb +6 -0
- data/lib/action_view/component/active_model_conversion_monkey_patch.rb +9 -0
- data/lib/action_view/component/base.rb +26 -56
- data/lib/action_view/component/monkey_patch.rb +29 -0
- data/lib/action_view/component/preview.rb +106 -0
- data/lib/action_view/component/railtie.rb +51 -0
- data/lib/action_view/component/test_helpers.rb +1 -1
- data/lib/action_view/component/version.rb +1 -1
- data/lib/rails/generators/component/USAGE +13 -0
- data/lib/rails/generators/component/component_generator.rb +50 -0
- data/lib/rails/generators/component/templates/component.html.erb.tt +5 -0
- data/lib/rails/generators/component/templates/component.rb.tt +9 -0
- data/lib/rails/generators/rspec/component_generator.rb +19 -0
- data/lib/rails/generators/rspec/templates/component_spec.rb.tt +13 -0
- data/lib/rails/generators/test_unit/component_generator.rb +20 -0
- data/lib/rails/generators/test_unit/templates/component_test.rb.tt +12 -0
- data/lib/railties/lib/rails.rb +6 -0
- data/lib/railties/lib/rails/component_examples_controller.rb +9 -0
- data/lib/railties/lib/rails/components_controller.rb +55 -0
- data/lib/railties/lib/rails/templates/rails/components/index.html.erb +8 -0
- data/lib/railties/lib/rails/templates/rails/components/preview.html.erb +1 -0
- data/lib/railties/lib/rails/templates/rails/components/previews.html.erb +6 -0
- data/lib/railties/lib/rails/templates/rails/examples/show.html.erb +1 -0
- metadata +36 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0d1497b59c35721a9d9c48906ca5b1a64eb30f72e9bfd17d0da2437820e7e87
|
|
4
|
+
data.tar.gz: e703a27db8cac4a589f6d8f78e3ffc2bb7783af16d7c49c8986d06db7155bcd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 481e35fdf095d48a1c3a33795d8e1d8b0be6a6b6944b149e5a6320add88f8748f4ea8e19282cd90bb62f9858291cd07084e9b1221051fcfb89f70b7dd6471281
|
|
7
|
+
data.tar.gz: 45fd087cc394799a8f21044c58de91a3aeee93dc29c8fbd8ff8c4a1979977bb86bc47d38341f51a37bbe93038c81b77908ec8d634d4d41b064a11585a684ae44
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
|
+
# v1.6.0
|
|
2
|
+
|
|
3
|
+
* Avoid dropping elements in the render_inline test helper.
|
|
4
|
+
|
|
5
|
+
*@dark-panda*
|
|
6
|
+
|
|
7
|
+
* Add test for helpers.asset_url.
|
|
8
|
+
|
|
9
|
+
*Christopher Coleman*
|
|
10
|
+
|
|
11
|
+
* Add rudimentary compatibility with better_html.
|
|
12
|
+
|
|
13
|
+
*Joel Hawksley*
|
|
14
|
+
|
|
15
|
+
* Template-less variants fall back to default template.
|
|
16
|
+
|
|
17
|
+
*Asger Behncke Jacobsen*, *Cesario Uy*
|
|
18
|
+
|
|
19
|
+
* Generated tests use new naming convention.
|
|
20
|
+
|
|
21
|
+
*Simon Træls Ravn*
|
|
22
|
+
|
|
23
|
+
* Eliminate sqlite dependency.
|
|
24
|
+
|
|
25
|
+
*Simon Dawson*
|
|
26
|
+
|
|
27
|
+
* Add support for rendering components via #to_component_class
|
|
28
|
+
|
|
29
|
+
*Vinicius Stock*
|
|
30
|
+
|
|
31
|
+
# v1.5.3
|
|
32
|
+
|
|
33
|
+
* Add support for RSpec to generators.
|
|
34
|
+
|
|
35
|
+
*Dylan Clark, Ryan Workman*
|
|
36
|
+
|
|
37
|
+
* Require controllers as part of setting autoload paths.
|
|
38
|
+
|
|
39
|
+
*Joel Hawksley*
|
|
40
|
+
|
|
41
|
+
# v1.5.2
|
|
42
|
+
|
|
43
|
+
* Disable eager loading initializer.
|
|
44
|
+
|
|
45
|
+
*Kasper Meyer*
|
|
46
|
+
|
|
47
|
+
# v1.5.1
|
|
48
|
+
|
|
49
|
+
* Update railties class to work with Rails 6.
|
|
50
|
+
|
|
51
|
+
*Juan Manuel Ramallo*
|
|
52
|
+
|
|
53
|
+
# v1.5.0
|
|
54
|
+
|
|
55
|
+
Note: `actionview-component` is now loaded by requiring `actionview/component`, not `actionview/component/base`.
|
|
56
|
+
|
|
57
|
+
* Fix issue with generating component method signatures.
|
|
58
|
+
|
|
59
|
+
*Ryan Workman, Dylan Clark*
|
|
60
|
+
|
|
61
|
+
* Create component generator.
|
|
62
|
+
|
|
63
|
+
*Vinicius Stock*
|
|
64
|
+
|
|
65
|
+
* Add helpers proxy.
|
|
66
|
+
|
|
67
|
+
*Kasper Meyer*
|
|
68
|
+
|
|
69
|
+
* Introduce ActionView::Component::Previews.
|
|
70
|
+
|
|
71
|
+
*Juan Manuel Ramallo*
|
|
72
|
+
|
|
1
73
|
# v1.4.0
|
|
2
74
|
|
|
3
75
|
* Fix bug where components broke in application paths with periods.
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
actionview-component (1.
|
|
4
|
+
actionview-component (1.6.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
@@ -62,19 +62,28 @@ GEM
|
|
|
62
62
|
tzinfo (~> 1.1)
|
|
63
63
|
zeitwerk (~> 2.1, >= 2.1.8)
|
|
64
64
|
ast (2.4.0)
|
|
65
|
+
better_html (1.0.14)
|
|
66
|
+
actionview (>= 4.0)
|
|
67
|
+
activesupport (>= 4.0)
|
|
68
|
+
ast (~> 2.0)
|
|
69
|
+
erubi (~> 1.4)
|
|
70
|
+
html_tokenizer (~> 0.0.6)
|
|
71
|
+
parser (>= 2.4)
|
|
72
|
+
smart_properties
|
|
65
73
|
builder (3.2.3)
|
|
66
74
|
concurrent-ruby (1.1.5)
|
|
67
|
-
crass (1.0.
|
|
75
|
+
crass (1.0.5)
|
|
68
76
|
erubi (1.8.0)
|
|
69
77
|
globalid (0.4.2)
|
|
70
78
|
activesupport (>= 4.2.0)
|
|
71
79
|
haml (5.1.2)
|
|
72
80
|
temple (>= 0.8.0)
|
|
73
81
|
tilt
|
|
82
|
+
html_tokenizer (0.0.7)
|
|
74
83
|
i18n (1.6.0)
|
|
75
84
|
concurrent-ruby (~> 1.0)
|
|
76
85
|
jaro_winkler (1.5.3)
|
|
77
|
-
loofah (2.
|
|
86
|
+
loofah (2.3.1)
|
|
78
87
|
crass (~> 1.0.2)
|
|
79
88
|
nokogiri (>= 1.5.9)
|
|
80
89
|
mail (2.7.1)
|
|
@@ -87,7 +96,7 @@ GEM
|
|
|
87
96
|
mini_portile2 (2.4.0)
|
|
88
97
|
minitest (5.1.0)
|
|
89
98
|
nio4r (2.5.2)
|
|
90
|
-
nokogiri (1.10.
|
|
99
|
+
nokogiri (1.10.5)
|
|
91
100
|
mini_portile2 (~> 2.4.0)
|
|
92
101
|
parallel (1.17.0)
|
|
93
102
|
parser (2.6.3.0)
|
|
@@ -139,6 +148,7 @@ GEM
|
|
|
139
148
|
slim (4.0.1)
|
|
140
149
|
temple (>= 0.7.6, < 0.9)
|
|
141
150
|
tilt (>= 2.0.6, < 2.1)
|
|
151
|
+
smart_properties (1.15.0)
|
|
142
152
|
sprockets (3.7.2)
|
|
143
153
|
concurrent-ruby (~> 1.0)
|
|
144
154
|
rack (> 1, < 3)
|
|
@@ -163,6 +173,7 @@ PLATFORMS
|
|
|
163
173
|
|
|
164
174
|
DEPENDENCIES
|
|
165
175
|
actionview-component!
|
|
176
|
+
better_html (~> 1)
|
|
166
177
|
bundler (>= 1.14)
|
|
167
178
|
haml (~> 5)
|
|
168
179
|
minitest (= 5.1.0)
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
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.
|
|
9
9
|
|
|
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
|
|
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.
|
|
11
11
|
|
|
12
12
|
## Design philosophy
|
|
13
13
|
|
|
@@ -32,7 +32,7 @@ $ bundle
|
|
|
32
32
|
In `config/application.rb`, add:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
require "action_view/component
|
|
35
|
+
require "action_view/component"
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Guide
|
|
@@ -97,10 +97,26 @@ Components are subclasses of `ActionView::Component::Base` and live in `app/comp
|
|
|
97
97
|
|
|
98
98
|
Component class names end in -`Component`.
|
|
99
99
|
|
|
100
|
+
Component module names are plural, as they are for controllers. (`Users::AvatarComponent`)
|
|
101
|
+
|
|
100
102
|
Components support ActiveModel validations. Components are validated after initialization, but before rendering.
|
|
101
103
|
|
|
102
104
|
Content passed to an `ActionView::Component` as a block is captured and assigned to the `content` accessor.
|
|
103
105
|
|
|
106
|
+
#### Quick start
|
|
107
|
+
|
|
108
|
+
Use the component generator to create a new `ActionView::Component`.
|
|
109
|
+
|
|
110
|
+
The generator accepts the component name and the list of accepted properties as arguments:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
bin/rails generate component Example title content
|
|
114
|
+
invoke test_unit
|
|
115
|
+
create test/components/example_component_test.rb
|
|
116
|
+
create app/components/example_component.rb
|
|
117
|
+
create app/components/example_component.html.erb
|
|
118
|
+
```
|
|
119
|
+
|
|
104
120
|
#### Implementation
|
|
105
121
|
|
|
106
122
|
An `ActionView::Component` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name:
|
|
@@ -147,6 +163,24 @@ Components can be rendered via:
|
|
|
147
163
|
|
|
148
164
|
`render(component: TestComponent, locals: { foo: :bar })`
|
|
149
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
|
|
178
|
+
def initialize(post)
|
|
179
|
+
@post = post
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
150
184
|
The following syntax has been deprecated and will be removed in v2.0.0:
|
|
151
185
|
|
|
152
186
|
`render(TestComponent.new(foo: :bar))`
|
|
@@ -201,6 +235,73 @@ def test_render_component_for_tablet
|
|
|
201
235
|
end
|
|
202
236
|
```
|
|
203
237
|
|
|
238
|
+
### Previewing Components
|
|
239
|
+
`ActionView::Component::Preview`s provide a way to see how components look by visiting a special URL that renders them.
|
|
240
|
+
In the previous example, the preview class for `TestComponent` would be called `TestComponentPreview` and located in `test/components/previews/test_component_preview.rb`.
|
|
241
|
+
To see the preview of the component with a given title, implement a method that renders the component.
|
|
242
|
+
You can define as many examples as you want:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
# test/components/previews/test_component_preview.rb
|
|
246
|
+
|
|
247
|
+
class TestComponentPreview < ActionView::Component::Preview
|
|
248
|
+
def with_default_title
|
|
249
|
+
render(TestComponent, title: "Test component default")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def with_long_title
|
|
253
|
+
render(TestComponent, title: "This is a really long title to see how the component renders this")
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The previews will be available in <http://localhost:3000/rails/components/test_component/with_default_title>
|
|
259
|
+
and <http://localhost:3000/rails/components/test_component/with_long_title>.
|
|
260
|
+
|
|
261
|
+
Previews use the application layout by default, but you can also use other layouts from your app:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# test/components/previews/test_component_preview.rb
|
|
265
|
+
|
|
266
|
+
class TestComponentPreview < ActionView::Component::Preview
|
|
267
|
+
layout "admin"
|
|
268
|
+
|
|
269
|
+
...
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
By default, the preview classes live in `test/components/previews`.
|
|
274
|
+
This can be configured using the `preview_path` option.
|
|
275
|
+
For example, if you want to use `lib/component_previews`, set the following in `config/application.rb`:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Setting up RSpec
|
|
282
|
+
|
|
283
|
+
If you're using RSpec, you can configure component specs to have access to test helpers. Add the following to
|
|
284
|
+
`spec/rails_helper.rb`:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
require "action_view/component/test_helpers"
|
|
288
|
+
|
|
289
|
+
RSpec.configure do |config|
|
|
290
|
+
# ...
|
|
291
|
+
|
|
292
|
+
# Ensure that the test helpers are available in component specs
|
|
293
|
+
config.include ActionView::Component::TestHelpers, type: :component
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Specs created by the generator should now have access to test helpers like `render_inline`.
|
|
298
|
+
|
|
299
|
+
To use component previews, set the following in `config/application.rb`:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
|
|
303
|
+
```
|
|
304
|
+
|
|
204
305
|
## Frequently Asked Questions
|
|
205
306
|
|
|
206
307
|
### Can I use other templating languages besides ERB?
|
|
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
|
|
|
39
39
|
spec.add_development_dependency "minitest", "= 5.1.0"
|
|
40
40
|
spec.add_development_dependency "haml", "~> 5"
|
|
41
41
|
spec.add_development_dependency "slim", "~> 4.0"
|
|
42
|
+
spec.add_development_dependency "better_html", "~> 1"
|
|
42
43
|
spec.add_development_dependency "rubocop", "= 0.74"
|
|
43
44
|
spec.add_development_dependency "rubocop-github", "~> 0.13.0"
|
|
44
45
|
end
|
|
@@ -1,39 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ActionView::Base
|
|
8
|
-
module RenderMonkeyPatch
|
|
9
|
-
def render(options = {}, args = {}, &block)
|
|
10
|
-
if options.respond_to?(:render_in)
|
|
11
|
-
ActiveSupport::Deprecation.warn(
|
|
12
|
-
"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."
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
options.render_in(self, &block)
|
|
16
|
-
elsif options.is_a?(Class) && options < ActionView::Component::Base
|
|
17
|
-
options.new(args).render_in(self, &block)
|
|
18
|
-
elsif options.is_a?(Hash) && options.has_key?(:component)
|
|
19
|
-
options[:component].new(options[:locals]).render_in(self, &block)
|
|
20
|
-
else
|
|
21
|
-
super
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
prepend RenderMonkeyPatch
|
|
27
|
-
end
|
|
3
|
+
require "active_model"
|
|
4
|
+
require "action_view"
|
|
5
|
+
require "active_support/configurable"
|
|
6
|
+
require_relative "preview"
|
|
28
7
|
|
|
29
8
|
module ActionView
|
|
30
9
|
module Component
|
|
31
10
|
class Base < ActionView::Base
|
|
32
11
|
include ActiveModel::Validations
|
|
33
12
|
include ActiveSupport::Configurable
|
|
34
|
-
include
|
|
13
|
+
include ActionView::Component::Previews
|
|
35
14
|
|
|
36
|
-
|
|
15
|
+
delegate :form_authenticity_token, :protect_against_forgery?, to: :helpers
|
|
37
16
|
|
|
38
17
|
# Entrypoint for rendering components. Called by ActionView::Base#render.
|
|
39
18
|
#
|
|
@@ -93,10 +72,14 @@ module ActionView
|
|
|
93
72
|
@controller ||= view_context.controller
|
|
94
73
|
end
|
|
95
74
|
|
|
96
|
-
#
|
|
75
|
+
# Provides a proxy to access helper methods through
|
|
76
|
+
def helpers
|
|
77
|
+
@helpers ||= view_context
|
|
78
|
+
end
|
|
79
|
+
|
|
97
80
|
# Removes the first part of the path and the extension.
|
|
98
81
|
def virtual_path
|
|
99
|
-
self.class.source_location.gsub(%r{(.*app/)|(\.rb)}, "")
|
|
82
|
+
self.class.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
|
|
100
83
|
end
|
|
101
84
|
|
|
102
85
|
def view_cache_dependencies
|
|
@@ -109,12 +92,6 @@ module ActionView
|
|
|
109
92
|
|
|
110
93
|
private
|
|
111
94
|
|
|
112
|
-
def variant_exists
|
|
113
|
-
return if self.class.variants.include?(@variant) || @variant.nil?
|
|
114
|
-
|
|
115
|
-
errors.add(:variant, "'#{@variant}' has no template defined")
|
|
116
|
-
end
|
|
117
|
-
|
|
118
95
|
def request
|
|
119
96
|
@request ||= controller.request
|
|
120
97
|
end
|
|
@@ -129,7 +106,7 @@ module ActionView
|
|
|
129
106
|
end
|
|
130
107
|
|
|
131
108
|
def call_method_name(variant)
|
|
132
|
-
if variant.present?
|
|
109
|
+
if variant.present? && variants.include?(variant)
|
|
133
110
|
"call_#{variant}"
|
|
134
111
|
else
|
|
135
112
|
"call"
|
|
@@ -173,6 +150,15 @@ module ActionView
|
|
|
173
150
|
templates.map { |template| template[:variant] }
|
|
174
151
|
end
|
|
175
152
|
|
|
153
|
+
# we'll eventually want to update this to support other types
|
|
154
|
+
def type
|
|
155
|
+
"text/html"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def identifier
|
|
159
|
+
source_location
|
|
160
|
+
end
|
|
161
|
+
|
|
176
162
|
private
|
|
177
163
|
|
|
178
164
|
def templates
|
|
@@ -206,31 +192,15 @@ module ActionView
|
|
|
206
192
|
handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
|
|
207
193
|
template = File.read(file_path)
|
|
208
194
|
|
|
209
|
-
# This can be removed once this code is merged into Rails
|
|
210
195
|
if handler.method(:call).parameters.length > 1
|
|
211
|
-
handler.call(
|
|
212
|
-
else
|
|
213
|
-
handler.call(
|
|
196
|
+
handler.call(self, template)
|
|
197
|
+
else # remove before upstreaming into Rails
|
|
198
|
+
handler.call(OpenStruct.new(source: template, identifier: identifier, type: type))
|
|
214
199
|
end
|
|
215
200
|
end
|
|
216
201
|
end
|
|
217
202
|
|
|
218
|
-
|
|
219
|
-
attr_reader :source
|
|
220
|
-
|
|
221
|
-
def initialize(source = nil)
|
|
222
|
-
@source = source
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def identifier
|
|
226
|
-
""
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# we'll eventually want to update this to support other types
|
|
230
|
-
def type
|
|
231
|
-
"text/html"
|
|
232
|
-
end
|
|
233
|
-
end
|
|
203
|
+
ActiveSupport.run_load_hooks(:action_view_component, self)
|
|
234
204
|
end
|
|
235
205
|
end
|
|
236
206
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
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
|
+
class ActionView::Base
|
|
8
|
+
module RenderMonkeyPatch
|
|
9
|
+
def render(options = {}, args = {}, &block)
|
|
10
|
+
if options.respond_to?(:render_in)
|
|
11
|
+
ActiveSupport::Deprecation.warn(
|
|
12
|
+
"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."
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
options.render_in(self, &block)
|
|
16
|
+
elsif options.is_a?(Class) && options < ActionView::Component::Base
|
|
17
|
+
options.new(args).render_in(self, &block)
|
|
18
|
+
elsif options.is_a?(Hash) && options.has_key?(:component)
|
|
19
|
+
options[:component].new(options[:locals]).render_in(self, &block)
|
|
20
|
+
elsif options.respond_to?(:to_component_class) && !options.to_component_class.nil?
|
|
21
|
+
options.to_component_class.new(options).render_in(self, &block)
|
|
22
|
+
else
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
prepend RenderMonkeyPatch
|
|
29
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
require "active_support/descendants_tracker"
|
|
5
|
+
require_relative "test_helpers"
|
|
6
|
+
|
|
7
|
+
module ActionView
|
|
8
|
+
module Component #:nodoc:
|
|
9
|
+
module Previews
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
# Set the location of component previews through app configuration:
|
|
14
|
+
#
|
|
15
|
+
# config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
|
|
16
|
+
#
|
|
17
|
+
mattr_accessor :preview_path, instance_writer: false
|
|
18
|
+
|
|
19
|
+
# Enable or disable component previews through app configuration:
|
|
20
|
+
#
|
|
21
|
+
# config.action_view_component.show_previews = true
|
|
22
|
+
#
|
|
23
|
+
# Defaults to +true+ for development environment
|
|
24
|
+
#
|
|
25
|
+
mattr_accessor :show_previews, instance_writer: false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Preview
|
|
30
|
+
extend ActiveSupport::DescendantsTracker
|
|
31
|
+
include ActionView::Component::TestHelpers
|
|
32
|
+
|
|
33
|
+
def render(component, *locals)
|
|
34
|
+
render_inline(component, *locals)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
# Returns all component preview classes.
|
|
39
|
+
def all
|
|
40
|
+
load_previews if descendants.empty?
|
|
41
|
+
descendants
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns the html of the component in its layout
|
|
45
|
+
def call(example)
|
|
46
|
+
example_html = new.public_send(example)
|
|
47
|
+
|
|
48
|
+
Rails::ComponentExamplesController.render(template: "examples/show",
|
|
49
|
+
layout: @layout || "layouts/application",
|
|
50
|
+
assigns: { example: example_html })
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns the component object class associated to the preview.
|
|
54
|
+
def component
|
|
55
|
+
self.name.sub(%r{Preview$}, "").constantize
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns all of the available examples for the component preview.
|
|
59
|
+
def examples
|
|
60
|
+
public_instance_methods(false).map(&:to_s).sort
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns +true+ if the example of the component preview exists.
|
|
64
|
+
def example_exists?(example)
|
|
65
|
+
examples.include?(example)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns +true+ if the preview exists.
|
|
69
|
+
def exists?(preview)
|
|
70
|
+
all.any? { |p| p.preview_name == preview }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Find a component preview by its underscored class name.
|
|
74
|
+
def find(preview)
|
|
75
|
+
all.find { |p| p.preview_name == preview }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns the underscored name of the component preview without the suffix.
|
|
79
|
+
def preview_name
|
|
80
|
+
name.sub(/Preview$/, "").underscore
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Setter for layout name.
|
|
84
|
+
def layout(layout_name)
|
|
85
|
+
@layout = layout_name
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def load_previews
|
|
91
|
+
if preview_path
|
|
92
|
+
Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def preview_path
|
|
97
|
+
Base.preview_path
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def show_previews
|
|
101
|
+
Base.show_previews
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
module Component
|
|
5
|
+
class Railtie < Rails::Railtie # :nodoc:
|
|
6
|
+
config.action_view_component = ActiveSupport::OrderedOptions.new
|
|
7
|
+
|
|
8
|
+
initializer "action_view_component.set_configs" do |app|
|
|
9
|
+
options = app.config.action_view_component
|
|
10
|
+
|
|
11
|
+
options.show_previews = Rails.env.development? if options.show_previews.nil?
|
|
12
|
+
|
|
13
|
+
if options.show_previews
|
|
14
|
+
options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/components/previews" : nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
ActiveSupport.on_load(:action_view_component) do
|
|
18
|
+
options.each { |k, v| send("#{k}=", v) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
initializer "action_view_component.set_autoload_paths" do |app|
|
|
23
|
+
require "railties/lib/rails/components_controller"
|
|
24
|
+
require "railties/lib/rails/component_examples_controller"
|
|
25
|
+
|
|
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.compile_config_methods" do
|
|
34
|
+
ActiveSupport.on_load(:action_view_component) do
|
|
35
|
+
config.compile_methods! if config.respond_to?(:compile_methods!)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
config.after_initialize do |app|
|
|
40
|
+
options = app.config.action_view_component
|
|
41
|
+
|
|
42
|
+
if options.show_previews
|
|
43
|
+
app.routes.prepend do
|
|
44
|
+
get "/rails/components" => "rails/components#index", :internal => true
|
|
45
|
+
get "/rails/components/*path" => "rails/components#previews", :internal => true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -4,7 +4,7 @@ module ActionView
|
|
|
4
4
|
module Component
|
|
5
5
|
module TestHelpers
|
|
6
6
|
def render_inline(component, **args, &block)
|
|
7
|
-
Nokogiri::HTML(controller.view_context.render(component, args, &block))
|
|
7
|
+
Nokogiri::HTML.fragment(controller.view_context.render(component, args, &block))
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def controller
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
============
|
|
3
|
+
Creates a new component and test.
|
|
4
|
+
Pass the component name, either CamelCased or under_scored, and an optional list of attributes as arguments.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
========
|
|
8
|
+
bin/rails generate component Profile name age
|
|
9
|
+
|
|
10
|
+
creates a Profile component and test:
|
|
11
|
+
Component: app/components/profile_component.rb
|
|
12
|
+
Template: app/components/profile_component.html.erb
|
|
13
|
+
Test: test/components/profile_component_test.rb
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rails
|
|
4
|
+
module Generators
|
|
5
|
+
class ComponentGenerator < Rails::Generators::NamedBase
|
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
|
7
|
+
|
|
8
|
+
argument :attributes, type: :array, default: [], banner: "attribute"
|
|
9
|
+
hook_for :test_framework
|
|
10
|
+
check_class_collision suffix: "Component"
|
|
11
|
+
|
|
12
|
+
def create_component_file
|
|
13
|
+
template "component.rb", File.join("app/components", "#{file_name}_component.rb")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create_template_file
|
|
17
|
+
template "component.html.erb", File.join("app/components", "#{file_name}_component.html.erb")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def file_name
|
|
23
|
+
@_file_name ||= super.sub(/_component\z/i, "")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def requires_content?
|
|
27
|
+
return @requires_content if @asked
|
|
28
|
+
|
|
29
|
+
@asked = true
|
|
30
|
+
@requires_content = ask("Would you like #{class_name} to require content? (Y/n)").downcase == "y"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parent_class
|
|
34
|
+
defined?(ApplicationComponent) ? "ApplicationComponent" : "ActionView::Component::Base"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize_signature
|
|
38
|
+
if attributes.present?
|
|
39
|
+
attributes.map { |attr| "#{attr.name}:" }.join(", ")
|
|
40
|
+
else
|
|
41
|
+
"*"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def initialize_body
|
|
46
|
+
attributes.map { |attr| "@#{attr.name} = #{attr.name}" }.join("\n ")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rspec
|
|
4
|
+
module Generators
|
|
5
|
+
class ComponentGenerator < ::Rails::Generators::NamedBase
|
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
|
7
|
+
|
|
8
|
+
def create_test_file
|
|
9
|
+
template "component_spec.rb", File.join("spec/components", "#{file_name}_component_spec.rb")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def file_name
|
|
15
|
+
@_file_name ||= super.sub(/_component\z/i, "")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require "rails_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe <%= class_name %>Component, type: :component do
|
|
4
|
+
pending "add some examples to (or delete) #{__FILE__}"
|
|
5
|
+
|
|
6
|
+
# it "renders something useful" do
|
|
7
|
+
# expect(
|
|
8
|
+
# render_inline(described_class, attr: "value") { "Hello, components!" }.css("p").to_html
|
|
9
|
+
# ).to include(
|
|
10
|
+
# "Hello, components!"
|
|
11
|
+
# )
|
|
12
|
+
# end
|
|
13
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TestUnit
|
|
4
|
+
module Generators
|
|
5
|
+
class ComponentGenerator < ::Rails::Generators::NamedBase
|
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
|
7
|
+
check_class_collision suffix: "ComponentTest"
|
|
8
|
+
|
|
9
|
+
def create_test_file
|
|
10
|
+
template "component_test.rb", File.join("test/components", "#{file_name}_component_test.rb")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def file_name
|
|
16
|
+
@_file_name ||= super.sub(/_component\z/i, "")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class <%= class_name %>ComponentTest < ActiveSupport::TestCase
|
|
4
|
+
include ActionView::Component::TestHelpers
|
|
5
|
+
|
|
6
|
+
test "component renders something useful" do
|
|
7
|
+
# assert_equal(
|
|
8
|
+
# %(<span title="my title">Hello, components!</span>),
|
|
9
|
+
# render_inline(<%= class_name %>Component, attr: "value") { "Hello, components!" }.css("span").to_html
|
|
10
|
+
# )
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/application_controller"
|
|
4
|
+
|
|
5
|
+
class Rails::ComponentsController < Rails::ApplicationController # :nodoc:
|
|
6
|
+
prepend_view_path File.expand_path("templates/rails", __dir__)
|
|
7
|
+
|
|
8
|
+
around_action :set_locale, only: :previews
|
|
9
|
+
before_action :find_preview, only: :previews
|
|
10
|
+
before_action :require_local!, unless: :show_previews?
|
|
11
|
+
|
|
12
|
+
if respond_to?(:content_security_policy)
|
|
13
|
+
content_security_policy(false)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def index
|
|
17
|
+
@previews = ActionView::Component::Preview.all
|
|
18
|
+
@page_title = "Component Previews"
|
|
19
|
+
render template: "components/index"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def previews
|
|
23
|
+
if params[:path] == @preview.preview_name
|
|
24
|
+
@page_title = "Component Previews for #{@preview.preview_name}"
|
|
25
|
+
render template: "components/previews"
|
|
26
|
+
else
|
|
27
|
+
@example_name = File.basename(params[:path])
|
|
28
|
+
render template: "components/preview", layout: false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def show_previews? # :doc:
|
|
35
|
+
ActionView::Component::Base.show_previews
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def find_preview # :doc:
|
|
39
|
+
candidates = []
|
|
40
|
+
params[:path].to_s.scan(%r{/|$}) { candidates << $` }
|
|
41
|
+
preview = candidates.detect { |candidate| ActionView::Component::Preview.exists?(candidate) }
|
|
42
|
+
|
|
43
|
+
if preview
|
|
44
|
+
@preview = ActionView::Component::Preview.find(preview)
|
|
45
|
+
else
|
|
46
|
+
raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_locale
|
|
51
|
+
I18n.with_locale(params[:locale] || I18n.default_locale) do
|
|
52
|
+
yield
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% @previews.each do |preview| %>
|
|
2
|
+
<h3><%= link_to preview.preview_name.titleize, "/rails/components/#{preview.preview_name}" %></h3>
|
|
3
|
+
<ul>
|
|
4
|
+
<% preview.examples.each do |preview_example| %>
|
|
5
|
+
<li><%= link_to preview_example, "/rails/components/#{preview.preview_name}/#{preview_example}" %></li>
|
|
6
|
+
<% end %>
|
|
7
|
+
</ul>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= raw @preview.call(@example_name) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= raw @example %>
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: actionview-component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.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-11
|
|
11
|
+
date: 2019-12-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '4.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: better_html
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1'
|
|
83
97
|
- !ruby/object:Gem::Dependency
|
|
84
98
|
name: rubocop
|
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -127,9 +141,29 @@ files:
|
|
|
127
141
|
- README.md
|
|
128
142
|
- Rakefile
|
|
129
143
|
- actionview-component.gemspec
|
|
144
|
+
- lib/action_view/component.rb
|
|
145
|
+
- lib/action_view/component/active_model_conversion_monkey_patch.rb
|
|
130
146
|
- lib/action_view/component/base.rb
|
|
147
|
+
- lib/action_view/component/monkey_patch.rb
|
|
148
|
+
- lib/action_view/component/preview.rb
|
|
149
|
+
- lib/action_view/component/railtie.rb
|
|
131
150
|
- lib/action_view/component/test_helpers.rb
|
|
132
151
|
- lib/action_view/component/version.rb
|
|
152
|
+
- lib/rails/generators/component/USAGE
|
|
153
|
+
- lib/rails/generators/component/component_generator.rb
|
|
154
|
+
- lib/rails/generators/component/templates/component.html.erb.tt
|
|
155
|
+
- lib/rails/generators/component/templates/component.rb.tt
|
|
156
|
+
- lib/rails/generators/rspec/component_generator.rb
|
|
157
|
+
- lib/rails/generators/rspec/templates/component_spec.rb.tt
|
|
158
|
+
- lib/rails/generators/test_unit/component_generator.rb
|
|
159
|
+
- lib/rails/generators/test_unit/templates/component_test.rb.tt
|
|
160
|
+
- lib/railties/lib/rails.rb
|
|
161
|
+
- lib/railties/lib/rails/component_examples_controller.rb
|
|
162
|
+
- lib/railties/lib/rails/components_controller.rb
|
|
163
|
+
- lib/railties/lib/rails/templates/rails/components/index.html.erb
|
|
164
|
+
- lib/railties/lib/rails/templates/rails/components/preview.html.erb
|
|
165
|
+
- lib/railties/lib/rails/templates/rails/components/previews.html.erb
|
|
166
|
+
- lib/railties/lib/rails/templates/rails/examples/show.html.erb
|
|
133
167
|
- script/bootstrap
|
|
134
168
|
- script/console
|
|
135
169
|
- script/install
|