The Missing Pieces

Once well defined roles are chosen for each layer of Rails, it becomes obvious that there is no answer about what category of objects should handle app-specific business logic. Controllers are responsible for HTTP, views manage formatting and presentation, and records deal with persistence. There is a big gap between these layers where the actual core piece of the app should live, agnostic of communication or databases.

Diagram 2

The cloud with questions marks above is exactly where the hard work of designing a good Rails architecture sits. That is the brain of the app, beyond simply transporting data from the database to the web via HTTP and markups. That is where the rules for permissions, validations, user flows, collaboration with other services, and everything that makes the app unique and useful to people. That is the app’s business logic.

The requirements for how business logic should be structured is familiar: there should be small objects that collaborate with each other through exchange of messages. Each object should have a single responsibility, a limited and concise public interface.

Moreover, these objects should not be coupled with Rails. The layers from the framework are already assigned other tasks related to persistence, presentation, and transport. The business logic should be decoupled from external libraries as much as possible, as it specializes in the universe the app is featured in.

For that, we will introduce new layers to the app stack: repositories, inputs, models, actions, and results. To better explain these concepts, let’s start with a simple example of Rails code: a classic blog app. This code is similar to the Blog example found in the Rails Guides.

In the example, there is one Active Record for the table of articles, with a couple of validations:

# app/models/article.rb

class Article < ApplicationRecord
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }

Articles Controller interacts with the Article Record to instantiate, fetch, and persist data. This code was generated by the Rails scaffold command:

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  # GET /articles
  def index
    @articles = Article.all

  # GET /articles/1
  def show

  # GET /articles/new
  def new
    @article =

  # GET /articles/1/edit
  def edit

  # POST /articles
  def create
    @article =

      redirect_to @article, notice: 'Article was successfully created.'
      render :new

  # PATCH/PUT /articles/1
  def update
    if @article.update(article_params)
      redirect_to @article, notice: 'Article was successfully updated.'
      render :edit

  # DELETE /articles/1
  def destroy
    redirect_to articles_url, notice: 'Article was successfully destroyed.'


  # Use callbacks to share common setup or constraints between actions.
  def set_article
    @article = Article.find(params[:id])

  # Only allow a list of trusted parameters through.
  def article_params
    params.require(:article).permit(:title, :body)

Finally, the views invoked by Articles Controller renders instances of Article Record to list their contents, and to generate HTML forms. For example, the index view lists all Articles:

<p id="notice"><%= notice %></p>


     <th colspan="3"></th>

   <% @articles.each do |article| %>
       <td><%= article.title %></td>
       <td><%= article.body %></td>
       <td><%= link_to 'Show', article %></td>
       <td><%= link_to 'Edit', edit_article_path(article) %></td>
       <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
   <% end %>


<%= link_to 'New Article', new_article_path %>

The new view, for instance, renders an HTML form to allow users to write new Articles:

<h1>New Article</h1>

<%= form_with(model: @article) do |form| %>
 <% if @article.errors.any? %>
   <div id="error_explanation">
     <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2>

       <% @article.errors.each do |error| %>
         <li><%= error.full_message %></li>
       <% end %>
 <% end %>

 <div class="field">
   <%= form.label :title %>
   <%= form.text_field :title %>

 <div class="field">
   <%= form.label :body %>
   <%= form.text_area :body %>

 <div class="actions">
   <%= form.submit %>
<% end %>

<%= link_to 'Back', articles_path %>