我很难通过
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 来获得最终的集合。 使用一个查询从一个数据库获取集合,然后从另一个查询获取另一个集合。 然后使用 Ruby 从这两个集合中进行选择/过滤。我希望这对你有帮助。
我不了解 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/ 拥有第二个数据库的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。它很旧,但很好地理解了如何处理这个问题。
我希望这有帮助!