Изменение имени таблицы во время выполнения запроса в приложении Rails - PullRequest
0 голосов
/ 27 сентября 2018

У меня есть толстое мультитенантное приложение Rails на Apache + mod_passenger, которое выводит цены на продукты из таблицы 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

То, что я хочу, - это разделить таблицу «продукты» очень специфическим образом, чтобы я в итоге получил что-то вроде products_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

Но какое влияние это может оказать на приложение, учитывая большой объем трафика (тысячи запросов в секунду)?Есть ли что-то, что мне не хватает?Стоит ли попробовать другую стратегию?

Большое спасибо за любые отзывы / мысли!

1 Ответ

0 голосов
/ 27 сентября 2018

Метод

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) создает другой класс модели для арендатора с идентификатором TENANT_ID.Этот класс имеет имя Product_<TENANT_ID>, работает с таблицей products_<TENANT_ID> и наследует все методы класса Product.Так что это можно использовать как обычную модель.И сам класс Product остается нетронутым: его table_name по-прежнему products.

...