-
Notifications
You must be signed in to change notification settings - Fork 461
View Components
Approved by: @dmarcoux, @danidoni, @vpereira, @rubhanazeem
A partial with questionable code quality. It is perhaps complex or huge. It's unclear what data it needs since it doesn't have a clear interface. On top of this, it is probably barely tested and even if it is, it's only through slow integration tests of pages in which it is used.
Replace the partial by a view component located under src/api/app/components. View components are plain old Ruby objects (PORO). It makes them reusable, easy to test through unit tests, encapsulated and they have a clear interface defined by their initialize method. Replacing pretty much any partial by a view component is beneficial, unless a partial is super simple without any logic and used in multiple views. This is a perfect use case for a partial and using a view component won't be very helpful in this case.
There are many view component implementations in Ruby, but we decided to use the view_component gem since it integrates nicely with Rails and has increasing support throughout the Rails community. Its open-source development is also backed by GitHub.
- All view components must inherit from
ApplicationComponent. This parent class has methods to prevent potential errors in common scenarios when using view components. - View component names end with
Component. - Name view components after what they render, not what they accept. (Example:
AvatarComponentinstead ofUserComponentif the view component is rendering the avatar of a user.)
Specs of view components are located under src/api/app/spec/components. They are typical unit tests with a few extra methods and Capybara matchers like have_text, have_css (and much more) are available.
Specs can be split in two common scenarios:
- A single expectation
In the code below, the view component is rendered with render_inline(...).to_html(). We use the have_text matcher from Capybara to test the rendered component.
require 'rails_helper'
RSpec.describe ExampleComponent, type: :component do
context 'for anonymous user' do
it do
expect(render_inline(described_class.new(title: 'Everything is fine')).to_html).to have_text('Everything is fine')
end
end
end- Multiple expectations
The view component is rendered with render_inline(...).to_html once in a before block to avoid wasting time by rendering it multiple times. The rendered component is stored in rendered_component, a method provided by the view_component gem. Use Capybara matchers like have_text for your expectations. Setting User.session = create(:admin_user) in a before block allows you to test how the view component renders for various users based on their permissions.
require 'rails_helper'
RSpec.describe ExampleComponent, type: :component do
context 'for admin user' do
before do
User.session = create(:admin_user)
render_inline(described_class.new(title: 'Everything is fine for an admin')).to_html
end
it do
expect(rendered_component).to have_text('Everything is fine for an admin')
end
it do
expect(rendered_component).to have_text('Delete this example?')
end
end
endPreviews of view components are located under src/api/app/spec/components/previews. They are accessible at https://$HOST:$PORT/rails/view_components/$COMPONENT_NAME/$METHOD_NAME. Previews are enabled by default in development and test environments.
class ExampleComponentPreview < ViewComponent::Preview
# Accessible at https://my_app.com/rails/view_components/example_component/with_a_title
def with_a_title
render(ExampleComponent.new(title: "This is my example"))
end
endIn view components, custom helpers can be used through the helpers proxy. So if you have a custom helper named user_icon, use it inside a view component with helpers.user_icon. Built-in helpers like link_to shouldn't be prefixed by helpers.
To authorize users inside view components or their Haml template, use the policy method provided by ApplicationComponent. It is exactly the same as the policy method you already know from Pundit. Beside the argument provided to the policy method, nothing should change when migrating authorization code from a partial to a view component.
For example, to check in a view component if the current user can create the package my_package, write policy(my_package).create?.
- Gem: https://rubygems.org/gems/view_component
- Official documentation: https://viewcomponent.org/
- Talks from a maintainer of the view_component gem
React and Vue are 2 JavaScript frameworks which also implement view components, they call them components. The main difference with the view_component gem is that this is a client-side approach. For our needs, using a JavaScript framework would be pulling a lot of dependencies and imply a lot of changes only to support view components. Most of the JavaScript we write tends to be boilerplate code, so a simpler framework like Stimulus would be a much better choice for OBS. This is why we went with the view_component gem and a server-side approach.
Yes! View components and partials serve the same purpose. Unlike partials though, view components are easy to test through unit tests, encapsulated and they have a clear interface defined by their initialize method.
Yes! However, a super simple partial without any logic doesn't benefit much from being migrated to a view component since in this case, tests aren't needed and the clear interface of a view component through its initialize method isn't helpful if it's empty.
- Development Environment Overview
- Development Environment Tips & Tricks
- Spec-Tips
- Code Style
- Rubocop
- Testing with VCR
- Test in kanku
- Authentication
- Authorization
- Autocomplete
- BS Requests
- Events
- ProjectLog
- Notifications
- Feature Toggles
- Build Results
- Attrib classes
- Flags
- The BackendPackage Cache
- Maintenance classes
- Cloud uploader
- Delayed Jobs
- Staging Workflow
- StatusHistory
- OBS API
- Owner Search
- Search
- Links
- Distributions
- Repository
- Data Migrations
- Package Versions
- next_rails
- Ruby Update
- Rails Profiling
- Remote Pairing Setup Guide
- Factory Dashboard
- osc
- Setup an OBS Development Environment on macOS
- Run OpenQA smoketest locally
- Responsive Guidelines
- Importing database dumps
- Problem Statement & Solution
- Kickoff New Stuff
- New Swagger API doc
- Documentation and Communication
- GitHub Actions
- Brakeman
- How to Introduce Software Design Patterns
- Query Objects
- Services
- View Components
- RFC: Core Components
- RFC: Decorator Pattern
- RFC: Backend models
- RFC: Hotwire Turbo Frames Pattern