actionview-component 1.7.0 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE +5 -8
  3. data/.github/PULL_REQUEST_TEMPLATE +2 -4
  4. data/.github/workflows/ruby_on_rails.yml +6 -2
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.md +150 -0
  8. data/CONTRIBUTING.md +10 -19
  9. data/Gemfile.lock +20 -5
  10. data/README.md +204 -248
  11. data/actionview-component.gemspec +10 -8
  12. data/{lib/railties/lib → app/controllers}/rails/components_controller.rb +18 -8
  13. data/docs/case-studies/jellyswitch.md +76 -0
  14. data/lib/action_view/component.rb +1 -20
  15. data/lib/action_view/component/base.rb +2 -246
  16. data/lib/action_view/component/preview.rb +1 -80
  17. data/lib/action_view/component/railtie.rb +1 -64
  18. data/lib/action_view/component/test_case.rb +1 -3
  19. data/lib/action_view/component/test_helpers.rb +1 -19
  20. data/lib/rails/generators/component/component_generator.rb +6 -12
  21. data/lib/rails/generators/component/templates/component.rb.tt +0 -4
  22. data/lib/rails/generators/erb/component_generator.rb +21 -0
  23. data/lib/rails/generators/erb/templates/component.html.erb.tt +1 -0
  24. data/lib/rails/generators/haml/component_generator.rb +21 -0
  25. data/lib/rails/generators/haml/templates/component.html.haml.tt +1 -0
  26. data/lib/rails/generators/rspec/component_generator.rb +1 -1
  27. data/lib/rails/generators/rspec/templates/component_spec.rb.tt +1 -1
  28. data/lib/rails/generators/slim/component_generator.rb +21 -0
  29. data/lib/rails/generators/slim/templates/component.html.slim.tt +1 -0
  30. data/lib/rails/generators/test_unit/component_generator.rb +1 -1
  31. data/lib/rails/generators/test_unit/templates/component_test.rb.tt +3 -3
  32. data/lib/railties/lib/rails.rb +0 -1
  33. data/lib/railties/lib/rails/templates/rails/components/preview.html.erb +1 -1
  34. data/lib/view_component.rb +30 -0
  35. data/lib/view_component/base.rb +279 -0
  36. data/lib/view_component/conversion.rb +9 -0
  37. data/lib/view_component/engine.rb +65 -0
  38. data/lib/view_component/preview.rb +78 -0
  39. data/lib/view_component/previewable.rb +25 -0
  40. data/lib/view_component/render_monkey_patch.rb +31 -0
  41. data/lib/view_component/rendering_monkey_patch.rb +13 -0
  42. data/lib/view_component/template_error.rb +9 -0
  43. data/lib/view_component/test_case.rb +9 -0
  44. data/lib/view_component/test_helpers.rb +39 -0
  45. data/lib/view_component/version.rb +11 -0
  46. data/script/console +1 -1
  47. metadata +48 -21
  48. data/lib/action_view/component/conversion.rb +0 -11
  49. data/lib/action_view/component/previewable.rb +0 -27
  50. data/lib/action_view/component/render_monkey_patch.rb +0 -29
  51. data/lib/action_view/component/template_error.rb +0 -11
  52. data/lib/action_view/component/version.rb +0 -13
  53. data/lib/rails/generators/component/templates/component.html.erb.tt +0 -5
  54. data/lib/railties/lib/rails/component_examples_controller.rb +0 -9
  55. data/lib/railties/lib/rails/templates/rails/examples/show.html.erb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '085434585e1e3fa1493bbc7bd90439b1645015acd66a713a33545c0eb4677dc4'
4
- data.tar.gz: 34ec8d8f390471689fbd1085c94885b74c4f9a1b0b7d9b78650dae506520dd13
3
+ metadata.gz: 24d6994186bb4c3e5f6fb8c628a3d91124cc6bdcbad61b966365c9170ab3b901
4
+ data.tar.gz: 020ab981225e9bb34508583c2aff096b125e43bee8c5ecc935ab52c6a22eb014
5
5
  SHA512:
6
- metadata.gz: 89e3c7d8b6c1d67a29f2ad67a032e424c42f57dd719d5b8cbde01b167385d9d2aeb5feb8fd77fcd9284e0a33da828b935edeebbb758c7cd68951178eea2d3b14
7
- data.tar.gz: bc76f28233c72f7d5ff3a818429e8c763649db3ec46e406b554070cd456b73ae923905ecb94d3b37949ca5c6b0960890594a341e72a9755343462cf4c5c24cff
6
+ metadata.gz: 608b7e73a07c3b24c1fa273f65c1dea5f1e5d993f3db8ff3c3dde2d9d6a207604141849686cd78f4a6ac7a4a6c45567d81fb3eb054c7bccf4802565a3eb83a71
7
+ data.tar.gz: 8376ee07b5534e81b25c6241a0e9da6d02cf13a0b8b3b4036cdb6a062695897efaefd75a779f5d5d7009477ab03524da13f7a52fc557484dfd128cf77927e3f3
@@ -6,17 +6,12 @@
6
6
  ### Motivation
7
7
 
8
8
  <!-- What would you like to do with this feature? Can you provide
9
- context or references to similar behavior in other libraries. -->
9
+ context or references to similar behavior in other libraries? -->
10
10
 
11
-
12
-
13
-
14
-
15
- <!-- **** Filing a Bug Report? Include these sections. **** -->
11
+ <!-- **** Filing a Bug Report? Include these sections: **** -->
16
12
 
17
13
  ### Steps to reproduce
18
- <!-- Provide an series of steps or, better yet, a link to a repo to
19
- demonstrate the bug you've identified. -->
14
+ <!-- Provide a series of steps or, better yet, a link to a repo to demonstrate the bug you've identified. -->
20
15
 
21
16
  ### Expected behavior
22
17
  <!-- Tell us what should happen -->
@@ -28,3 +23,5 @@ demonstrate the bug you've identified. -->
28
23
  **Rails version**:
29
24
 
30
25
  **Ruby version**:
26
+
27
+ **Gem version**:
@@ -1,4 +1,4 @@
1
- <!-- https://github.com/github/actionview-component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
1
+ <!-- See https://github.com/github/view_component/blob/master/CONTRIBUTING.md#submitting-a-pull-request -->
2
2
 
3
3
  ### Summary
4
4
 
@@ -14,6 +14,4 @@ request, mention that information here. This could include
14
14
  benchmarks, or other information.
15
15
 
16
16
  If you are updating any of the CHANGELOG files or are asked to update the
17
- CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file.
18
-
19
- Thanks for contributing to actionview-component! -->
17
+ CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file. -->
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  matrix:
10
10
  rails_version: [5.0.0, 5.2.3, 6.0.0, master]
11
- ruby_version: [2.4.x, 2.5.x, 2.6.x]
11
+ ruby_version: [2.4.x, 2.5.x, 2.6.x, 2.7.x]
12
12
  exclude:
13
13
  - rails_version: master
14
14
  ruby_version: 2.4.x
@@ -20,9 +20,13 @@ jobs:
20
20
  uses: actions/setup-ruby@v1
21
21
  with:
22
22
  ruby-version: ${{ matrix.ruby_version }}
23
+ - name: Update rubygems when testing with Ruby 2.4.x
24
+ if: startsWith(matrix.ruby_version, '2.4')
25
+ run: |
26
+ gem update --system --no-document
23
27
  - name: Build and test with Rake
24
28
  run: |
25
- gem install bundler:1.14.0
29
+ gem install bundler:1.17.3
26
30
  bundle update
27
31
  bundle install --jobs 4 --retry 3
28
32
  bundle exec rake
data/.gitignore CHANGED
@@ -11,6 +11,7 @@
11
11
  /tmp/
12
12
  /test/log/*
13
13
  /test/app/tmp/*
14
+ /test/tmp/*
14
15
 
15
16
  # Used by dotenv library to load environment variables.
16
17
  # .env
@@ -2,3 +2,7 @@ inherit_gem:
2
2
  rubocop-github:
3
3
  - config/default.yml
4
4
  - config/rails.yml
5
+
6
+ AllCops:
7
+ Exclude:
8
+ - "test/tmp/**/*"
@@ -1,3 +1,153 @@
1
+ # master
2
+
3
+ # v1.17.0
4
+
5
+ * Support Ruby 2.4 in CI.
6
+
7
+ *Andrew Mason*
8
+
9
+ * ViewComponent generators do not not prompt for content requirement.
10
+
11
+ *Joel Hawksley*
12
+
13
+ * Add post-install message that gem has been renamed to `view_component`.
14
+
15
+ *Joel Hawksley*
16
+
17
+ # v1.16.0
18
+
19
+ * Add `refute_component_rendered` test helper.
20
+
21
+ *Joel Hawksley*
22
+
23
+ * Check for Rails before invocation.
24
+
25
+ *Dave Paola*
26
+
27
+ * Allow components to be rendered without a template file (aka inline component).
28
+
29
+ *Rainer Borene*
30
+
31
+ # v1.15.0
32
+
33
+ * Re-introduce ActionView::Component::TestHelpers.
34
+
35
+ *Joel Hawksley*
36
+
37
+ * Bypass monkey patch on Rails 6.1 builds.
38
+
39
+ *Joel Hawksley*
40
+
41
+ * Make `ActionView::Helpers::TagHelper` available in Previews.
42
+
43
+ ```ruby
44
+ def with_html_content
45
+ render(MyComponent.new) do
46
+ tag.div do
47
+ content_tag(:span, "Hello")
48
+ end
49
+ end
50
+ end
51
+ ```
52
+
53
+ *Sean Doyle*
54
+
55
+ # v1.14.1
56
+
57
+ * Fix bug where generator created invalid test code.
58
+
59
+ *Joel Hawksley*
60
+
61
+ # v1.14.0
62
+
63
+ * Rename ActionView::Component::Base to ViewComponent::Base
64
+
65
+ *Joel Hawksley*
66
+
67
+ # v1.13.0
68
+
69
+ * Allow components to be rendered inside controllers.
70
+
71
+ *Joel Hawksley*
72
+
73
+ * Improve backtraces from exceptions raised in templates.
74
+
75
+ *Blake Williams*
76
+
77
+ # v1.12.0
78
+
79
+ * Revert: Remove initializer requirement for Ruby 2.7+
80
+
81
+ *Joel Hawksley*
82
+
83
+ * Restructure Railtie into Engine
84
+
85
+ *Sean Doyle*
86
+
87
+ * Allow components to override before_render_check
88
+
89
+ *Joel Hawksley*
90
+
91
+ # v1.11.1
92
+
93
+ * Relax Capybara requirement.
94
+
95
+ *Joel Hawksley*
96
+
97
+ # v1.11.0
98
+
99
+ * Add support for Capybara matchers.
100
+
101
+ *Joel Hawksley*
102
+
103
+ * Add erb, haml, & slim template generators
104
+
105
+ *Asger Behncke Jacobsen*
106
+
107
+ # v1.10.0
108
+
109
+ * Deprecate all `render` syntaxes except for `render(MyComponent.new(foo: :bar))`
110
+
111
+ *Joel Hawksley*
112
+
113
+ # v1.9.0
114
+
115
+ * Remove initializer requirement for Ruby 2.7+
116
+
117
+ *Dylan Clark*
118
+
119
+ # v1.8.1
120
+
121
+ * Run validation checks before calling `#render?`.
122
+
123
+ *Ash Wilson*
124
+
125
+ # v1.8.0
126
+
127
+ * Remove the unneeded ComponentExamplesController and simplify preview rendering.
128
+
129
+ *Jon Palmer*
130
+
131
+ * Add `#render?` hook to easily allow components to be no-ops.
132
+
133
+ *Kyle Fox*
134
+
135
+ * Don't assume ApplicationController exists.
136
+
137
+ *Jon Palmer*
138
+
139
+ * Allow some additional checks to overrided render?
140
+
141
+ *Sergey Malykh*
142
+
143
+ * Fix generator placing namespaced components in the root directory.
144
+
145
+ *Asger Behncke Jacobsen*
146
+
147
+ * Fix cache test.
148
+
149
+ *Sergey Malykh*
150
+
1
151
  # v1.7.0
2
152
 
3
153
  * Simplify validation of templates and compilation.
@@ -1,7 +1,7 @@
1
- ## Contributing
1
+ # Contributing
2
2
 
3
- [fork]: https://github.com/github/actionview-component/fork
4
- [pr]: https://github.com/github/actionview-component/compare
3
+ [fork]: https://github.com/github/view_component/fork
4
+ [pr]: https://github.com/github/view_component/compare
5
5
  [style]: https://github.com/styleguide/ruby
6
6
  [code-of-conduct]: CODE_OF_CONDUCT.md
7
7
 
@@ -15,9 +15,11 @@ Please note that this project is released with a [Contributor Code of Conduct][c
15
15
 
16
16
  0. [Fork][fork] and clone the repository
17
17
  0. Configure and install the dependencies: `bundle`
18
- 0. Make sure the tests pass on your machine: `rake`
18
+ 0. Make sure the tests pass on your machine: `bundle exec rake`
19
19
  0. Create a new branch: `git checkout -b my-branch-name`
20
20
  0. Make your change, add tests, and make sure the tests still pass
21
+ 0. Add an entry to the top of `CHANGELOG.md` for your changes
22
+ 0. If it's your first time contributing, add yourself to the contributors at the bottom of `README.md`
21
23
  0. Push to your fork and [submit a pull request][pr]
22
24
  0. Pat your self on the back and wait for your pull request to be reviewed and merged.
23
25
 
@@ -32,24 +34,13 @@ Here are a few things you can do that will increase the likelihood of your pull
32
34
  If you are the current maintainer of this gem:
33
35
 
34
36
  1. Create a branch for the release: `git checkout -b release-vxx.xx.xx`
35
- 1. Bump gem version in `lib/action_view/component/version.rb`.
37
+ 1. Bump gem version in `lib/action_view/component/version.rb`. Try to adhere to SemVer.
36
38
  1. Add version heading/entries to `CHANGELOG.md`.
37
39
  1. Make sure your local dependencies are up to date: `bundle`
38
40
  1. Ensure that tests are green: `bundle exec rake`
39
- 1. Build a test gem `GEM_VERSION=$(git describe --tags 2>/dev/null | sed 's/-/./g' | sed 's/v//') gem build actionview-component.gemspec`
40
- 1. Test the test gem:
41
- 1. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
42
- 1. Install the new gem locally
43
- 1. Test behavior locally, branch deploy, whatever needs to happen
44
- 1. Make a PR to github/actionview-component.
41
+ 1. Make a PR to github/view_component.
45
42
  1. Build a local gem: `gem build actionview-component.gemspec`
46
- 1. Merge github/actionview-component PR
43
+ 1. Merge github/view_component PR
47
44
  1. Tag and push: `git tag vx.xx.xx; git push --tags`
48
- 1. Create a GitHub release with the pushed tag (https://github.com/github/actionview-component/releases/new) and populate it with a list of the commits from `git log --pretty=format:"- %s" --reverse refs/tags/[OLD TAG]...refs/tags/[NEW TAG]`
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]`
49
46
  1. Push to rubygems.org -- `gem push actionview-component-VERSION.gem`
50
-
51
- ## Resources
52
-
53
- - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
54
- - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
55
- - [GitHub Help](https://help.github.com)
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionview-component (1.7.0)
4
+ actionview-component (1.17.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
@@ -174,11 +189,11 @@ PLATFORMS
174
189
  DEPENDENCIES
175
190
  actionview-component!
176
191
  better_html (~> 1)
177
- bundler (>= 1.14)
192
+ bundler (~> 1.14)
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,111 +1,105 @@
1
- # ActionView::Component
2
- `ActionView::Component` is a framework for building view components in Rails.
1
+ # ViewComponent
2
+ A view component framework for Rails.
3
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/actionview-component/issues/new) to discuss them with us.
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.
5
23
 
6
24
  ## Roadmap
7
25
 
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.
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.
9
27
 
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.
28
+ This gem includes a backport of those changes for Rails `5.0.0` through `6.1.0.alpha`.
11
29
 
12
30
  ## Design philosophy
13
31
 
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).
32
+ This library is designed to integrate as seamlessly as possible with Rails, with the [least surprise](https://www.artima.com/intv/ruby4.html).
15
33
 
16
34
  ## Compatibility
17
35
 
18
- `actionview-component` is tested for compatibility with combinations of Ruby `2.4`/`2.5`/`2.6` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`.
36
+ `actionview-component` is tested for compatibility with combinations of Ruby `2.4`/`2.5`/`2.6`/`2.7` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`master`.
19
37
 
20
38
  ## Installation
21
- Add this line to your application's Gemfile:
39
+
40
+ In `Gemfile`, add:
22
41
 
23
42
  ```ruby
24
43
  gem "actionview-component"
25
44
  ```
26
45
 
27
- And then execute:
28
- ```bash
29
- $ bundle
30
- ```
31
-
32
46
  In `config/application.rb`, add:
33
47
 
34
48
  ```bash
35
- require "action_view/component/railtie"
49
+ require "view_component/engine"
36
50
  ```
37
51
 
38
52
  ## Guide
39
53
 
40
54
  ### What are components?
41
55
 
42
- `ActionView::Component`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).
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).
43
57
 
44
- ### Why components?
58
+ Components are most effective in cases where view code is reused or benefits from being tested directly.
45
59
 
46
- In working on views in the Rails monolith at GitHub (which has over 3700 templates), we have run into several key pain points:
60
+ ### Why should I use components?
47
61
 
48
62
  #### Testing
49
63
 
50
- Currently, Rails encourages testing views via integration or system tests. This discourages us from testing our views thoroughly, due to the costly overhead of exercising the routing/controller layer, instead of just the view. It also often leads to partials being tested for each view they are included in, cheapening the benefit of DRYing up our views.
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.
51
65
 
52
- #### Code Coverage
66
+ For partials, this means being tested for each view they are included in, reducing the benefit of reusing them.
53
67
 
54
- Many common Ruby code coverage tools cannot properly handle coverage of views, making it difficult to audit how thorough our tests are and leading to gaps in our test suite.
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.
55
69
 
56
70
  #### Data Flow
57
71
 
58
- 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 we reuse a view across different contexts.
59
-
60
- #### Standards
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.
61
73
 
62
- Our views often fail even the most basic standards of code quality we expect out of our Ruby classes: long methods, deep conditional nesting, and mystery guests abound.
74
+ By clearly defining the context necessary to render a `ViewComponent`, they're easier to reuse than partials.
63
75
 
64
- ### What are the benefits?
76
+ #### Standards
65
77
 
66
- #### Testing
78
+ Views often fail basic Ruby code quality standards: long methods, deep conditional nesting, and mystery guests abound.
67
79
 
68
- `ActionView::Component` allows views to be unit-tested. In the main GitHub codebase, our unit tests run in around 25ms/test, vs. ~6s/test for integration tests.
80
+ `ViewComponent`s are Ruby objects, making it easy to follow code quality standards.
69
81
 
70
82
  #### Code Coverage
71
83
 
72
- `ActionView::Component` is at least partially compatible with code coverage tools. We’ve seen some success with SimpleCov.
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.
73
85
 
74
- #### Data flow
75
-
76
- By clearly defining the context necessary to render a component, we’ve found them to be easier to reuse than partials.
77
-
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
- ### When should I use components?
91
-
92
- Components are most effective in cases where view code is reused or needs to be tested directly.
86
+ `ViewComponent` is at least partially compatible with code coverage tools, such as SimpleCov.
93
87
 
94
88
  ### Building components
95
89
 
96
- 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.
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.
97
93
 
98
94
  Component class names end in -`Component`.
99
95
 
100
96
  Component module names are plural, as they are for controllers. (`Users::AvatarComponent`)
101
97
 
102
- Components support ActiveModel validations. Components are validated after initialization, but before rendering.
103
-
104
- Content passed to an `ActionView::Component` as a block is captured and assigned to the `content` accessor.
98
+ Content passed to a `ViewComponent` as a block is captured and assigned to the `content` accessor.
105
99
 
106
100
  #### Quick start
107
101
 
108
- Use the component generator to create a new `ActionView::Component`.
102
+ Use the component generator to create a new `ViewComponent`.
109
103
 
110
104
  The generator accepts the component name and the list of accepted properties as arguments:
111
105
 
@@ -117,34 +111,36 @@ bin/rails generate component Example title content
117
111
  create app/components/example_component.html.erb
118
112
  ```
119
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
+
120
122
  #### Implementation
121
123
 
122
- An `ActionView::Component` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name:
124
+ A `ViewComponent` is a Ruby file and corresponding template file with the same base name:
123
125
 
124
126
  `app/components/test_component.rb`:
125
127
  ```ruby
126
- class TestComponent < ActionView::Component::Base
127
- validates :content, :title, presence: true
128
-
128
+ class TestComponent < ViewComponent::Base
129
129
  def initialize(title:)
130
130
  @title = title
131
131
  end
132
-
133
- private
134
-
135
- attr_reader :title
136
132
  end
137
133
  ```
138
134
 
139
135
  `app/components/test_component.html.erb`:
140
136
  ```erb
141
- <span title="<%= title %>"><%= content %></span>
137
+ <span title="<%= @title %>"><%= content %></span>
142
138
  ```
143
139
 
144
- We can render it in a view as:
140
+ Which is rendered in a view as:
145
141
 
146
142
  ```erb
147
- <%= render(TestComponent, title: "my title") do %>
143
+ <%= render(TestComponent.new(title: "my title")) do %>
148
144
  Hello, World!
149
145
  <% end %>
150
146
  ```
@@ -155,64 +151,18 @@ Which returns:
155
151
  <span title="my title">Hello, World!</span>
156
152
  ```
157
153
 
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
- #### Error case
189
-
190
- If the component is rendered with a blank title:
191
-
192
- ```erb
193
- <%= render(TestComponent, title: "") do %>
194
- Hello, World!
195
- <% end %>
196
- ```
197
-
198
- An error will be raised:
199
-
200
- `ActiveModel::ValidationError: Validation failed: Title can't be blank`
154
+ `ViewComponent` requires the presence of an `initialize` method in each component.
201
155
 
202
156
  #### Content Areas
203
157
 
204
-
205
158
  A component can declare additional content areas to be rendered in the component. For example:
206
159
 
207
160
  `app/components/modal_component.rb`:
208
161
  ```ruby
209
- class ModalComponent < ActionView::Component::Base
210
- validates :user, :header, :body, presence: true
211
-
162
+ class ModalComponent < ViewComponent::Base
212
163
  with_content_areas :header, :body
213
164
 
214
- def initialize(user:)
215
- @user = user
165
+ def initialize(*)
216
166
  end
217
167
  end
218
168
  ```
@@ -221,16 +171,16 @@ end
221
171
  ```erb
222
172
  <div class="modal">
223
173
  <div class="header"><%= header %></div>
224
- <div class="body"><%= body %>"></div>
174
+ <div class="body"><%= body %></div>
225
175
  </div>
226
176
  ```
227
177
 
228
- We can render it in a view as:
178
+ Which is rendered in a view as:
229
179
 
230
180
  ```erb
231
- <%= render(ModalComponent, user: {name: 'Jane'}) do |component| %>
181
+ <%= render(ModalComponent.new) do |component| %>
232
182
  <% component.with(:header) do %>
233
- Hello <%= user[:name] %>
183
+ Hello Jane
234
184
  <% end %>
235
185
  <% component.with(:body) do %>
236
186
  <p>Have a great day.</p>
@@ -247,223 +197,190 @@ Which returns:
247
197
  </div>
248
198
  ```
249
199
 
250
- Content for content areas can be passed as arguments to the render method or as named blocks passed to the `with` method.
251
- This allows a few different combinations of ways to render the component:
200
+ ### Inline Component
252
201
 
253
- ##### Required render argument optionally overridden or wrapped by a named block
254
-
255
- `app/components/modal_component.rb`:
256
- ```ruby
257
- class ModalComponent < ActionView::Component::Base
258
- validates :header, :body, presence: true
202
+ A component can be rendered without any template file as well.
259
203
 
260
- with_content_areas :header, :body
204
+ `app/components/inline_component.rb`:
261
205
 
262
- def initialize(header:)
263
- @header = header
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
264
214
  end
265
215
  end
266
216
  ```
267
217
 
268
- ```erb
269
- <%= render(ModalComponent, header: "Hi!") do |component| %>
270
- <% help_enabled? && component.with(:header) do %>
271
- <span class="help_icon"><%= component.header %></span>
272
- <% end %>
273
- <% component.with(:body) do %>
274
- <p>Have a great day.</p>
275
- <% end %>
276
- <% end %>
277
- ```
278
-
279
- ##### Required argument passed by render argument or by named block
218
+ ### Conditional Rendering
280
219
 
281
- `app/components/modal_component.rb`:
282
- ```ruby
283
- class ModalComponent < ActionView::Component::Base
284
- validates :header, :body, presence: true
220
+ Components can implement a `#render?` method to determine if they should be rendered.
285
221
 
286
- with_content_areas :header, :body
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:
287
223
 
288
- def initialize(header: nil)
289
- @header = header
290
- end
291
- end
224
+ `app/components/confirm_email_component.html.erb`
292
225
  ```
293
-
294
- `app/views/render_arg.html.erb`:
295
- ```erb
296
- <%= render(ModalComponent, header: "Hi!") do |component| %>
297
- <% component.with(:body) do %>
298
- <p>Have a great day.</p>
299
- <% end %>
226
+ <% if user.requires_confirmation? %>
227
+ <div class="alert">
228
+ Please confirm your email address.
229
+ </div>
300
230
  <% end %>
301
231
  ```
302
232
 
303
- `app/views/with_block.html.erb`:
233
+ or the view that renders the component:
234
+
235
+ `app/views/_banners.html.erb`
304
236
  ```erb
305
- <%= render(ModalComponent) do |component| %>
306
- <% component.with(:header) do %>
307
- <span class="help_icon">Hello</span>
308
- <% end %>
309
- <% component.with(:body) do %>
310
- <p>Have a great day.</p>
311
- <% end %>
237
+ <% if current_user.requires_confirmation? %>
238
+ <%= render(ConfirmEmailComponent.new(user: current_user)) %>
312
239
  <% end %>
313
240
  ```
314
241
 
315
- ##### Optional argument passed by render argument, by named block, or neither
242
+ Instead, the `#render?` hook expresses this logic in the Ruby class, simplifying the view:
316
243
 
317
- `app/components/modal_component.rb`:
244
+ `app/components/confirm_email_component.rb`
318
245
  ```ruby
319
- class ModalComponent < ActionView::Component::Base
320
- validates :body, presence: true
321
-
322
- with_content_areas :header, :body
246
+ class ConfirmEmailComponent < ViewComponent::Base
247
+ def initialize(user:)
248
+ @user = user
249
+ end
323
250
 
324
- def initialize(header: nil)
325
- @header = header
251
+ def render?
252
+ @user.requires_confirmation?
326
253
  end
327
254
  end
328
255
  ```
329
256
 
330
- `app/components/modal_component.html.erb`:
331
- ```erb
332
- <div class="modal">
333
- <% if header %>
334
- <div class="header"><%= header %></div>
335
- <% end %>
336
- <div class="body"><%= body %>"></div>
257
+ `app/components/confirm_email_component.html.erb`
258
+ ```
259
+ <div class="banner">
260
+ Please confirm your email address.
337
261
  </div>
338
262
  ```
339
263
 
340
- `app/views/render_arg.html.erb`:
264
+ `app/views/_banners.html.erb`
341
265
  ```erb
342
- <%= render(ModalComponent, header: "Hi!") do |component| %>
343
- <% component.with(:body) do %>
344
- <p>Have a great day.</p>
345
- <% end %>
346
- <% end %>
266
+ <%= render(ConfirmEmailComponent.new(user: current_user)) %>
347
267
  ```
348
268
 
349
- `app/views/with_block.html.erb`:
350
- ```erb
351
- <%= render(ModalComponent) do |component| %>
352
- <% component.with(:header) do %>
353
- <span class="help_icon">Hello</span>
354
- <% end %>
355
- <% component.with(:body) do %>
356
- <p>Have a great day.</p>
357
- <% end %>
358
- <% end %>
359
- ```
360
-
361
- `app/views/no_header.html.erb`:
362
- ```erb
363
- <%= render(ModalComponent) do |component| %>
364
- <% component.with(:body) do %>
365
- <p>Have a great day.</p>
366
- <% end %>
367
- <% end %>
368
- ```
269
+ To assert that a component has not been rendered, use `refute_component_rendered` from `ViewComponent::TestHelpers`.
369
270
 
370
271
  ### Testing
371
272
 
372
- Components are unit tested directly. The `render_inline` test helper wraps the result in `Nokogiri.HTML`, allowing us to test the component above as:
273
+ Unit test components directly, using the `render_inline` test helper and Capybara matchers:
373
274
 
374
275
  ```ruby
375
- require "action_view/component/test_case"
276
+ require "view_component/test_case"
376
277
 
377
- class MyComponentTest < ActionView::Component::TestCase
278
+ class MyComponentTest < ViewComponent::TestCase
378
279
  test "render component" do
379
- assert_equal(
380
- %(<span title="my title">Hello, World!</span>),
381
- render_inline(TestComponent, title: "my title") { "Hello, World!" }.to_html
382
- )
280
+ render_inline(TestComponent.new(title: "my title")) { "Hello, World!" }
281
+
282
+ assert_selector("span[title='my title']", "Hello, World!")
383
283
  end
384
284
  end
385
285
  ```
386
286
 
387
- In general, we’ve found it makes the most sense to test components based on their rendered HTML.
388
-
389
287
  #### Action Pack Variants
390
288
 
391
- To test a specific variant you can wrap your test with the `with_variant` helper method as:
289
+ Use the `with_variant` helper to test specific variants:
392
290
 
393
291
  ```ruby
394
292
  test "render component for tablet" do
395
293
  with_variant :tablet do
396
- assert_equal(
397
- %(<span title="my title">Hello, tablets!</span>),
398
- render_inline(TestComponent, title: "my title") { "Hello, tablets!" }.css("span").to_html
399
- )
294
+ render_inline(TestComponent.new(title: "my title")) { "Hello, tablets!" }
295
+
296
+ assert_selector("span[title='my title']", "Hello, tablets!")
400
297
  end
401
298
  end
402
299
  ```
403
300
 
404
301
  ### Previewing Components
405
- `ActionView::Component::Preview` provides a way to see how components look by visiting a special URL that renders them.
406
- In the previous example, the preview class for `TestComponent` would be called `TestComponentPreview` and located in `test/components/previews/test_component_preview.rb`.
407
- To see the preview of the component with a given title, implement a method that renders the component.
408
- You can define as many examples as you want:
302
+ `ViewComponent::Preview`, like `ActionMailer::Preview`, provides a way to preview components in isolation:
409
303
 
304
+ `test/components/previews/test_component_preview.rb`
410
305
  ```ruby
411
- # test/components/previews/test_component_preview.rb
412
-
413
- class TestComponentPreview < ActionView::Component::Preview
306
+ class TestComponentPreview < ViewComponent::Preview
414
307
  def with_default_title
415
- render(TestComponent, title: "Test component default")
308
+ render(TestComponent.new(title: "Test component default"))
416
309
  end
417
310
 
418
311
  def with_long_title
419
- render(TestComponent, title: "This is a really long title to see how the component renders this")
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
420
321
  end
421
322
  end
422
323
  ```
423
324
 
424
- The previews will be available in <http://localhost:3000/rails/components/test_component/with_default_title>
425
- and <http://localhost:3000/rails/components/test_component/with_long_title>.
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>.
426
328
 
427
- Previews use the application layout by default, but you can also use other layouts from your app:
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.
428
332
 
429
- ```ruby
430
- # test/components/previews/test_component_preview.rb
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
431
336
 
432
- class TestComponentPreview < ActionView::Component::Preview
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
433
342
  layout "admin"
434
343
 
435
344
  ...
436
345
  end
437
346
  ```
438
347
 
439
- By default, the preview classes live in `test/components/previews`.
440
- This can be configured using the `preview_path` option.
441
- For example, if you want to use `lib/component_previews`, set the following in `config/application.rb`:
348
+ Preview classes live in `test/components/previews`, can be configured using the `preview_path` option.
349
+
350
+ To use `lib/component_previews`:
442
351
 
352
+ `config/application.rb`
443
353
  ```ruby
444
354
  config.action_view_component.preview_path = "#{Rails.root}/lib/component_previews"
445
355
  ```
446
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
+
447
366
  ### Setting up RSpec
448
367
 
449
- If you're using RSpec, you can configure component specs to have access to test helpers. Add the following to
450
- `spec/rails_helper.rb`:
368
+ To use RSpec, add the following:
451
369
 
370
+ `spec/rails_helper.rb`
452
371
  ```ruby
453
- require "action_view/component/test_helpers"
372
+ require "view_component/test_helpers"
454
373
 
455
374
  RSpec.configure do |config|
456
- # ...
457
-
458
- # Ensure that the test helpers are available in component specs
459
- config.include ActionView::Component::TestHelpers, type: :component
375
+ config.include ViewComponent::TestHelpers, type: :component
460
376
  end
461
377
  ```
462
378
 
463
- Specs created by the generator should now have access to test helpers like `render_inline`.
379
+ Specs created by the generator have access to test helpers like `render_inline`.
464
380
 
465
- To use component previews, set the following in `config/application.rb`:
381
+ To use component previews:
466
382
 
383
+ `config/application.rb`
467
384
  ```ruby
468
385
  config.action_view_component.preview_path = "#{Rails.root}/spec/components/previews"
469
386
  ```
@@ -480,7 +397,7 @@ Inline templates have been removed (for now) due to concerns raised by [@soutaro
480
397
 
481
398
  ### Isn't this just like X library?
482
399
 
483
- `ActionView::Component` is far from a novel idea! Popular implementations of view components in Ruby include, but are not limited to:
400
+ `ViewComponent` is far from a novel idea! Popular implementations of view components in Ruby include, but are not limited to:
484
401
 
485
402
  - [trailblazer/cells](https://github.com/trailblazer/cells)
486
403
  - [dry-rb/dry-view](https://github.com/dry-rb/dry-view)
@@ -493,11 +410,50 @@ Inline templates have been removed (for now) due to concerns raised by [@soutaro
493
410
  - [Introducing ActionView::Component with Joel Hawksley, Ruby on Rails Podcast](http://5by5.tv/rubyonrails/276)
494
411
  - [Rails to Introduce View Components, Dev.to](https://dev.to/andy/rails-to-introduce-view-components-3ome)
495
412
  - [ActionView::Components in Rails 6.1, Drifting Ruby](https://www.driftingruby.com/episodes/actionview-components-in-rails-6-1)
496
- - [Demo repository, actionview-component-demo](https://github.com/joelhawksley/actionview-component-demo)
413
+ - [Demo repository, view-component-demo](https://github.com/joelhawksley/view-component-demo)
497
414
 
498
415
  ## Contributing
499
416
 
500
- Bug reports and pull requests are welcome on GitHub at https://github.com/github/actionview-component. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
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|
501
457
 
502
458
  ## License
503
459