A Rails API Pattern for Complex Collections

Written by

When building APIs in Rails I’ve noticed that controllers get heavier and heavier over time with logic around the paramaters that are being received and the collections that are being built.

I’ve been finding that breaking this collection out into its own class, with its own set of logic and then having a factory method for contructing that collection has huge benefits. These collections are easier to test, easier to optimize, and make changing the logic easier beacuse it’s centralized.

To demonstrate these collections, consider an app that manages an API for recipes. In the recipes controller, we would have code like this:

def index
  @recipe_collection = RecipeCollection.from_params(params)
  render json: @recipe_collection.recipes
end

Then RecipeCollection would looks like this:

class RecipeCollection
  attr_accessor :featured, :time_filter

  # Handles the logic of sorting params into a collection
  def self.from_params(params)
    RecipeCollection.new.tap do |recipe_collection|
      recipe_collection.time_filter = params[:time_filter]
      recipe_collection.featured = params[:featured]
    end
  end

  # Should return an ActiveRecord relation
  def recipes
    query = Recipe.scoped
    query.featured if featured.present?
    query.filter_by_time(time_filter)
  end

  def time_filter
  valid_time_filters.include?( @time_filter ) ? @time_filter : default_time_filter
  end

  def default_time_filter
    "today"
  end

  def valid_time_filters
    %w[ today yesterday this-week this-year ]
  end
end

This, RecipeCollection, class represents a collection of recipes that you would like to build. It can be interrogated about defauls (default_time_filter) and current state (time_filter). It be tested, indepedently of the controller which maps nicely to requirements like “The recipes page, by default, should just include todays recipes”.

I’ve found this pattern of having collections outside of the controller, with methods for defaults and the ability to see the complete state of the collection has really cleaned up recent projects.

As a future improvement, I think the defaults and accessor methods could be cleaned up with some metaprogramming or by making use of a middleware like design pattern.


rail-api, rails, ruby

Comments or Questions? Contact Nick @nixterrimus on twitter.

Nick is a software engineer, geek, web enthusaist, open source contributor, home automation tinkerer, ocean admirer and all around general optimist living in San Francisco. Want to get in touch about professional matters? Nick Rowe is also available on LinkedIn.