我有一个多租户的Rails应用程序。每个模型都有一个account_id,属于一个帐户,并且具有当前帐户ID的默认范围:
class Derp < ApplicationRecord
default_scope { where(account_id: Account.current_id) }
belongs_to :account
end
这很好用,我在其他应用程序的生产中使用了这种模式(我知道默认范围是不受欢迎的,但这是一种公认的模式。请参阅:https://leanpub.com/multi-tenancy-rails)。
现在这里是踢球者 - 我有一个客户端(可能还有更多的线路,谁知道),谁想在自己的服务器上运行软件。为了解决这个问题,我简单地创建了一个带有type属性的服务器模型:
class Server < ApplicationRecord
enum server_type: { multitenant: 0, standalone: 1 }
end
现在在我的多租户服务器实例上,我只创建一个Server记录并将server_type设置为0,并在我的独立实例上将其设置为1.然后我在应用程序控制器中有一些帮助方法来帮助有了这个,即:
class ApplicationController < ActionController::Base
around_action :scope_current_account
...
def server
@server ||= Server.first
end
def current_account
if server.standalone?
@current_account ||= Account.first
elsif server.first.multitenant?
@current_account ||= Account.find_by_subdomain(subdomain) if subdomain
end
end
def scope_current_account
Account.current_id = current_account.id
yield
rescue ActiveRecord::RecordNotFound
redirect_to not_found_path
ensure
Account.current_id = nil
end
end
这有效,但是我有大量的记录集,我在这个特定的独立客户端上查询(70,000条记录)。我在account_id上有一个索引,但我的主要客户表在我的开发机器上从100ms到400ms。
然后我意识到:独立服务器根本不需要关注帐户ID,特别是如果它会影响性能。
所以我真的要做的就是让这条线有条件:
default_scope { where(account_id: Account.current_id) }
我想做这样的事情:
class Derp < ApplicationRecord
if Server.first.multitenant?
default_scope { where(account_id: Account.current_id) }
end
end
但显然语法错了。我已经在条件范围的Stack Overflow上看到了一些其他示例,但似乎没有一个基于完全独立的模型的条件语句。有没有办法在Ruby中实现类似的东西?
编辑:Kicker,我刚刚意识到这只会解决一个独立服务器的速度问题,并且所有多租户帐户仍然需要处理使用account_id进行查询。也许我应该专注于那个......答案 0 :(得分:1)
我会避免使用y
因为我过去曾被它咬过。特别是,我在一个应用程序中有一些地方,我想肯定有它的范围,以及其他我不喜欢的地方。我希望范围界定的地方通常最终成为控制器/后台工作,而我不想要/需要它的地方最终成为测试。
因此,考虑到这一点,我会选择控制器中的显式方法,而不是模型中的隐式作用域:
你有:
default_scope
我在控制器中有一个名为class Derp < ApplicationRecord
if Server.first.multitenant?
default_scope { where(account_id: Account.current_id) }
end
end
的方法:
account_derps
然后,只要我想加载给定帐户的derps,我就会使用def account_derps
Derp.for_account(current_account)
end
。如果我需要,我可以自由地使用account_derps
进行无范围的查找。
关于这种方法的最佳部分是你可以在这里放弃你的Derp
逻辑。
你在这里提到另一个问题:
这样可行,但我已经获得了大量的记录集,我在这个特定的独立客户端上查询(70,000条记录)。我在account_id上有一个索引,但我的主要客户表在我的开发机器上从100毫秒到400毫秒。
我认为这很可能是由于缺少索引。但我在这里或查询中看不到表格架构,所以我不确定。可能是您在Server.first.multitenant?
和其他某个字段上执行了查询,但您只是将索引添加到account_id
。如果您正在使用PostgreSQL,那么查询前的account_id
将指向正确的方向。如果您不确定如何破译其结果(有时它们可能很棘手),那么我建议使用精彩的pev(Postgres EXPLAIN Visualizer),它会指向查询中最慢的部分以图形格式。
最后,感谢您花时间阅读我的书,并就SO的相关主题提出如此详细的问题:)
答案 1 :(得分:0)
这是我的解决方案:
首先,将任何帐户作用域模型所具有的帐户范围内容抽象为从ApplicationRecord继承的抽象基类:
class AccountScopedRecord < ApplicationRecord
self.abstract_class = true
default_scope { where(account_id: Account.current_id) }
belongs_to :account
end
现在任何模型都可以干净利落地像帐户一样:
class Job < AccountScopedRecord
...
end
要解决有条件的抽象问题,请进一步了解ActiveRecord问题:
module AccountScoped
extend ActiveSupport::Concern
included do
default_scope { where(account_id: Account.current_id) }
belongs_to :account
end
end
然后AccountScopedRecord可以:
class AccountScopedRecord < ApplicationRecord
self.abstract_class = true
if Server.first.multitenant?
send(:include, AccountScoped)
end
end
现在,独立帐户可以忽略任何与帐户相关的内容:
# Don't need this callback on standalone anymore
around_action :scope_current_account, if: multitenant?
# Method gets simplified
def current_account
@current_account ||= Account.find_by_subdomain(subdomain) if subdomain
end