Simple Repository Pattern for Ruby on Rails

Cherry Ramatis - Jun 13 '23 - - Dev Community

What is the repository pattern? (In simple terms)

In the famous book "Patterns of Enterprise Application Architecture", Martin Fowler define a repository with the following phrase

A repository performs the tasks of an intermediary between the domain model layer and data mapping.

I'm a visual person(hope you're too) so this quote can be described as the follow diagram:

Repository Pattern General

Model(or Entity) on this diagram just represents the structure of an actor(user for example) and the whole database communication is handled by the repository class, that way we can easily swap repositories that use different databases or even mock the whole repository to isolate on unit tests.

Applying to rails

Unfortunately in rails we can't go 100% by the book on this pattern so we need to understand the concept and adapt to our current situation. Let's understand the general concepts and the limitations we have with ActiveRecord on rails today

The general concept

The general concept around repository pattern is to decouple the database communication to a new class, allowing easy mocking and swapping of implementations(use a different class for the same entity with the same methods_names/types but with different database for example).

Our limitation

Well, what about the limitations? basically it's around the fact that rails is a MVC framework that uses convention over configuration a lot, so we can't delegate database functionality without calling the model

The solution

To solve this we'll rely on good professional decision (it is what it is) to keep the model without any decision method, just configuration and field declaration.

Let's consider a sample rails app with a Task model like the following:



class Task < ApplicationRecord
end


Enter fullscreen mode Exit fullscreen mode

For this model we can define a TaskRepository located at app/repositories/task_repository.rb like the following:



class TaskRepository
  def initialize(db = Todo)
    @db = db
  end

  def find_by_id(id:)
    @db.find(id)
  end

  def list_all
    @db.all
  end
end


Enter fullscreen mode Exit fullscreen mode

After defining this class we can use it at the controller (or a service if you want to decouple even more) like the following:

Observe that we receive the repository on the constructor, so we can replace if needed.



class TaskController < ApplicationController
  def initialize(repository = TaskRepository.new)
    @repo = repository
  end

  def index
    @tasks = @repo.list_all
  end

  def show
    @task = @repo.find_by_id(id: params[:id])
  end
end


Enter fullscreen mode Exit fullscreen mode

Nice right? That way we have a couple advantages:

  • More control about the methods and what it receives (by using named params)

  • Ability to swap the repositories (just change the initialize on controller to a different class).

For example we could define a similar repository implementing a different library or database(like scyllaDB 👀)

  • Easy mocking (let's see this next).

Testing/Mocking

Testing is a very important aspect of development using RoR and to keep the unit tests offline and without any external dependencies we need to rely on mocking, the principal advantage of the repository pattern is to make our life easier while mocking so let's see how we do it with a simple test example:



class MockedTaskRepository
  def list_all
    ['mocked']
  end
end

describe TodoController do
  let (:controller) { TodoController.new(MockedTaskRepository.new) }

  it 'should return all tasks' do
    tasks = controller.index

    expect(tasks).to eq(['mocked'])
    expect(tasks.length).to be(1)
  end
end



Enter fullscreen mode Exit fullscreen mode

See? we can easily define another class and reuse it on tests or any other context that we want.

. . . . . . . . . . . . . . . . . . . . . .