代码之家  ›  专栏  ›  技术社区  ›  Nick M

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

  •  1
  • Nick M  · 技术社区  · 6 年前

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

    然后是内部产品。我有。。。

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

    我想要的是以一种非常具体的方式对“products”表进行分区,以便最终为每个租户生成类似products\u TENANT-ID的内容(基本上是主products表的视图,但这是另一种情况),并且能够像这样进行查询:

    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
    

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

    1 回复  |  直到 6 年前
        1
  •  7
  •   Ilya Konyukhov    6 年前

    方法

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

    不过,它有一个副作用:它会更改的表名 Product 班级。例如,当以后在同一请求中使用此类时:

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

    products 如你所料;它将保持不变的变化 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_<TENANT_ID> ,使用表格 products_<TENANT_ID> 产品 班级。所以它可以像普通模型一样使用。和阶级 它本身保持不变:它的 table_name 产品 .

    推荐文章