_Note: This gem is in the process of a name / API change, see https://github.com/github/actionview-component/issues/206_ _You are viewing the README for the development version of ActionView::Component. If you are using the current release version you can find the README at https://github.com/github/actionview-component/blob/v1.11.1/README.md_ # ActionView::Component `ActionView::Component` is a framework for building view components in Rails. **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. ## Roadmap 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. This gem includes a backport of those changes for Rails `5.0.0` through `6.1.0.alpha`. ## Design philosophy This library is designed to integrate as seamlessly as possible with Rails, with the [least surprise](https://www.artima.com/intv/ruby4.html). ## Compatibility `actionview-component` is tested for compatibility with combinations of Ruby `2.5`/`2.6`/`2.7` and Rails `5.0.0`/`5.2.3`/`6.0.0`/`6.1.0.alpha`. ## Installation Add this line to your application's Gemfile: ```ruby gem "actionview-component" ``` And then execute: ```bash $ bundle ``` In `config/application.rb`, add: ```bash require "action_view/component/railtie" ``` ## Guide ### What are components? `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). ### Why components? In working on views in the Rails monolith at GitHub (which has over 3700 templates), we have run into several key pain points: #### Testing 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. #### Code Coverage 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. #### Data Flow 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. #### Standards 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. ### What are the benefits? #### Testing `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. #### Code Coverage `ActionView::Component` is at least partially compatible with code coverage tools. We’ve seen some success with SimpleCov. #### Data flow By clearly defining the context necessary to render a component, we’ve found them to be easier to reuse than partials. ### When should I use components? Components are most effective in cases where view code is reused or needs to be tested directly. ### Building components #### Conventions 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. Component class names end in -`Component`. Component module names are plural, as they are for controllers. (`Users::AvatarComponent`) Components support ActiveModel validations. Components are validated after initialization, but before rendering. Content passed to an `ActionView::Component` as a block is captured and assigned to the `content` accessor. #### Quick start Use the component generator to create a new `ActionView::Component`. The generator accepts the component name and the list of accepted properties as arguments: ```bash bin/rails generate component Example title content invoke test_unit create test/components/example_component_test.rb create app/components/example_component.rb create app/components/example_component.html.erb ``` `ActionView::Component` includes template generators for the `erb`, `haml`, and `slim` template engines and will use the template engine specified in your Rails config (`config.generators.template_engine`) by default. If you want to override this behavior, you can pass the template engine as an option to the generator: ```bash bin/rails generate component Example title content --template-engine slim invoke test_unit create test/components/example_component_test.rb create app/components/example_component.rb create app/components/example_component.html.slim ``` #### Implementation An `ActionView::Component` is a Ruby file and corresponding template file (in any format supported by Rails) with the same base name: `app/components/test_component.rb`: ```ruby class TestComponent < ActionView::Component::Base validates :content, :title, presence: true def initialize(title:) @title = title end private attr_reader :title end ``` `app/components/test_component.html.erb`: ```erb <%= content %> ``` We can render it in a view as: ```erb <%= render(TestComponent.new(title: "my title")) do %> Hello, World! <% end %> ``` Which returns: ```html Hello, World! ``` #### Error case If the component is rendered with a blank title: ```erb <%= render(TestComponent.new(title: "")) do %> Hello, World! <% end %> ``` An error will be raised: `ActiveModel::ValidationError: Validation failed: Title can't be blank` #### Content Areas A component can declare additional content areas to be rendered in the component. For example: `app/components/modal_component.rb`: ```ruby class ModalComponent < ActionView::Component::Base validates :user, :header, :body, presence: true with_content_areas :header, :body def initialize(user:) @user = user end attr_reader :user end ``` `app/components/modal_component.html.erb`: ```erb
Have a great day.
<% end %> <% end %> ``` Which returns: ```htmlHave a great day.
Have a great day.
<% end %> <% end %> ``` ##### Required argument passed by render argument or by named block `app/components/modal_component.rb`: ```ruby class ModalComponent < ActionView::Component::Base validates :header, :body, presence: true with_content_areas :header, :body def initialize(header: nil) @header = header end end ``` `app/views/render_arg.html.erb`: ```erb <%= render(ModalComponent.new(header: "Hi!")) do |component| %> <% component.with(:body) do %>Have a great day.
<% end %> <% end %> ``` `app/views/with_block.html.erb`: ```erb <%= render(ModalComponent) do |component| %> <% component.with(:header) do %> Hello <% end %> <% component.with(:body) do %>Have a great day.
<% end %> <% end %> ``` ##### Optional argument passed by render argument, by named block, or neither `app/components/modal_component.rb`: ```ruby class ModalComponent < ActionView::Component::Base validates :body, presence: true with_content_areas :header, :body def initialize(header: nil) @header = header end end ``` `app/components/modal_component.html.erb`: ```erbHave a great day.
<% end %> <% end %> ``` `app/views/with_block.html.erb`: ```erb <%= render(ModalComponent.new) do |component| %> <% component.with(:header) do %> Hello <% end %> <% component.with(:body) do %>Have a great day.
<% end %> <% end %> ``` `app/views/no_header.html.erb`: ```erb <%= render(ModalComponent.new) do |component| %> <% component.with(:body) do %>Have a great day.
<% end %> <% end %> ``` ### Conditional Rendering Components can implement a `#render?` method which indicates if they should be rendered, or not at all. For example, you might have a component that displays a "Please confirm your email address" banner to users who haven't confirmed their email address. The logic for rendering the banner would need to go in either the component template: ``` <% if user.requires_confirmation? %>