Build Rails Apps with Components

Ask questions Research chat →

https://terminalwire.com/articles/superview · scraped

rails

Attachments

Scraped Content

— 1461 words · 2026-05-19 12:31:04 UTC ·

Excerpt

![](https://terminalwire-assets.fly.storage.tigris.dev/Shared-Image-2025-02-04-17-07-38-Ak24lGgzHgQiMsH8xQdIqnMXzCQBseivs0sg0J9XM1qZ70wQF2NYLduEiCMpbVFk8Tu6QMlagxroL3YfeTHoeM96NMWwAL5neXbv.png) If you’ve ever worked on a Rails app, you’ve probably run into problems with the view layer turning into a mess as the app gets older or the team gets bigger. Rails attempts to tame this problem with solutions like strict locals, but I’ve found them to be cumbersome and view them as a worse implementation of Ruby method definitions. Fortunately component libraries like Phlex and ViewComponent use Ruby method definitions to create a more sane boundary between application code and views, but using them as controller views hasn’t been straightforward and requires a lot of boilerplate. That’s where Superview comes in—Superview is a gem that makes it possible to build Rails applications from the ground-up using nothing but components. If a class instance responds to renders_in and has attr_writer
![](https://terminalwire-assets.fly.storage.tigris.dev/Shared-Image-2025-02-04-17-07-38-Ak24lGgzHgQiMsH8xQdIqnMXzCQBseivs0sg0J9XM1qZ70wQF2NYLduEiCMpbVFk8Tu6QMlagxroL3YfeTHoeM96NMWwAL5neXbv.png) If you’ve ever worked on a Rails app, you’ve probably run into problems with the view layer turning into a mess as the app gets older or the team gets bigger. Rails attempts to tame this problem with solutions like strict locals, but I’ve found them to be cumbersome and view them as a worse implementation of Ruby method definitions. Fortunately component libraries like Phlex and ViewComponent use Ruby method definitions to create a more sane boundary between application code and views, but using them as controller views hasn’t been straightforward and requires a lot of boilerplate. That’s where Superview comes in—Superview is a gem that makes it possible to build Rails applications from the ground-up using nothing but components. If a class instance responds to renders_in and has attr_writer methods, Superview can assign the controllers’ instance variables to the component and render it if the class name matches the action name. ## Implicit template rendering in Rails Before we dive into Superview, let’s understand how good we have it in Rails with view templates. Today a typical Rails blog posts controller action might look like this: ```plain text # ./app/controllers/posts_controller.rb class PostsController < ApplicationController before_action { @post = Post.find(params[:id]) } # Implicitly renders ./app/views/posts/show.html.erb end ``` Out of the box, Rails is configured to look for views in the ./app/views directory for the requested format. The major accomplishment is the code we didn’t have to write to find and render the template. Can we accomplish the same thing with component views? Let’s find out. ## The tedious way of manually rendering Phlex or ViewComponent views Phlex and ViewComponent views can be rendered in a Rails controller today, but it requires a bit of boilerplate that starts to feel really repetative as the number of actions grows in your apps. ```plain text # ./app/controllers/posts_controller.rb class PostsController < ApplicationController before_action { @post = Post.find(params[:id]) } def show respond_to do |format| # Rendering a component without Superview 👎 format.html { render PostComponent.new(post: @post) } end end end ``` The lines of code start to add up when you multiply these lines of code across each action in your Rails application. There has to be a better way, right? ## Automatically render components with Superview Since we’ve been spoiled by implicit template rendering in Rails, nobody really wants to write this boilerplate every time they want to render a component. That’s where Superview can help, and just for fun let’s make peoples’ heads explode 🤯 with an example of inline views in a controller. ```plain text # ./app/controllers/posts_controller.rb class PostsController < ApplicationController include Superview::Actions before_action { @post = Post.find(params[:id]) } # The `Show` class maps to the `show` action 👍 class Show < ApplicationComponent attr_writer :post def view_template h1 { @post.title } article { @post.body } end end # The `Edit` class maps to the `edit` action 👍 class Edit < ViewComponent::Base attr_writer :post erb_template <<~ERB <h1>Editing Post</h1> <form> <input type="text" value="<%= @post.title %>"> <textarea><%= @post.body %></textarea> </form> ERB end end ``` With Superview installed, the Show and Edit views will render for their corresponding actions. ### Installing Superview Of course, you’ll need to install Superview to get the controller above working with Superview::Actions. You can add it to your Rails app by running: Restart your Rails server and you should be good to go. ### Extracting components into their own view files I’ve found that inline views are polarizing in Rails. I love them because I can see my view code right next to my controller actions and it feels like Sinatra, but lots of people want their views in the corresponding ./app/views/posts directory. Fortunately for those who prefer to keep their views far, far away from the controller, it’s possible to move your views into their own folder. Let’s move our inline views from above into the ./app/views/posts directory. There’s a bit of Rails app configuration we need to do to get these views automatically loading with Zeitwerk, which is the code loader Rails uses to make your application work. ### Add app/views to Rails autoload paths We need to add the following to our config/application.rb file so Rails autoloader, Zeitwerk, picks up the view files. ```plain text # ./config/application.rb module YourApp class Application < Rails::Application config.autoload_paths += Rails.root.join("app/views") end end ``` ### Namespace the views Zeitwerk will automatically load the files in the ./app/views/posts directory, but only if we namespace the view in the Posts module. Here’s what view files look like, namespaced, in the ./app/views/posts directory. ```plain text # ./app/views/posts/show.rb class Posts::Show < ApplicationComponent attr_writer :post def view_template h1 { @post.title } article { @post.body } end end ``` Don’t forget the edit view! ```plain text # ./app/views/posts/edit.rb class Posts::Edit < ViewComponent::Base attr_writer :post erb_template <<~ERB <h1>Editing Post</h1> <form> <input type="text" value="<%= @post.title %>"> <textarea><%= @post.body %></textarea> </form> ERB end ``` If you’re wondering, “Why Posts and not Post”, it’s because the singular name Post is likely taken by a model. Pluralizing the namespace for views makes it less likely that you’ll run into a situation where a model and view namespace have the same name. Now there’s just one more thing… ### Include the Posts module in the controller We need to include the Posts module in the controller so that the view files are loaded by Zeitwerk. ```plain text # ./app/controllers/posts_controller.rb class PostsController < ApplicationController include Superview::Actions include Posts before_action { @post = Post.find(params[:id]) } end ``` The include Posts line loads the Show and Edit constants into the PostsController namespace, which means Superview can resolve them for the show and edit actions. If you have other controllers that need to render these views, you’d include the Posts module in those controllers as well. ## Common “edge” cases Good abstractions always have escape hatches for situations where the implicit abstraction won’t work, or needs a little help. Let’s look at how a few of those edge cases are handled. ### Multiple formats in an action For example, let’s say we need to render a JSON format in the same controller; we can use the render method to render the JSON response. ```plain text # ./app/controllers/posts_controller.rb class PostsController < ApplicationController include Superview::Actions include Posts before_action { @post = Post.find(params[:id]) } def show respond_to do |format| # The `component` method returns the component class for the action format.html { render component } # Renders the `show.json` view in the `./app/views/posts` directory format.json end end end ``` The component method will return the component class for the action, which is Posts::Show in this case. If you need to render a different component, you can pass the component class to the render method. ### Rendering a different component in an action One common pattern for the update action is to render the edit view if there’s an error so the user can see form errors and update their input. We can render the edit component in the update action by passing the Edit class to the component method. ```plain text # ./app/controllers/posts_controller.rb class PostsController < ApplicationController include Superview::Actions include Posts before_action { @post = Post.find(params[:id]) } def update if @post.update(post_params) redirect_to @post else render component Edit end end end ``` ### Selectively loading views in the controller If you have a controller that only needs to render a component for a single action, you can override the component_view method to return the component class for that action. ```plain text # ./app/controllers/posts_controller.rb class PostsController < ApplicationController include Superview::Actions before_action { @post = Post.find(params[:id]) } # Set the constant in the controller to the constant # of the view you wish to render. Show = Posts::Show end ``` ## Conclusion Superview makes it possible to build Rails applications from the ground-up using nothing but components. If it responds to renders_in and attr_writer, Superview can assign the controllers’ instance variables to the component and render it if the class name matches the action name. You can start using Superview in new and existing Rails apps by running: And then including the Superview::Actions module in your controllers. The module is backwards compatible with existing Rails views, so you can start using components in new controllers and views as you see fit. Now you have another tool to build more maintainable Rails applications with components and automatically render Phlex and ViewComponents for controller actions with Superview. ## Support this blog 🤗 If you like what you read and want to see more articles like this, please consider using Terminalwire for your web application’s command-line interface. In under 10 minutes you can build a command-line in your favorite language and web framework, deploy it to your server, then stream it to the Terminalwire thin-client that runs on your users desktops. Terminalwire manages the binaries, installation, and updates, so you can focus on building a great CLI experience.

Visibility

Visible to everyone

Reading Status

Related Bookmarks

My Note


Saved!

Annotations

Export as Markdown
+ Annotate selection

Add Annotation