タケユー・ウェブ日報

Ruby on Rails や Flutter といったWeb・モバイルアプリ技術を武器にお客様のビジネス立ち上げを支援する、タケユー・ウェブ株式会社の技術ブログです。

特定のActionで読み込み専用のレプリカを使う

ActiveRecord::Base.connected_to で包む

class PostsController < ApplicationController
  around_action :set_reading_role, only: %i[index show]

  def index
    @posts = Post.all # :reading
  end

  def show
    @post = Post.find(params[:id])
    @post.touch # => ActiveRecord::ReadOnlyError (Write query attempted while in readonly mode: UPDATE posts SET posts.updated_at = '2020-04-07 00:00:00' WHERE posts.id = 1):
  end

  private

  def set_reading_role
    ActiveRecord::Base.connected_to(role: :reading) do
      yield
    end
  end
end

DSL風に

class PostsController < ApplicationController
  set_default_connect_role :reading, only: %i[index show]

  def index
    @posts = Post.all # :reading
  end

  def show
    @post = Post.find(params[:id])
    @post.touch # => ActiveRecord::ReadOnlyError (Write query attempted while in readonly mode: UPDATE posts SET posts.updated_at = '2020-04-07 00:00:00' WHERE posts.id = 1):
  end
end
class ApplicationController < ActionController::Base
  include DefaultConnectRole
end
module DefaultConnectRole
  extend ActiveSupport::Concern

  class_methods do
    def set_default_connect_role(role, only: nil, except: nil)
      if only.nil? && except.nil?
        around_action wrap_connected_to(role)
      elsif only.present?
        around_action wrap_connected_to(role), only: only
      elsif except.present?
        around_action wrap_connected_to(role), except: except
      else
        raise ArgumentError.new("need any options")
      end
    end

    private

    def wrap_connected_to(role)
      lambda do |controller, block|
        with_connected_to(role, &block)
      end
    end
  end

  private

  def with_connected_to(role, &block)
    ActiveRecord::Base.connected_to(role: role) do
      yield
    end
  end
end