Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Repositories on Rails

Repositories on Rails

How to implement the Repository Pattern in your Rails application, leading to faster tests and more maintainable code.

Avatar for Christopher Coleman

Christopher Coleman

August 03, 2018

Other Decks in Programming

Transcript

  1. The Repository Pattern is a great way to: • abstract

    your data layer from your business logic • while speeding up tests • and making code more maintainable
  2. class ChangesUserRole attr_reader :users_repository def initialize(users_repository: default_users_repository) @users_repository = users_repository

    end def call(user:, new_role:) users_repository .for(user) .grant_role(new_role) .tap do |updated_user| yield updated_user if block_given? end end def default_users_repository Api::Users end end
  3. • LaunchPlan::Create • Create launch plan • Create tasks •

    Schedule tasks on timeline • LaunchPlan::Update • Update launch plan • Create/update/delete tasks • Reschedule tasks on timeline
  4. VS

  5. • Active Record Pattern: • Smart Mutable Record Objects •

    Dynamic, lazily evaluated associations • Repository Pattern: • Dumb Immutable Record Structs • Static, preloaded associations
  6. VS

  7. VS

  8. module Api module Users class << self delegate :authenticate, :create,

    to: :user_class def user_class ActualApi::User end end end end
  9. module Api class Users class << self delegate :find, :update,

    to: :user_class alias_method :for, :new end delegate :find, :update, to: :class attr_reader :instance def initialize(instance) @instance = instance end def grant_role(role) if update(instance.id, role: role) return find(instance.id) end raise InvalidRoleGrant end end end
  10. • Create public API for repository • implemented via ActiveRecord

    or Hanami::Model • Replace all ActiveRecord model references with Repository calls • Sigh contentedly when my test suite execution time drops • Rinse and repeat
  11. class UserLesson < ApplicationRecord def self.for_users(users) where(user_id: users.map(&:id)) end def

    user Api::Users.find(user_id) end end class Api::Users def self.with_lessons(users) user_lessons = UserLesson.for_users(users).group_by(&:user_id) users.map do |user| user.with(lesson: user_lessons[user.id].lesson) end end end
  12. The Repository Pattern is a great way to: • abstract

    your data layer from your business logic • while speeding up tests • and making code more maintainable