"Friendly Rails" guidelines
  • This is the post I point to when explaining (to friends, employees, contractors), what my current design patterns for Rails are. These standards are designed to keep codebases as contributor-friendly as possible.

  • Contributor-Friendliness may not be a priority for everyone, but because we run an agency, we need to be able to move new developers in and out of a codebase with minimal cognitive friction/overhead. The more layers (of complexity and libraries) that we've added to a codebase, the longer the learning curve for a new developer.

  • For example, it's reasonably common that we need to hand a codebase off to a team or developer that has no experience with Rails. The more esoteric the codebase, the more time we need to spend explaining and helping them get set up.

  • Many of these ideas/principles are also outlined more generally on html-first.com.

  • General

  • Avoid using Rails view helpers for forms, images, and links.

  • Do forms like this

  • <form action="<%= my_path %>" method="post">
      <input name="phone" type="text" />
      <input type="submit" />
  • Not Like This

  • <%= form_for(@user, url: my_path do |f| %>
      <%= f.label :email %>
      <%= f.text_field :phone %>
      <%= f.submit %>
    <% end %>
  • Do Images Like This

  • <img src="PATH_HERE" />
  • Not Like This

  • <%= image_tag "myimage.png" %>
  • Do Links Like This

  • <a href="<%= my_path %>">Click Here</a>
  • Not Like This

  • <%= link_to "Click Here", my_path %>
  • Routes

  • The Rails routes.rb API is one of the most unnecessarily obscure parts of Rails. Using keywords like resources and collection add very little value and mean that you have to open a terminal window and run a command in order to fully understand what's happening. Given that defining a route is conceptually a very straightforward task which only requires a single line of code per route, it doesn't make sense not to just keep the routes.rb file simple so that new developers can just look at it to understand what's happening (as opposed to running rails routes in a terminal).

  • Define routes like this

  • get "/orders"     => "orders#index",  :as => "orders"
    get "/oders/:id"  => "orders#show",   :as => "order"
    post "/oders/:id" => "orders#edit",   :as => "edit_order"
  • Not This

  • resources :orders
  • Gems

  • Because there are so many gems, and they're very easy to install, there's a tendency, particularly among early-career developers, to reach for gems for everything. This is a tendency that should be consciously restrained, because every new gem (usually) comes with it's own syntax that needs to be learned, and it's own maintenance burden.

    • Avoid gems whose only job is to provide a nicer syntax on top of an existing public API. Calling the API endpoint directly using something like HTTParty is almost always more expressive, more familiar, and more maintainable.

    • Avoid gems whose only job is to load in a CSS or javascript library. When those libraries inevitably need to be extended or upgraded, you have little to no control to modify them. And there is generally limited benefit to using a gem beyond slightly fewer lines of code.

    • Avoid gems that are very-slightly better at something that can be done with plain old Ruby or Rails, when that moderate improvement is not needed. The best example of this is using something like Ransack for pages with very simple, known filters. Rails already has a way to do what Ransack does, and while it might be slightly more verbose, it's also one less concept to be learned and library to be maintained.

  • Use inline_svg for SVGs

  • In an ideal world, we would just use <img src="icon.svg" /> for this. But unfortunately this won't allow us to control the icon's color with CSS or tailwind classes, which is something we want to do very regularly. I still haven't found a neat way around this, so today I'm still recommending using the inline_svg gem.

  • # Gemfile
    gem 'inline_svg'
  • <%= inline_svg("/icons/user.svg", class:"text-blue-900 w-4") %>
  • Asset Setup

  • Avoid The Asset Pipeline

  • The current default way to manage assets in Rails is called the Asset Pipeline. Using it requires understanding several different concepts and being able to deal with issues, particularly during local development.

  • But using the asset pipeline in Rails is not actually necessary. Instead, we can use plain old HTML. All we have to do is place our files in the /public folder, and then reference them using link and script tags. We've been doing this for over a year now and have had to make exactly zero trade-offs when it comes to performance and user experience. This is partly because, as you'll see below, we just don't need to load that much javascript and css.

  • Load CSS and Javascript files like this

  • <link  href="/stylesheets/variables.css" rel="stylesheet" >
    <script src="/js/htmx.min.js" defer></script>
  • Not Like This

  • <%= stylesheet_link_tag 'application', media: 'all' %>
    <%= javascript_pack_tag 'application' %>
  • CSS & JS

  • Use CSS variables, particularly for repeatedly used colors.

  • Personally I prefer defining variables in a single file called variables.css

  • :root {
      --color-1: #f2edda;
      --color-2: #e4e7e0;
      --color-3: #dcff54;
      --color-4: #1d2a3a;
      --color-5: #edf8fd;
      --color-6: #1a1f2a;
  • Once the variables are defined, create utility classes for background, text-color, and border-color. When you use these along with Tailwind, there are very few other times you'll need to reach for CSS.

  • .bg-color-1 {
      background-color: var(--color-1);
    .color-1 {
      color: var(--color-1);
    .border-color-1 {
      border-color: var(--color-1);
  • Where to apply Styles - inline, with custom CSS, or with Tailwind

  • When it comes to styling your markup, your first port of call should always be tailwind classes. Specifically, the classes found here (we use a stripped back version of Tailwind that doesn't have things like square-bracket classes).

  • Our main reason to use Tailwind is to completely circumvent what has traditionally been one of the most common sources of breakages and brittleness in codebases - the curse of the Cascade. The Cascading nature of CSS means there are effectively an infinite number of ways to break code someone else has written. With Tailwind, there is always exactly one way of assigning styles to markup, which make it much more controlled and less likely to break.

  • <div class="flex w-full justify-between items-end">
      <h1 class="text-lg font-bold mr-4">Title</h1>
      <p class="text-xl font-bold mt-3"> </p>
  • Sometimes, you want to do something very specific that there is unlikely to be a Tailwind class for. In that case, a (small) amount of inline styling is fine. Do not use inline styling when there is an exact tailwind class for the style you're applying.

  • <div class="absolute" style="top: 12px;right:3px">
      This is the right panel
  • Finally, there are also a small amount of scenarios where it makes sense to create reusable components. In this scenario, you should add the styles to a static css file

  • # /public/stylesheets/styles.css
    .shimmer {
      min-height: 20px;
      background: #f6f7f8;
      background-image: linear-gradient(
        to right,
        #f6f7f8 0%,
        #edeef1 20%,
        #f6f7f8 40%,
        #f6f7f8 100%
      background-repeat: repeat-y;
      background-size: 100% 50px;
      position: relative;
      -webkit-animation-duration: 1s;
      -webkit-animation-fill-mode: forwards;
      -webkit-animation-iteration-count: infinite;
      -webkit-animation-name: placeholderShimmer;
      -webkit-animation-timing-function: linear;
    @-webkit-keyframes placeholderShimmer {
      0% {
        background-position: -468px 0;
      100% {
        background-position: 468px 0;
  • Services/Utilities

  • TODO: Finish this

  • Javascript

  • TODO: Finish this

  • Forms

  • TODO: Finish this

  • Modals

  • TODO: Finish this

  • Filters

  • Website Page