6 things missing from Rails

… That you will probably end up creating yourself …

When should you throw everything out and start over

After over 12 years of working with Ruby on Rails on multiple different codebases, I’ve some common themes. This is my list of patterns or types of objects you will see in Rails applications.

1. Query Objects.

ActiveRecord provides the scope macro which allows us to define queries related to that model. However, for larger, more complex queries that span multiple models, scopes can often lead to a patchwork of distributed logic. To address this, I prefer to build stand alone a Query object. I usually place this in a separate folder: `app/queries’. The following is an example of query object:

A simple base query object:

module Kenglish
  QueryResult = Struct.new(:data, :meta)

  class Query
    class << self
      def result(*args)
        new(*args).result
      end
    end

    attr_reader :params, :relation

    def initialize(params, relation)
      @params = params
      @relation = relation
    end

    def build_result(data, meta)
      QueryResult.new(data, meta)
    end
  end
end

With an example ProductQuery:

module ProductQuery
  class All < Kenglish::Query

    def result
      records = apply_filters(search(relation)).includes(:owner).references(:owner)
      records = apply_sorting(records)
      records = paginate(records)
      build_result(records, pagination_meta(records))
    end
  end
end

In a controller, this could be invoked like this:

def index
  query = ProductQuery::All.result(params, site.products)
  render json: to_jsonapi(data: query.data, meta: query.meta)
end

Some rules of thumb here are:

  • Make your query objects injectable with ActiveRecord models or scopes (what I have called relation), this make it play well with policy scopes.
  • Build common methods for searching, sorting paginating and make these customizable for different types of applications (apis, frontend, graphql, etc)

2. Form Objects

A form object, or in other parlance, a contract object, is an object that receives data from frontend forms or API calls. Beginners to Rails will embed form-like logic into their controller. As application code matures, extracting a form object will be a natural next step.

Here’s a quick and dirty base form object:

module Kenglish
  class Form
    include Virtus.model
    include ActiveModel::Validations

    # Returns true if an id field is present for the obj/model
    # @return [Boolean] Has form been persisted?
    def persisted?
      id.present? && id.to_i.positive?
    end

    # Returns true if form is valid
    # @return [Boolean] Is form valid?
    def valid?(options = {})
      before_validation

      options     = {} if options.blank?
      context     = options[:context]
      validations = [super(context)]

      validations.all?
    end

    # Opposite of valid?
    # @return [Boolean] Is form valid?
    def invalid?(options = {})
      !valid?(options)
    end
  end
end

A signup form would look like this.

module SignupForm < Kenglish::Form
  attribute :first_name, String
  attribute :last_name, String
  attribute :password, String
  attribute :password_confirmation, String
  attribute :country, String, default: 'US'

  validates :first_name, :last_name, :country, presence: true
  validates :password, presence: true, confirmation: { case_sensitive: true }
end

One of the benefits of using Virtus, which requires you to specify the attributes, is that you don’t need to mess with strong params.

Some rules of thumb I’ve found useful:

  • Use Virtus and ActiveModel::Validations so Form objects feel like models
  • Don’t save data in your Form object, only use it for validation. Treat it more like ValueObject that holds data than something that save the form.
  • Write class level methods to translate incoming data from GraphQL, jsonapi or html forms.

3. Command / Operation objects

I dislike the name “service object” because it does not invokes anything specific other than “something that is not a Model, View or Controller”. As an example, the trailblazer library has Operations and the Rectify library has Commands. Either name is appropriate.

The goal is to have an object that has one method responsible for performing business logic (in many cases database transactions) on incoming data (your Form object). We can extract an example from Rectify to make something very abstract:

module Kenglish
  class ApplicationCommand
    class << self
      def call(*args, &block)
        new(*args, &block).call
      end

      def success?(resp)
        resp.key?(:success)
      end

      def error?(resp)
        resp.key?(:error)
      end
    end

    attr_reader :form

    def initialize(form)
      @form = form
    end

    def success(result)
      { success: result }
    end

    def error(result)
      { error: result }
    end
  end
end

An example of using this command object:

class ReservationCommand < Kenglish::ApplicationCommand
  def call
    return error(form.errors) unless form.valid?
    arline = form.arline
    reserversation = nil
    arline.transaction do
      # Airline booking logic goes here
      reservation = create_reservation(form)
    end
    success(reservation)
  end
end

Our controller would then look like this:

class ReservationsController < ApplicationController
  def create
    form = ReservationForm.new(params)
    resp = ReservationCommand.call(form)
    return render 'confirmation', locals: { reservation: resp[:success] } if success?(resp)
    render 'new', locals: { reservation_form: form }, status: :unprocessable_entity
  end
end

Some rules of thumb I’ve found useful

  • Create a uniform response object that makes it easy to handle error/success.
  • Design a way that commands can be chained together, functional programing style.

4. Decorators

Decorators should be used to wrap display logic that we only use in our view layer, serializers or grahpql resolvers. My goto library when using a purely ActiveRecord based Rails app is draper. However, a simple decorator can be created to delegate to any type of object.

A simple decorator base class looks like this:

module Kenglish
  class Decorator

    attr_reader :record, :helpers
    alias h helpers

    # Build a new decorator
    # @params [record] ActiveRecord obj
    # @params [helpers] Controller's view_context/helpers
    def initialize(record, helpers)
      @helpers = helpers
      @record = record
    end

    # For activemodel compatibility
    # @return [record]
    def to_model
      record
    end

    # For activemodel compatibility
    # @return [record]
    def model
      record
    end

    # For activemodel compatibility
    # @return [Boolean]
    def is_a?(klass)
      record.class.object_id == klass.object_id
    end

    # Delegate methods to record
    # @return []
    def method_missing(meth, *args)
      if record.respond_to?(meth)
        record.send(meth, *args)
      else
        super
      end
    end

    # Respond to missing methods
    # @return [Boolean]
    def respond_to_missing?(meth, _include_private = false)
      record.respond_to?(meth)
    end

    # Check if respond_to question mark method if
    # it does not respond to regular.
    # Respond to missing methods
    # @return [Boolean]
    def respond_to?(meth)
      return true if record.respond_to?(meth)
      super
    end

    delegate :class, to: :record
  end
end

To decorator a user:

class UserDecorator < Kenglish::Decorator
  def full_name
    [first_name, last_name].join(' ')
  end

  def profile_link
    h.link_to full_name, profile_path(self)
  end
end

5. Serializers

Many Rails applications in the wild are api-only or end up supporting apis. It is important to use a standardized method of serializing data. For jsonapi, fast_jsonapi is a great library. More and more, I’m finding people using GraphQL schemas in which case you can use graphql-ruby and build your own resolvers.

I always suggest staying away from libraries like rabl and jbuilder that are tightly coupled with Rails’s view layer. Libaraies like fast_jsonapi can be tested outside of Rails and shared with non-Rails Ruby application

6. Policy Objects

Large applications require authorization to determine who has access to what data. A great policy library is Pundit. It is important to keep you role system as independent as possible from your policy library. In an object-oriented fashion, pundit can accomdate this. Pundit’s greatest feature is Scope which can be fed into your query objects.