view_component 1.16.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.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE +27 -0
- data/.github/PULL_REQUEST_TEMPLATE +17 -0
- data/.github/workflows/ruby_on_rails.yml +31 -0
- data/.gitignore +53 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +373 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +46 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +202 -0
- data/LICENSE.txt +21 -0
- data/README.md +460 -0
- data/Rakefile +12 -0
- data/app/controllers/rails/components_controller.rb +65 -0
- data/docs/case-studies/jellyswitch.md +76 -0
- data/lib/action_view/component.rb +4 -0
- data/lib/action_view/component/base.rb +13 -0
- data/lib/action_view/component/preview.rb +8 -0
- data/lib/action_view/component/railtie.rb +3 -0
- data/lib/action_view/component/test_case.rb +9 -0
- data/lib/action_view/component/test_helpers.rb +17 -0
- data/lib/rails/generators/component/USAGE +13 -0
- data/lib/rails/generators/component/component_generator.rb +44 -0
- data/lib/rails/generators/component/templates/component.rb.tt +5 -0
- data/lib/rails/generators/erb/component_generator.rb +21 -0
- data/lib/rails/generators/erb/templates/component.html.erb.tt +1 -0
- data/lib/rails/generators/haml/component_generator.rb +21 -0
- data/lib/rails/generators/haml/templates/component.html.haml.tt +1 -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/slim/component_generator.rb +21 -0
- data/lib/rails/generators/slim/templates/component.html.slim.tt +1 -0
- data/lib/rails/generators/test_unit/component_generator.rb +20 -0
- data/lib/rails/generators/test_unit/templates/component_test.rb.tt +10 -0
- data/lib/railties/lib/rails.rb +5 -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/view_component.rb +30 -0
- data/lib/view_component/base.rb +279 -0
- data/lib/view_component/conversion.rb +9 -0
- data/lib/view_component/engine.rb +65 -0
- data/lib/view_component/preview.rb +78 -0
- data/lib/view_component/previewable.rb +25 -0
- data/lib/view_component/render_monkey_patch.rb +31 -0
- data/lib/view_component/rendering_monkey_patch.rb +13 -0
- data/lib/view_component/template_error.rb +9 -0
- data/lib/view_component/test_case.rb +9 -0
- data/lib/view_component/test_helpers.rb +39 -0
- data/lib/view_component/version.rb +11 -0
- data/script/bootstrap +6 -0
- data/script/console +8 -0
- data/script/install +6 -0
- data/script/release +6 -0
- data/script/test +6 -0
- data/view_component.gemspec +46 -0
- metadata +226 -0
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
9
|
+
level of experience, education, socio-economic status, nationality, personal
|
10
|
+
appearance, race, religion, or sexual identity and orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies within all project spaces, and it also applies when
|
49
|
+
an individual is representing the project or its community in public spaces.
|
50
|
+
Examples of representing a project or community include using an official
|
51
|
+
project e-mail address, posting via an official social media account, or acting
|
52
|
+
as an appointed representative at an online or offline event. Representation of
|
53
|
+
a project may be further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at opensource@github.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
72
|
+
|
73
|
+
[homepage]: https://www.contributor-covenant.org
|
74
|
+
|
75
|
+
For answers to common questions about this code of conduct, see
|
76
|
+
https://www.contributor-covenant.org/faq
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
[fork]: https://github.com/github/view_component/fork
|
4
|
+
[pr]: https://github.com/github/view_component/compare
|
5
|
+
[style]: https://github.com/styleguide/ruby
|
6
|
+
[code-of-conduct]: CODE_OF_CONDUCT.md
|
7
|
+
|
8
|
+
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
|
9
|
+
|
10
|
+
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.txt).
|
11
|
+
|
12
|
+
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
|
13
|
+
|
14
|
+
## Submitting a pull request
|
15
|
+
|
16
|
+
0. [Fork][fork] and clone the repository
|
17
|
+
0. Configure and install the dependencies: `bundle`
|
18
|
+
0. Make sure the tests pass on your machine: `bundle exec rake`
|
19
|
+
0. Create a new branch: `git checkout -b my-branch-name`
|
20
|
+
0. Make your change, add tests, and make sure the tests still pass
|
21
|
+
0. Add an entry to the top of `CHANGELOG.md` for your changes
|
22
|
+
0. If it's your first time contributing, add yourself to the contributors at the bottom of `README.md`
|
23
|
+
0. Push to your fork and [submit a pull request][pr]
|
24
|
+
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
25
|
+
|
26
|
+
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
27
|
+
|
28
|
+
- Write tests.
|
29
|
+
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
30
|
+
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
31
|
+
|
32
|
+
## Releasing
|
33
|
+
|
34
|
+
If you are the current maintainer of this gem:
|
35
|
+
|
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`. Try to adhere to SemVer.
|
38
|
+
1. Add version heading/entries to `CHANGELOG.md`.
|
39
|
+
1. Make sure your local dependencies are up to date: `bundle`
|
40
|
+
1. Ensure that tests are green: `bundle exec rake`
|
41
|
+
1. Make a PR to github/view_component.
|
42
|
+
1. Build a local gem: `gem build actionview-component.gemspec`
|
43
|
+
1. Merge github/view_component PR
|
44
|
+
1. Tag and push: `git tag vx.xx.xx; git push --tags`
|
45
|
+
1. Create a GitHub release with the pushed tag (https://github.com/github/view_component/releases/new) and populate it with a list of the commits from `git log --pretty=format:"- %s" --reverse refs/tags/[OLD TAG]...refs/tags/[NEW TAG]`
|
46
|
+
1. Push to rubygems.org -- `gem push actionview-component-VERSION.gem`
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
actionview-component (1.16.0)
|
5
|
+
capybara (>= 3)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actioncable (6.0.0)
|
11
|
+
actionpack (= 6.0.0)
|
12
|
+
nio4r (~> 2.0)
|
13
|
+
websocket-driver (>= 0.6.1)
|
14
|
+
actionmailbox (6.0.0)
|
15
|
+
actionpack (= 6.0.0)
|
16
|
+
activejob (= 6.0.0)
|
17
|
+
activerecord (= 6.0.0)
|
18
|
+
activestorage (= 6.0.0)
|
19
|
+
activesupport (= 6.0.0)
|
20
|
+
mail (>= 2.7.1)
|
21
|
+
actionmailer (6.0.0)
|
22
|
+
actionpack (= 6.0.0)
|
23
|
+
actionview (= 6.0.0)
|
24
|
+
activejob (= 6.0.0)
|
25
|
+
mail (~> 2.5, >= 2.5.4)
|
26
|
+
rails-dom-testing (~> 2.0)
|
27
|
+
actionpack (6.0.0)
|
28
|
+
actionview (= 6.0.0)
|
29
|
+
activesupport (= 6.0.0)
|
30
|
+
rack (~> 2.0)
|
31
|
+
rack-test (>= 0.6.3)
|
32
|
+
rails-dom-testing (~> 2.0)
|
33
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
34
|
+
actiontext (6.0.0)
|
35
|
+
actionpack (= 6.0.0)
|
36
|
+
activerecord (= 6.0.0)
|
37
|
+
activestorage (= 6.0.0)
|
38
|
+
activesupport (= 6.0.0)
|
39
|
+
nokogiri (>= 1.8.5)
|
40
|
+
actionview (6.0.0)
|
41
|
+
activesupport (= 6.0.0)
|
42
|
+
builder (~> 3.1)
|
43
|
+
erubi (~> 1.4)
|
44
|
+
rails-dom-testing (~> 2.0)
|
45
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
46
|
+
activejob (6.0.0)
|
47
|
+
activesupport (= 6.0.0)
|
48
|
+
globalid (>= 0.3.6)
|
49
|
+
activemodel (6.0.0)
|
50
|
+
activesupport (= 6.0.0)
|
51
|
+
activerecord (6.0.0)
|
52
|
+
activemodel (= 6.0.0)
|
53
|
+
activesupport (= 6.0.0)
|
54
|
+
activestorage (6.0.0)
|
55
|
+
actionpack (= 6.0.0)
|
56
|
+
activejob (= 6.0.0)
|
57
|
+
activerecord (= 6.0.0)
|
58
|
+
marcel (~> 0.3.1)
|
59
|
+
activesupport (6.0.0)
|
60
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
61
|
+
i18n (>= 0.7, < 2)
|
62
|
+
minitest (~> 5.1)
|
63
|
+
tzinfo (~> 1.1)
|
64
|
+
zeitwerk (~> 2.1, >= 2.1.8)
|
65
|
+
addressable (2.7.0)
|
66
|
+
public_suffix (>= 2.0.2, < 5.0)
|
67
|
+
ast (2.4.0)
|
68
|
+
better_html (1.0.14)
|
69
|
+
actionview (>= 4.0)
|
70
|
+
activesupport (>= 4.0)
|
71
|
+
ast (~> 2.0)
|
72
|
+
erubi (~> 1.4)
|
73
|
+
html_tokenizer (~> 0.0.6)
|
74
|
+
parser (>= 2.4)
|
75
|
+
smart_properties
|
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)
|
85
|
+
concurrent-ruby (1.1.5)
|
86
|
+
crass (1.0.5)
|
87
|
+
erubi (1.8.0)
|
88
|
+
globalid (0.4.2)
|
89
|
+
activesupport (>= 4.2.0)
|
90
|
+
haml (5.1.2)
|
91
|
+
temple (>= 0.8.0)
|
92
|
+
tilt
|
93
|
+
html_tokenizer (0.0.7)
|
94
|
+
i18n (1.6.0)
|
95
|
+
concurrent-ruby (~> 1.0)
|
96
|
+
jaro_winkler (1.5.3)
|
97
|
+
loofah (2.3.1)
|
98
|
+
crass (~> 1.0.2)
|
99
|
+
nokogiri (>= 1.5.9)
|
100
|
+
mail (2.7.1)
|
101
|
+
mini_mime (>= 0.1.1)
|
102
|
+
marcel (0.3.3)
|
103
|
+
mimemagic (~> 0.3.2)
|
104
|
+
method_source (0.9.2)
|
105
|
+
mimemagic (0.3.3)
|
106
|
+
mini_mime (1.0.2)
|
107
|
+
mini_portile2 (2.4.0)
|
108
|
+
minitest (5.1.0)
|
109
|
+
nio4r (2.5.2)
|
110
|
+
nokogiri (1.10.8)
|
111
|
+
mini_portile2 (~> 2.4.0)
|
112
|
+
parallel (1.17.0)
|
113
|
+
parser (2.6.3.0)
|
114
|
+
ast (~> 2.4.0)
|
115
|
+
public_suffix (4.0.3)
|
116
|
+
rack (2.0.8)
|
117
|
+
rack-test (1.1.0)
|
118
|
+
rack (>= 1.0, < 3)
|
119
|
+
rails (6.0.0)
|
120
|
+
actioncable (= 6.0.0)
|
121
|
+
actionmailbox (= 6.0.0)
|
122
|
+
actionmailer (= 6.0.0)
|
123
|
+
actionpack (= 6.0.0)
|
124
|
+
actiontext (= 6.0.0)
|
125
|
+
actionview (= 6.0.0)
|
126
|
+
activejob (= 6.0.0)
|
127
|
+
activemodel (= 6.0.0)
|
128
|
+
activerecord (= 6.0.0)
|
129
|
+
activestorage (= 6.0.0)
|
130
|
+
activesupport (= 6.0.0)
|
131
|
+
bundler (>= 1.3.0)
|
132
|
+
railties (= 6.0.0)
|
133
|
+
sprockets-rails (>= 2.0.0)
|
134
|
+
rails-dom-testing (2.0.3)
|
135
|
+
activesupport (>= 4.2.0)
|
136
|
+
nokogiri (>= 1.6)
|
137
|
+
rails-html-sanitizer (1.2.0)
|
138
|
+
loofah (~> 2.2, >= 2.2.2)
|
139
|
+
railties (6.0.0)
|
140
|
+
actionpack (= 6.0.0)
|
141
|
+
activesupport (= 6.0.0)
|
142
|
+
method_source
|
143
|
+
rake (>= 0.8.7)
|
144
|
+
thor (>= 0.20.3, < 2.0)
|
145
|
+
rainbow (3.0.0)
|
146
|
+
rake (13.0.1)
|
147
|
+
regexp_parser (1.7.0)
|
148
|
+
rubocop (0.74.0)
|
149
|
+
jaro_winkler (~> 1.5.1)
|
150
|
+
parallel (~> 1.10)
|
151
|
+
parser (>= 2.6)
|
152
|
+
rainbow (>= 2.2.2, < 4.0)
|
153
|
+
ruby-progressbar (~> 1.7)
|
154
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
155
|
+
rubocop-github (0.13.0)
|
156
|
+
rubocop (~> 0.70)
|
157
|
+
rubocop-performance (~> 1.3.0)
|
158
|
+
rubocop-performance (1.3.0)
|
159
|
+
rubocop (>= 0.68.0)
|
160
|
+
ruby-progressbar (1.10.1)
|
161
|
+
slim (4.0.1)
|
162
|
+
temple (>= 0.7.6, < 0.9)
|
163
|
+
tilt (>= 2.0.6, < 2.1)
|
164
|
+
smart_properties (1.15.0)
|
165
|
+
sprockets (3.7.2)
|
166
|
+
concurrent-ruby (~> 1.0)
|
167
|
+
rack (> 1, < 3)
|
168
|
+
sprockets-rails (3.2.1)
|
169
|
+
actionpack (>= 4.0)
|
170
|
+
activesupport (>= 4.0)
|
171
|
+
sprockets (>= 3.0.0)
|
172
|
+
temple (0.8.1)
|
173
|
+
thor (0.20.3)
|
174
|
+
thread_safe (0.3.6)
|
175
|
+
tilt (2.0.9)
|
176
|
+
tzinfo (1.2.5)
|
177
|
+
thread_safe (~> 0.1)
|
178
|
+
unicode-display_width (1.6.0)
|
179
|
+
websocket-driver (0.7.1)
|
180
|
+
websocket-extensions (>= 0.1.0)
|
181
|
+
websocket-extensions (0.1.4)
|
182
|
+
xpath (3.2.0)
|
183
|
+
nokogiri (~> 1.8)
|
184
|
+
zeitwerk (2.1.10)
|
185
|
+
|
186
|
+
PLATFORMS
|
187
|
+
ruby
|
188
|
+
|
189
|
+
DEPENDENCIES
|
190
|
+
actionview-component!
|
191
|
+
better_html (~> 1)
|
192
|
+
bundler (>= 1.14)
|
193
|
+
haml (~> 5)
|
194
|
+
minitest (= 5.1.0)
|
195
|
+
rails (= 6.0.0)
|
196
|
+
rake (~> 13.0)
|
197
|
+
rubocop (= 0.74)
|
198
|
+
rubocop-github (~> 0.13.0)
|
199
|
+
slim (~> 4.0)
|
200
|
+
|
201
|
+
BUNDLED WITH
|
202
|
+
1.17.3
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 GitHub
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,460 @@
|
|
1
|
+
# ViewComponent
|
2
|
+
A view component framework for Rails.
|
3
|
+
|
4
|
+
**Current Status**: Used in production at GitHub. Because of this, all changes will be thoroughly vetted, which could slow down the process of contributing. We will do our best to actively communicate status of pull requests with any contributors. If you have any substantial changes that you would like to make, it would be great to first [open an issue](http://github.com/github/view_component/issues/new) to discuss them with us.
|
5
|
+
|
6
|
+
## Migration in progress
|
7
|
+
|
8
|
+
This gem is in the process of a name / API change from `ActionView::Component` to `ViewComponent`, see https://github.com/github/view_component/issues/206.
|
9
|
+
|
10
|
+
### What's changing in the migration
|
11
|
+
|
12
|
+
1. `ActionView::Component::Base` is now `ViewComponent::Base`.
|
13
|
+
1. Components can only be rendered with `render(MyComponent.new)` syntax.
|
14
|
+
1. Validations are no longer supported by default.
|
15
|
+
|
16
|
+
### How to migrate to ViewComponent
|
17
|
+
|
18
|
+
1. In `application.rb`, require `view_component/engine`
|
19
|
+
1. Update components to inherit from `ViewComponent::Base`.
|
20
|
+
1. Update component tests to inherit from `ViewComponent::TestCase`.
|
21
|
+
1. Update component previews to inherit from `ViewComponent::Preview`.
|
22
|
+
1. Include `ViewComponent::TestHelpers` in the appropriate test helper file.
|
23
|
+
|
24
|
+
## Roadmap
|
25
|
+
|
26
|
+
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.
|
27
|
+
|
28
|
+
This gem includes a backport of those changes for Rails `5.0.0` through `6.1.0.alpha`.
|
29
|
+
|
30
|
+
## Design philosophy
|
31
|
+
|
32
|
+
This library is designed to integrate as seamlessly as possible with Rails, with the [least surprise](https://www.artima.com/intv/ruby4.html).
|
33
|
+
|
34
|
+
## Compatibility
|
35
|
+
|
36
|
+
`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`/`master`.
|
37
|
+
|
38
|
+
## Installation
|
39
|
+
|
40
|
+
In `Gemfile`, add:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
gem "actionview-component"
|
44
|
+
```
|
45
|
+
|
46
|
+
In `config/application.rb`, add:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
require "view_component/engine"
|
50
|
+
```
|
51
|
+
|
52
|
+
## Guide
|
53
|
+
|
54
|
+
### What are components?
|
55
|
+
|
56
|
+
`ViewComponent`s are Ruby classes that are used to render views. They take data as input and return output-safe HTML. Think of them as an evolution of the presenter/decorator/view model pattern, inspired by [React Components](https://reactjs.org/docs/react-component.html).
|
57
|
+
|
58
|
+
Components are most effective in cases where view code is reused or benefits from being tested directly.
|
59
|
+
|
60
|
+
### Why should I use components?
|
61
|
+
|
62
|
+
#### Testing
|
63
|
+
|
64
|
+
Rails encourages testing views with integration tests. This discourages us from testing views thoroughly, due to the overhead of exercising the routing and controller layers in addition to the view.
|
65
|
+
|
66
|
+
For partials, this means being tested for each view they are included in, reducing the benefit of reusing them.
|
67
|
+
|
68
|
+
`ViewComponent`s can be unit-tested. In the GitHub codebase, our component unit tests run in around 25 milliseconds, compared to about six seconds for integration tests.
|
69
|
+
|
70
|
+
#### Data Flow
|
71
|
+
|
72
|
+
Unlike a method declaration on an object, views do not declare the values they are expected to receive, making it hard to figure out what context is necessary to render them. This often leads to subtle bugs when reusing a view in different contexts.
|
73
|
+
|
74
|
+
By clearly defining the context necessary to render a `ViewComponent`, they're easier to reuse than partials.
|
75
|
+
|
76
|
+
#### Standards
|
77
|
+
|
78
|
+
Views often fail basic Ruby code quality standards: long methods, deep conditional nesting, and mystery guests abound.
|
79
|
+
|
80
|
+
`ViewComponent`s are Ruby objects, making it easy to follow code quality standards.
|
81
|
+
|
82
|
+
#### Code Coverage
|
83
|
+
|
84
|
+
Many common Ruby code coverage tools cannot properly handle coverage of views, making it difficult to audit how thorough tests are and leading to missing coverage in test suites.
|
85
|
+
|
86
|
+
`ViewComponent` is at least partially compatible with code coverage tools, such as SimpleCov.
|
87
|
+
|
88
|
+
### Building components
|
89
|
+
|
90
|
+
#### Conventions
|
91
|
+
|
92
|
+
Components are subclasses of `ViewComponent::Base` and live in `app/components`. It's recommended to create an `ApplicationComponent` that is a subclass of `ViewComponent::Base` and inherit from that instead.
|
93
|
+
|
94
|
+
Component class names end in -`Component`.
|
95
|
+
|
96
|
+
Component module names are plural, as they are for controllers. (`Users::AvatarComponent`)
|
97
|
+
|
98
|
+
Content passed to a `ViewComponent` as a block is captured and assigned to the `content` accessor.
|
99
|
+
|
100
|
+
#### Quick start
|
101
|
+
|
102
|
+
Use the component generator to create a new `ViewComponent`.
|
103
|
+
|
104
|
+
The generator accepts the component name and the list of accepted properties as arguments:
|
105
|
+
|
106
|
+
```bash
|
107
|
+
bin/rails generate component Example title content
|
108
|
+
invoke test_unit
|
109
|
+
create test/components/example_component_test.rb
|
110
|
+
create app/components/example_component.rb
|
111
|
+
create app/components/example_component.html.erb
|
112
|
+
```
|
113
|
+
|
114
|
+
`ViewComponent` includes template generators for the `erb`, `haml`, and `slim` template engines and will use the template engine specified in the Rails configuration (`config.generators.template_engine`) by default.
|
115
|
+
|
116
|
+
The template engine can also be passed as an option to the generator:
|
117
|
+
|
118
|
+
```bash
|
119
|
+
bin/rails generate component Example title content --template-engine slim
|
120
|
+
```
|
121
|
+
|
122
|
+
#### Implementation
|
123
|
+
|
124
|
+
A `ViewComponent` is a Ruby file and corresponding template file with the same base name:
|
125
|
+
|
126
|
+
`app/components/test_component.rb`:
|
127
|
+
```ruby
|
128
|
+
class TestComponent < ViewComponent::Base
|
129
|
+
def initialize(title:)
|
130
|
+
@title = title
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
`app/components/test_component.html.erb`:
|
136
|
+
```erb
|
137
|
+
<span title="<%= @title %>"><%= content %></span>
|
138
|
+
```
|
139
|
+
|
140
|
+
Which is rendered in a view as:
|
141
|
+
|
142
|
+
```erb
|
143
|
+
<%= render(TestComponent.new(title: "my title")) do %>
|
144
|
+
Hello, World!
|
145
|
+
<% end %>
|
146
|
+
```
|
147
|
+
|
148
|
+
Which returns:
|
149
|
+
|
150
|
+
```html
|
151
|
+
<span title="my title">Hello, World!</span>
|
152
|
+
```
|
153
|
+
|
154
|
+
`ViewComponent` requires the presence of an `initialize` method in each component.
|
155
|
+
|
156
|
+
#### Content Areas
|
157
|
+
|
158
|
+
A component can declare additional content areas to be rendered in the component. For example:
|
159
|
+
|
160
|
+
`app/components/modal_component.rb`:
|
161
|
+
```ruby
|
162
|
+
class ModalComponent < ViewComponent::Base
|
163
|
+
with_content_areas :header, :body
|
164
|
+
|
165
|
+
def initialize(*)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
`app/components/modal_component.html.erb`:
|
171
|
+
```erb
|
172
|
+
<div class="modal">
|
173
|
+
<div class="header"><%= header %></div>
|
174
|
+
<div class="body"><%= body %></div>
|
175
|
+
</div>
|
176
|
+
```
|
177
|
+
|
178
|
+
Which is rendered in a view as:
|
179
|
+
|
180
|
+
```erb
|
181
|
+
<%= render(ModalComponent.new) do |component| %>
|
182
|
+
<% component.with(:header) do %>
|
183
|
+
Hello Jane
|
184
|
+
<% end %>
|
185
|
+
<% component.with(:body) do %>
|
186
|
+
<p>Have a great day.</p>
|
187
|
+
<% end %>
|
188
|
+
<% end %>
|
189
|
+
```
|
190
|
+
|
191
|
+
Which returns:
|
192
|
+
|
193
|
+
```html
|
194
|
+
<div class="modal">
|
195
|
+
<div class="header">Hello Jane</div>
|
196
|
+
<div class="body"><p>Have a great day.</p></div>
|
197
|
+
</div>
|
198
|
+
```
|
199
|
+
|
200
|
+
### Inline Component
|
201
|
+
|
202
|
+
A component can be rendered without any template file as well.
|
203
|
+
|
204
|
+
`app/components/inline_component.rb`:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
class InlineComponent < ViewComponent::Base
|
208
|
+
def call
|
209
|
+
if active?
|
210
|
+
link_to "Cancel integration", integration_path, method: :delete
|
211
|
+
else
|
212
|
+
link_to "Integrate now!", integration_path
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
```
|
217
|
+
|
218
|
+
### Conditional Rendering
|
219
|
+
|
220
|
+
Components can implement a `#render?` method to determine if they should be rendered.
|
221
|
+
|
222
|
+
For example, given a component that displays a banner to users who haven't confirmed their email address, the logic for whether to render the banner would need to go in either the component template:
|
223
|
+
|
224
|
+
`app/components/confirm_email_component.html.erb`
|
225
|
+
```
|
226
|
+
<% if user.requires_confirmation? %>
|
227
|
+
<div class="alert">
|
228
|
+
Please confirm your email address.
|
229
|
+
</div>
|
230
|
+
<% end %>
|
231
|
+
```
|
232
|
+
|
233
|
+
or the view that renders the component:
|
234
|
+
|
235
|
+
`app/views/_banners.html.erb`
|
236
|
+
```erb
|
237
|
+
<% if current_user.requires_confirmation? %>
|
238
|
+
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
239
|
+
<% end %>
|
240
|
+
```
|
241
|
+
|
242
|
+
Instead, the `#render?` hook expresses this logic in the Ruby class, simplifying the view:
|
243
|
+
|
244
|
+
`app/components/confirm_email_component.rb`
|
245
|
+
```ruby
|
246
|
+
class ConfirmEmailComponent < ViewComponent::Base
|
247
|
+
def initialize(user:)
|
248
|
+
@user = user
|
249
|
+
end
|
250
|
+
|
251
|
+
def render?
|
252
|
+
@user.requires_confirmation?
|
253
|
+
end
|
254
|
+
end
|
255
|
+
```
|
256
|
+
|
257
|
+
`app/components/confirm_email_component.html.erb`
|
258
|
+
```
|
259
|
+
<div class="banner">
|
260
|
+
Please confirm your email address.
|
261
|
+
</div>
|
262
|
+
```
|
263
|
+
|
264
|
+
`app/views/_banners.html.erb`
|
265
|
+
```erb
|
266
|
+
<%= render(ConfirmEmailComponent.new(user: current_user)) %>
|
267
|
+
```
|
268
|
+
|
269
|
+
To assert that a component has not been rendered, use `refute_component_rendered` from `ViewComponent::TestHelpers`.
|
270
|
+
|
271
|
+
### Testing
|
272
|
+
|
273
|
+
Unit test components directly, using the `render_inline` test helper and Capybara matchers:
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
require "view_component/test_case"
|
277
|
+
|
278
|
+
class MyComponentTest < ViewComponent::TestCase
|
279
|
+
test "render component" do
|
280
|
+
render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
|
281
|
+
|
282
|
+
assert_selector("span[title='my title']", "Hello, World!")
|
283
|
+
end
|
284
|
+
end
|
285
|
+
```
|
286
|
+
|
287
|
+
#### Action Pack Variants
|
288
|
+
|
289
|
+
Use the `with_variant` helper to test specific variants:
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
test "render component for tablet" do
|
293
|
+
with_variant :tablet do
|
294
|
+
render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }
|
295
|
+
|
296
|
+
assert_selector("span[title='my title']", "Hello, tablets!")
|
297
|
+
end
|
298
|
+
end
|
299
|
+
```
|
300
|
+
|
301
|
+
### Previewing Components
|
302
|
+
`ViewComponent::Preview`, like `ActionMailer::Preview`, provides a way to preview components in isolation:
|
303
|
+
|
304
|
+
`test/components/previews/test_component_preview.rb`
|
305
|
+
```ruby
|
306
|
+
class TestComponentPreview < ViewComponent::Preview
|
307
|
+
def with_default_title
|
308
|
+
render(TestComponent.new(title: "Test component default"))
|
309
|
+
end
|
310
|
+
|
311
|
+
def with_long_title
|
312
|
+
render(TestComponent.new(title: "This is a really long title to see how the component renders this"))
|
313
|
+
end
|
314
|
+
|
315
|
+
def with_content_block
|
316
|
+
render(TestComponent.new(title: "This component accepts a block of content") do
|
317
|
+
tag.div do
|
318
|
+
content_tag(:span, "Hello")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
```
|
324
|
+
|
325
|
+
Which generates <http://localhost:3000/rails/components/test_component/with_default_title>,
|
326
|
+
<http://localhost:3000/rails/components/test_component/with_long_title>,
|
327
|
+
and <http://localhost:3000/rails/components/test_component/with_content_block>.
|
328
|
+
|
329
|
+
The `ViewComponent::Preview` base class includes
|
330
|
+
[`ActionView::Helpers::TagHelper`][tag-helper], which provides the [`tag`][tag]
|
331
|
+
and [`content_tag`][content_tag] view helper methods.
|
332
|
+
|
333
|
+
[tag-helper]: https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html
|
334
|
+
[tag]: https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag
|
335
|
+
[content_tag]: https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag
|
336
|
+
|
337
|
+
Previews default to the application layout, but can be overridden:
|
338
|
+
|
339
|
+
`test/components/previews/test_component_preview.rb`
|
340
|
+
```ruby
|
341
|
+
class TestComponentPreview < ViewComponent::Preview
|
342
|
+
layout "admin"
|
343
|
+
|
344
|
+
...
|
345
|
+
end
|
346
|
+
```
|
347
|
+
|
348
|
+
Preview classes live in `test/components/previews`, can be configured using the `preview_path` option.
|
349
|
+
|
350
|
+
To use `lib/component_previews`:
|
351
|
+
|
352
|
+
`config/application.rb`
|
353
|
+
```ruby
|
354
|
+
config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
|
355
|
+
```
|
356
|
+
|
357
|
+
#### Configuring TestController
|
358
|
+
|
359
|
+
Component tests and previews assume the existence of an `ApplicationController` class, be can beconfigured using the `test_controller` option:
|
360
|
+
|
361
|
+
`config/application.rb`
|
362
|
+
```ruby
|
363
|
+
config.action_view_component.test_controller = "BaseController"
|
364
|
+
```
|
365
|
+
|
366
|
+
### Setting up RSpec
|
367
|
+
|
368
|
+
To use RSpec, add the following:
|
369
|
+
|
370
|
+
`spec/rails_helper.rb`
|
371
|
+
```ruby
|
372
|
+
require "view_component/test_helpers"
|
373
|
+
|
374
|
+
RSpec.configure do |config|
|
375
|
+
config.include ViewComponent::TestHelpers, type: :component
|
376
|
+
end
|
377
|
+
```
|
378
|
+
|
379
|
+
Specs created by the generator have access to test helpers like `render_inline`.
|
380
|
+
|
381
|
+
To use component previews:
|
382
|
+
|
383
|
+
`config/application.rb`
|
384
|
+
```ruby
|
385
|
+
config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
|
386
|
+
```
|
387
|
+
|
388
|
+
## Frequently Asked Questions
|
389
|
+
|
390
|
+
### Can I use other templating languages besides ERB?
|
391
|
+
|
392
|
+
Yes. This gem is tested against ERB, Haml, and Slim, but it should support most Rails template handlers.
|
393
|
+
|
394
|
+
### What happened to inline templates?
|
395
|
+
|
396
|
+
Inline templates have been removed (for now) due to concerns raised by [@soutaro](https://github.com/soutaro) regarding compatibility with the type systems being developed for Ruby 3.
|
397
|
+
|
398
|
+
### Isn't this just like X library?
|
399
|
+
|
400
|
+
`ViewComponent` is far from a novel idea! Popular implementations of view components in Ruby include, but are not limited to:
|
401
|
+
|
402
|
+
- [trailblazer/cells](https://github.com/trailblazer/cells)
|
403
|
+
- [dry-rb/dry-view](https://github.com/dry-rb/dry-view)
|
404
|
+
- [komposable/komponent](https://github.com/komposable/komponent)
|
405
|
+
- [activeadmin/arbre](https://github.com/activeadmin/arbre)
|
406
|
+
|
407
|
+
## Resources
|
408
|
+
|
409
|
+
- [Rethinking the View Layer with Components, RailsConf 2019](https://www.youtube.com/watch?v=y5Z5a6QdA-M)
|
410
|
+
- [Introducing ActionView::Component with Joel Hawksley, Ruby on Rails Podcast](http://5by5.tv/rubyonrails/276)
|
411
|
+
- [Rails to Introduce View Components, Dev.to](https://dev.to/andy/rails-to-introduce-view-components-3ome)
|
412
|
+
- [ActionView::Components in Rails 6.1, Drifting Ruby](https://www.driftingruby.com/episodes/actionview-components-in-rails-6-1)
|
413
|
+
- [Demo repository, view-component-demo](https://github.com/joelhawksley/view-component-demo)
|
414
|
+
|
415
|
+
## Contributing
|
416
|
+
|
417
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/github/view_component. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
|
418
|
+
|
419
|
+
## Contributors
|
420
|
+
|
421
|
+
`actionview-component` is built by:
|
422
|
+
|
423
|
+
|<img src="https://avatars.githubusercontent.com/joelhawksley?s=256" alt="joelhawksley" width="128" />|<img src="https://avatars.githubusercontent.com/tenderlove?s=256" alt="tenderlove" width="128" />|<img src="https://avatars.githubusercontent.com/jonspalmer?s=256" alt="jonspalmer" width="128" />|<img src="https://avatars.githubusercontent.com/juanmanuelramallo?s=256" alt="juanmanuelramallo" width="128" />|<img src="https://avatars.githubusercontent.com/vinistock?s=256" alt="vinistock" width="128" />|
|
424
|
+
|:---:|:---:|:---:|:---:|:---:|
|
425
|
+
|@joelhawksley|@tenderlove|@jonspalmer|@juanmanuelramallo|@vinistock|
|
426
|
+
|Denver|Seattle|Boston||Toronto|
|
427
|
+
|
428
|
+
|<img src="https://avatars.githubusercontent.com/metade?s=256" alt="metade" width="128" />|<img src="https://avatars.githubusercontent.com/asgerb?s=256" alt="asgerb" width="128" />|<img src="https://avatars.githubusercontent.com/xronos-i-am?s=256" alt="xronos-i-am" width="128" />|<img src="https://avatars.githubusercontent.com/dylnclrk?s=256" alt="dylnclrk" width="128" />|<img src="https://avatars.githubusercontent.com/kaspermeyer?s=256" alt="kaspermeyer" width="128" />|
|
429
|
+
|:---:|:---:|:---:|:---:|:---:|
|
430
|
+
|@metade|@asgerb|@xronos-i-am|@dylnclrk|@kaspermeyer|
|
431
|
+
|London|Copenhagen|Russia, Kirov|Berkeley, CA|Denmark|
|
432
|
+
|
433
|
+
|<img src="https://avatars.githubusercontent.com/rdavid1099?s=256" alt="rdavid1099" width="128" />|<img src="https://avatars.githubusercontent.com/kylefox?s=256" alt="kylefox" width="128" />|<img src="https://avatars.githubusercontent.com/traels?s=256" alt="traels" width="128" />|<img src="https://avatars.githubusercontent.com/rainerborene?s=256" alt="rainerborene" width="128" />|<img src="https://avatars.githubusercontent.com/jcoyne?s=256" alt="jcoyne" width="128" />|
|
434
|
+
|:---:|:---:|:---:|:---:|:---:|
|
435
|
+
|@rdavid1099|@kylefox|@traels|@rainerborene|@jcoyne|
|
436
|
+
|Los Angeles|Edmonton|Odense, Denmark|Brazil|Minneapolis|
|
437
|
+
|
438
|
+
|<img src="https://avatars.githubusercontent.com/elia?s=256" alt="elia" width="128" />|<img src="https://avatars.githubusercontent.com/cesariouy?s=256" alt="cesariouy" width="128" />|<img src="https://avatars.githubusercontent.com/spdawson?s=256" alt="spdawson" width="128" />|<img src="https://avatars.githubusercontent.com/rmacklin?s=256" alt="rmacklin" width="128" />|<img src="https://avatars.githubusercontent.com/michaelem?s=256" alt="michaelem" width="128" />|
|
439
|
+
|:---:|:---:|:---:|:---:|:---:|
|
440
|
+
|@elia|@cesariouy|@spdawson|@rmacklin|@michaelem|
|
441
|
+
|Milan||United Kingdom||Berlin|
|
442
|
+
|
443
|
+
|<img src="https://avatars.githubusercontent.com/mellowfish?s=256" alt="mellowfish" width="128" />|<img src="https://avatars.githubusercontent.com/horacio?s=256" alt="horacio" width="128" />|<img src="https://avatars.githubusercontent.com/dukex?s=256" alt="dukex" width="128" />|<img src="https://avatars.githubusercontent.com/dark-panda?s=256" alt="dark-panda" width="128" />|<img src="https://avatars.githubusercontent.com/smashwilson?s=256" alt="smashwilson" width="128" />|
|
444
|
+
|:---:|:---:|:---:|:---:|:---:|
|
445
|
+
|@mellowfish|@horacio|@dukex|@dark-panda|@smashwilson|
|
446
|
+
|Spring Hill, TN|Buenos Aires|São Paulo||Gambrills, MD|
|
447
|
+
|
448
|
+
|<img src="https://avatars.githubusercontent.com/blakewilliams?s=256" alt="blakewilliams" width="128" />|
|
449
|
+
|:---:|
|
450
|
+
|@blakewilliams|
|
451
|
+
|Boston, MA|
|
452
|
+
|
453
|
+
|<img src="https://avatars.githubusercontent.com/seanpdoyle?s=256" alt="seanpdoyle" width="128" />|
|
454
|
+
|:---:|
|
455
|
+
|@seanpdoyle|
|
456
|
+
|New York, NY|
|
457
|
+
|
458
|
+
## License
|
459
|
+
|
460
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|