Метод
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
.