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

Ruby on rails 在Rails应用程序的查询运行时更改表名,ruby-on-rails,ruby,activerecord,rails-activerecord,Ruby On Rails,Ruby,Activerecord,Rails Activerecord,我在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_na

我在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
我想我可以创建一个方法:

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") ...
表名不会像您预期的那样是
产品
;它将保持与以前通过
为\u租户
方法更改的相同

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

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
那里发生了什么:


方法
tenant\u model(tenant\u ID)
为ID为
tenant\u ID
的租户创建另一个模型类。该类的名称为
Product
,与表
products
一起使用,并继承
Product
类的所有方法。所以它可以像普通模型一样使用。类
产品
本身保持不变:它的
表名
仍然是
产品

Hi Ilya。回答得很好,谢谢你。我不介意表名是否在同一请求中保持更改,因为同一请求来自特定租户(网站)的用户。只有在一个请求中设置了表_名称,而在其他请求(用户)中保持不变时,才会出现问题。我计划重构所有控制器以使用Products.for_租户(租户ID)。在这个特定的场景中,我应该仍然实现您的模块,还是我可以继续我的原始想法?基本上,这将问题简化为确定self.table_name与请求的确切行为方式。您决定:)我的工作经验告诉我,具有副作用的方法通常不是一个好主意。如果您不喜欢使用这个模块,并且非常确定用户请求应该只与一个租户一起工作,那么还有另一种策略:为您的基本控制器定义一个过滤器,它将为产品类设置表名,并在以后不使用任何其他方法的情况下使用这个类。因此,从该基本控制器继承的所有控制器都将为该特定租户调整产品类!太糟糕了,所以不允许送人啤酒!希望有一天它能实现:)谢谢!是的,它也有用。我以更复杂的方式编写了该检查,因为我的代码应该与不同版本的Rails一起工作。
class Product < PostgresDatabase
  include Partitionable

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