1

I'm experimenting with rails 7 multidb sharding, and I would like to be able to set the default shard for a rails console session.

I can use

ActiveRecord::Base.connected_to(role: :writing, shard: :default) do
  @id = Person.create! # Creates a record in shard default
end

but that's quite cumbersome for each command. Is there any way to set it from the command line, something like

shard=shard_one rails c 
Manuel van Rijn
  • 10,170
  • 1
  • 29
  • 52
Notalifeform
  • 194
  • 1
  • 8
  • I looked at this answer: https://stackoverflow.com/a/51445793/430721 but I don't think it will work in my case, since I need to to run it around the console – Notalifeform Feb 02 '22 at 20:27

3 Answers3

2

I changed this to the following solution because the db:rollback tasks didn't work because it had an issue with reading the database.yml

database.yml

development:
  nl:
    <<: *default_database
    database: <%= ENV.fetch("DATABASE_DATABASE_NL") { Rails.application.credentials.dig(:mysql, :database_nl) } %>
  uk:
    <<: *default_database
    database: <%= ENV.fetch("DATABASE_DATABASE_UK") { Rails.application.credentials.dig(:mysql, :database_uk) } %>

And add the following in your application.rb

def config.database_configuration
  parsed = super
  default_shard = ENV.fetch('DEFAULT_SHARD') { 'nl' }
  default_shard_config = { "#{default_shard}": parsed[Rails.env].delete(default_shard) }
  parsed[Rails.env] = default_shard_config.merge(parsed[Rails.env])
  parsed
end

Now you can still run DISABLE_SPRING=true DEFAULT_SHARD="uk" rails but also bin/rails db:rollback:uk etc.

Manuel van Rijn
  • 10,170
  • 1
  • 29
  • 52
0

I ended up hacking my database.yml file like this

<%= ctrys = ['nl','uk']; main = ENV.fetch('DEFAULT_SHARD'){'nl'}; ctrys.delete(main); ctrys.unshift(main);nil %>
development:
  <%= ctrys[0] %>:
    <<: *default_database
    database: <%= ENV.fetch("DATABASE_DATABASE_#{ctrys[0].upcase}") { Rails.application.credentials.dig(:mysql, :"database_#{ctrys[0]}") } %>
  <%= ctrys[1] %>:
    <<: *default_database
    database: <%= ENV.fetch("DATABASE_DATABASE_#{ctrys[1].upcase}") { Rails.application.credentials.dig(:mysql, :"database_#{ctrys[1]}") } %>

I can use it like this:

~prompt% DISABLE_SPRING=true DEFAULT_SHARD="uk" rails
Loading development environment (Rails 7.0.1)
hvg-web(dev)> ActiveRecord::Base.connection.pool.db_config.name
=> "uk"

(spring will cache it, so disable spring)

this hack does give the test-runner a challenge though:

/usr/src/app# rspec
Running via Spring preloader in process 2504
Running via Spring preloader in process 2510
Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB.

feels hacky, but it works - maybe somebody else can come up with a better plan?

Manuel van Rijn
  • 10,170
  • 1
  • 29
  • 52
Notalifeform
  • 194
  • 1
  • 8
0

The non-block equivalent of ActiveRecord::Base.connected_to(...) do ... end is ActiveRecord::Base.connecting_to(...). Since writing is the default role, in your case the simplified code is ActiveRecord::Base.connecting_to(shard: :default) which will set the active shard until you change it. For example with ActiveRecord::Base.connecting_to(shard: :other_shard).

Dave Morehouse
  • 241
  • 1
  • 7