Rails 中是否可以跨多个数据库进行内连接?

问题描述 投票:0回答:3

我很难通过

has_many :through
关联访问数据,其中某些表位于单独的数据库中。

# database_one
class Input < ApplicationRecord
  belongs_to :user      # Works great
end

# database_two
class User < AbstractClass
  belongs_to :group     # Works great
  has_many :inputs      # Works great
end

# database_two
class Group < AbstractClass
  has_many :users                     # Works great
  has_many :inputs, through: :users   # Does not work at all
end

class AbstractClass < ApplicationRecord
  self.abstract_class = true
  establish_connection "database_two_#{Rails.env}".to_sym
end

因此,使用上面的代码,我可以执行以下操作:

Group.first
=> #<Group id: 1...

User.first
=> #<User id: 1...

User.first.inputs
=> #<ActiveRecord::Associations::CollectionProxy []>

Group.first.users
=> #<ActiveRecord::Associations::CollectionProxy []>

但它不会让我执行以下操作:

Group.first.inputs
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  relation "users" does not exist
LINE 1: SELECT  "inputs".* FROM "inputs" INNER JOIN "users" ON "inpu...
                                                ^
: SELECT  "inputs".* FROM "inputs" INNER JOIN "users" ON "inputs"."user_id" = "users"."id" WHERE "users"."group_id" = $1 LIMIT $2

看起来不可能跨两个数据库进行

INNER JOIN
?我可以做些什么来缓解这个问题吗?我尝试将此方法添加到
AbstractClass
,但遗憾的是它没有解决任何问题:

def self.table_name_prefix
  "database_two_#{Rails.env}."
end

作为解决方法,我已将以下内容添加到组模型中,但这不是我正在寻找的解决方案。

def inputs
  Input.where(id: users.ids)
end
ruby-on-rails postgresql join activerecord
3个回答
9
投票

我认为不可能在一个查询中连接两个不同的表。您可能可以做的是使用 Ruby 来获得最终的集合。 使用一个查询从一个数据库获取集合,然后从另一个查询获取另一个集合。 然后使用 Ruby 从这两个集合中进行选择/过滤。我希望这对你有帮助。


0
投票

我不了解 PostgreSQL,但如果您手动构建联接,它可以在 MySQL 5.7 和 Rails v7 上运行:

class Legacy::Site < Legacy::Record
  scope :joins_new_site, -> {
    # Workaround to join different databases.
    new_db = ::Site.connection.current_database
    new_table = ::Site.table_name
    joins("INNER JOIN #{new_db}.#{new_table} ON #{new_table}.legacy_site_id = #{table_name}.site_id")
  }

  scope :enabled, -> { joins_new_site.merge(::Site.enabled) }
end

我还没有尝试过“有很多通过”连接,但适用相同的原则,只需显式命名数据库即可。


-1
投票

这就是我执行此操作的方法(假设两个数据库在同一主机上运行):

1/ 拥有第二个数据库的database.yml 文件 这很重要,因为它将允许您以 Rails 方式连接到第二个数据库。我确信您已经设置好了,但是对于未来的开发人员偶然发现这个问题,可以按如下方式完成:

config/database.yml

development:
  adapter: postgresql
  encoding: unicode
  database: database_one_development
  pool: 5
  username: USERNAME
  password: PASSWORD

test:
  adapter: postgresql
  encoding: unicode
  database: database_one_test
  pool: 5
  username: USERNAME
  password: PASSWORD

production:
  adapter: postgresql
  encoding: unicode
  database: database_one_production
  pool: 5
  username: USERNAME
  password: PASSWORD

config/database_two.yml

development:
  adapter: postgresql
  encoding: unicode
  database: database_two_development
  pool: 5
  username: USERNAME
  password: PASSWORD

test:
  adapter: postgresql
  encoding: unicode
  database: database_two_test
  pool: 5
  username: USERNAME
  password: PASSWORD

production:
  adapter: postgresql
  encoding: unicode
  database: database_two_production
  pool: 5
  username: USERNAME
  password: PASSWORD

config/initializers/database_connector.rb

DATABASE_ONE_DB = YAML.load_file(File.join(Rails.root, "config", "database.yml"))[Rails.env.to_s]
DATABASE_TWO_DB = YAML.load_file(File.join(Rails.root, "config", "database_two.yml"))[Rails.env.to_s]

2/ 设置你的 AbstractClass 如下:

class DatabaseTwoModel < ApplicationRecord
  self.abstract_class = true
  establish_connection DATABASE_TWO

  def self.table_name_prefix
    "database_two_#{Rails.env}."
  end
end

您的型号如下:

class User < DatabaseTwoModel
  belongs_to :group
  has_many :inputs
end

class Group < DatabaseTwoModel
  has_many :users
  has_many :inputs, through: :users
end

3/为了避免任何混淆,我为属于database_one的模型创建另一个类

class DatabaseOneModel < ApplicationRecord
  self.abstract_class = true

  def self.table_name_prefix
    "database_one_#{Rails.env}."
  end
end

您的

Input
模型应该像这样继承此类:

class Input < DatabaseOneModel
  belongs_to :user      # Works great
end

它本身确实工作得很好,但是当进行内连接时,它可能会搞乱 SQL 查询。

4/R规格

如果您使用 Rspec,您需要将其添加到您的

rails_helper.rb
文件中:

database_one = Rails.configuration.database_configuration[Rails.env]
database_two = YAML.load_file(File.join(Rails.root, "config", "database_two.yml"))[Rails.env]

# start by truncating all the tables but then use the faster
# transaction strategy the rest of the time.
config.before(:suite) do
  ActiveRecord::Base.establish_connection database_two
  DatabaseCleaner.clean_with(:truncation)
  DatabaseCleaner.strategy = :transaction
  ActiveRecord::Base.establish_connection database_one
  DatabaseCleaner.clean_with(:truncation)
  DatabaseCleaner.strategy = :transaction
end

这会清理数据库,并且您的测试将顺利运行。

您应该能够运行您的查询以及 has_many 。

不同的主机

如果您的数据库位于不同的主机上,您可以查看 St-Elsewhere gem。它很旧,但很好地理解了如何处理这个问题。

我希望这有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.