在Rails应用程序中的查询运行时更改表名

我在Apache + mod_passenger上有一个胖的多租户Rails应用程序,它从PostgreSQL表中输出产品价格,如下所示:

Table "public.products" Column | Type id | bigint name | character varying(100) price | numeric(8,2) 

然后在products.rb我有……

 class Product < PostgresDatabase self.table_name = "products" # ... yadda yadda end 

我想要的是以非常具体的方式对“产品”表进行分区,以便最终为每个租户提供类似product_TENANT-ID的内容(基本上是主要产品表的视图,但这是另一个故事),并且能够像这样查询:

 Products.for_tenant(TENANT-ID).where(:name => "My product")...... 

我想我可以创建一个方法:

 class Product < PostgresDatabase self.table_name = "products" # ... yadda yadda def for_tenant(tid) self.table_name = "products_" + tid.to_s self end end 

但考虑到有大量流量(每秒数千个请求),这会对应用程序产生什么样的影响? 有什么我想念的吗? 我应该尝试不同的策略吗?

非常感谢您的任何反馈/想法!

方法

 def self.for_tenant(tid) self.table_name = "products_" + tid.to_s self end 

但是,它有意义,它有副作用:它更改Product类的表名。 稍后在同一请求中使用此类时,例如,以这种方式:

 Product.where(name: "My other product") ... 

表名不是您所期望的产品; 它将保持与之前的for_tenant方法所改变的相同。

为了避免这种歧义并保持代码清洁,您可以使用另一种策略:

1)定义一个模块,该模块包含租户分区的所有工作逻辑:

 # app/models/concerns/partitionable.rb module Partitionable def self.included(base) base.class_eval do def self.tenant_model(tid) partition_suffix = "_#{tid}" table = "#{table_name}#{partition_suffix}" exists = connection.select_one("SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = '#{table}')") unless exists['exists'] == 't' || exists['exists'] == true # different versions of pg gem give different answers return self # returning original model class end class_name = "#{name}#{partition_suffix}" model_class = Class.new(self) model_class.define_singleton_method(:table_name) do table end model_class.define_singleton_method(:name) do class_name end model_class end end end end 

2)在模型类中包含此模块:

 class Product < PostgresDatabase include Partitionable ... end 

3)以与预期相同的方式使用它:

 Product.tenant_model(TENANT_ID).where(name: "My product")... 

那里发生了什么:

方法tenant_model(TENANT_ID)为ID为TENANT_ID的租户创建另一个模型类。 此类的名称为Product_ ,与表products_并inheritanceProduct类的所有方法。 所以它可以像普通模型一样使用。 类Product本身保持不变:它的table_name仍然是products