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

时间:2017-09-29 19:04:25

标签: ruby-on-rails postgresql join activerecord

我很难通过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

2 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:0)

这就是我这样做的方式(假设两个数据库都在同一主机上运行):

1 / 为您的第二个数据库创建一个database.yml文件 这很重要,因为它允许您以Rails方式连接到第二个数据库。我相信你已经有了它的设置但是对于未来的开发人员绊倒这个问题,它可以按照以下方式完成:

<强>配置/ 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

<强>配置/ 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

<强>配置/初始化/ 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 / Rspec

如果您使用的是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。它已经过时了,但对如何处理这个问题有了很好的理解。

我希望这有帮助!