Rails单模型中自动使用双Apartment数据库处理公共与租户数据
刚好碰到过类似的多租户+共享公共数据的场景,给你整理一套可落地的Rails模型层实现方案,完全适配你用带c后缀ID区分公共/租户数据的思路:
一、先搞定多数据库的基础配置
首先得在config/database.yml里把两个数据库的连接信息配置好。假设你用PostgreSQL,示例配置如下:
# 公共库配置 common: adapter: postgresql database: common_db username: your_db_user password: your_db_password host: localhost pool: 5 # 租户库(这里假设动态绑定当前租户,你可以根据自己的多租户方案调整,比如用租户子域名/ID区分) development: primary: adapter: postgresql database: tenant_dev_<%= Current.tenant.id %> # 用Current.tenant存储当前租户上下文 username: your_db_user password: your_db_password host: localhost pool: 5 # 生产环境同理,替换成对应的数据库信息 production: common: adapter: postgresql url: <%= ENV['COMMON_DATABASE_URL'] %> pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> primary: adapter: postgresql url: <%= ENV["DATABASE_URL"] %> pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
二、模型层核心:根据ID自动切换数据库
假设你的目标模型是Product,我们要在模型里实现操作前自动判断ID格式,切换到对应数据库的逻辑。这里推荐用Rails的connected_to块来切换连接(比直接调用establish_connection更安全,不会污染全局连接):
class Product < ApplicationRecord # 辅助方法:判断当前ID是否属于公共数据 def self.common_data_id?(id) id.to_s.end_with?('c') end # 重写find方法:根据ID后缀切换数据库查询 def self.find(id) if common_data_id?(id) # 切换到公共库,同时去掉ID后缀用真实ID查询 connected_to(role: :common) do super(id.delete('c')) end else # 用当前租户的数据库查询 super(id) end end # 处理保存逻辑:公共数据存到公共库,租户数据存到当前库 def save if self.id.to_s.end_with?('c') self.class.connected_to(role: :common) do # 去掉后缀后再存入公共库(避免公共库ID带c) self.id = self.id.delete('c') super end else super end end # 同理,处理更新、销毁操作 def update(attributes) if self.id.to_s.end_with?('c') self.class.connected_to(role: :common) do super(attributes) end else super(attributes) end end def destroy if self.id.to_s.end_with?('c') self.class.connected_to(role: :common) do super end else super end end # 处理批量查询(比如where条件带ID的情况) def self.where(conditions) if conditions[:id] && common_data_id?(conditions[:id]) connected_to(role: :common) do super(id: conditions[:id].delete('c')) end else super(conditions) end end end
三、额外的优化和避坑点
- ID格式验证:在模型里加个验证,确保输入的ID符合规则,避免乱输入导致数据库切换错误:
validates :id, format: { with: /^\d+c?$/, message: "ID格式无效:公共数据需以c结尾(如1c),租户数据为纯数字(如1)" } - 全局ID唯一性:业务层面要约定好公共库和租户库的ID范围,比如公共库ID从10000开始,租户库用1-9999,避免出现租户ID和公共库ID(去掉c后)重复的情况,防止查询混淆。
- 连接池管理:
connected_to块会自动管理连接的获取和释放,所以尽量用块的方式切换连接,不要直接调用establish_connection(会全局修改模型的连接,影响其他请求)。 - 关联处理:如果模型有关联关系(比如公共分类和租户商品的关联),要在关联模型里也做类似的数据库切换逻辑,或者查询时手动指定连接。
- 测试覆盖:测试时要分别验证公共数据和租户数据的CRUD操作,确保数据库切换逻辑正确,比如创建ID为
1c的Product后,检查它确实存在于CommonDB而非租户库。
内容的提问来源于stack exchange,提问作者Alexander
相关产品推荐
相关产品推荐

