タケユー・ウェブ日報

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