How to connect to primary database and shards using Rails 6.1 multiple databases ActiveRecord::ConnectionHandling?

Viewed 591

I am trying to build an app which will use one of latest Rails 6.1 features: multiple databases - you can read in sources at the bottom.

I would like to implement multitenancy by being able to switch between databases based on request domain.

Each club would have his own database, and one primary database would store app specific data like Users, Clubs.

I created two types of records: GlobalRecord using primary database and ShardRecord using horizontal shards databases.

And also tried to use around_action to select current club database.

# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode

development:
  primary:
    <<: *default
    database: primary
    migrations_paths: db/migrate
  club_1:
    <<: *default
    database: club_1
    migrations_paths: db/shard_migrate
  club_2:
    <<: *default
    database: club_2
    host: 1.1.1.1
    username: deploy
    password: pass
    migrations_paths: db/shard_migrate
class Club < GlobalRecord; end
class GlobalRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to shards: {
    club_1: { writing: :primary, reading: :primary },
    club_2: { writing: :primary, reading: :primary }
  }
end
class MemberRecord < ShardRecord; end
class ShardRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to shards: {
    club_1: { writing: :club_1, reading: :club_1 },
    club_2: { writing: :club_2, reading: :club_2 }
  }
end
class ApplicationController < ActionController::API
  before_action :set_club
  around_action :connect_to_shard

  private

  def set_club
    @club = SelectClubByDomain.new(request).slug
  end

  def connect_to_shard
    ActiveRecord::Base.connected_to(role: :writing, shard: @club.slug) do
      yield
    end
  end
end

I have few design questions/issues I would like to ask YOU about:

  1. I would like to set GlobalRecord with connects_to database: { writing: :primary } but because I added around_action I have to set it as above. Is it bad design? Can I refactor it so I could always call GlobalRecord without ActiveRecord::Base.connected_to(role: :writing, shard: :club_1) block?

Tried to use ActiveRecord::Base.connected_to_many(GlobalRecord, ShardRecord, role: :writing, shard: @club_slug.to_sym) in around_action but on request(clubs#index => [Club.all]) I get an error:

ActiveRecord::ConnectionNotEstablished (No connection pool for 'GlobalRecord' found for the 'club_1' shard.)
  1. On production server on start I see this warning, how to fix it?
=> Run `bin/rails server --help` for more startup options
Failed to define attribute methods because of ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished

Sources:

1 Answers

In general it's absolutely fine to switch connections with an around filter, we have been doing this for years (even before Rails 6) in my previous company.

Btw: did you see that the guide is mentioning your posted exception?

Note that connected_to with a role will look up an existing connection and switch using the connection specification name. This means that if you pass an unknown role like connected_to(role: :nonexistent) you will get an error that says ActiveRecord::ConnectionNotEstablished (No connection pool for 'ActiveRecord::Base' found for the 'nonexistent' role.)

https://guides.rubyonrails.org/active_record_multiple_databases.html#using-manual-connection-switching

Related