世界上最伟大的投资就是投资自己的教育
gem 介绍及源码解析系列之 acts_as_tenant (一)
1. 简介
以前我做过一个项目是这样的。给很多幼儿园建立网站,域名也是由公司给幼儿园的,用的是子域名,比如 foo.school.com 就给 foo 幼儿园,bar.school.com 就给 bar 幼儿园,数据库用的是同一个。幼儿园是占了一张表,幼儿园下还有很多班级,年级,老师等资源,这些都是另外的几张表。这样一个老师或学生只能看到自己幼儿园的数据,这样就必须在 teachers,users 等表加上 school_id 这个标识,为了方便查找和冗余,其他各种资源也加了 school_id 这个字段。在查找各种资源的时候,例如 topic.where(school: @school),都要带上这个,一改就得改全部。这很不方便,这种其实可以通过 default_scope 来解决,不过还是不方便,容易出问题。
其实还有一种解决方案,是这样的,每个幼儿园就一个数据库,但这种虽然数据隔离了,但操作起来也不方便。
如果项目使用 PostgreSQL 的话还好,可以用schema来解决。偏偏用的是 MySQL。
找了很久找到了这个acts_as_tenant。
本次使用的 gem 的版本为 0.3.9。
2. 使用
我们以本站为例,有很多文章 article,有很多分类 group,文章属于分类,我们只要做到通过分类 group 来隔离文章 article 就好了。
class ApplicationController < ActionController::Base
set_current_tenant_through_filter
before_action :current_tenant
def current_tenant
current_group = Group.first
set_current_tenant(current_group)
end
end
为了测试效果,我把current_group
改成了Group.first
,这里可以通过 subdomain 或 url 参数来指定的,例如Group.find(params[:group_id])
等。
class Article < ActiveRecord::Base
acts_as_tenant(:group)
end
在显示所有 articles 的页面刷新一下,就会发现只显示 Group.first 下的所有 articles。
或者用rails console
来测试。
ActsAsTenant.current_tenant = Group.first
Group Load (0.9ms) SELECT "groups".* FROM "groups" ORDER BY "groups"."id" ASC LIMIT
Article.all
Article Load (2.4ms) SELECT "articles".* FROM "articles" WHERE "articles"."group_id" = $1 [["group_id", 1]]
只会找到 group_id 等于 1 的那些记录。这样就 OK 了。
acts_as_tenant还有其他功能,具体就慢慢研究官方的 readme 文档了。
3. 源码解析
接下来,来从源码入手,理解上面的功能是如何实现的。
先思想一个问题,就是为什么执行Article.all
的时候,会带上group_id
呢?这个地方跟 article 这个 model 有关,而从上面的代码可以看出,article 这个 model 只有一个方法:acts_as_tenant(:group)
。
分析源码要抓住一个关键的点入手,这个 gem 主要的是 model 层次的功能,所以我们从类方法acts_as_tenant
分析起。
# https://github.com/ErwinM/acts_as_tenant/blob/master/lib/acts_as_tenant/model_extensions.rb#L48
def acts_as_tenant(tenant = :account, options = {})
ActsAsTenant.set_tenant_klass(tenant)
# Create the association
valid_options = options.slice(:foreign_key, :class_name, :inverse_of)
fkey = valid_options[:foreign_key] || ActsAsTenant.fkey
belongs_to tenant, valid_options
default_scope lambda {
if ActsAsTenant.configuration.require_tenant && ActsAsTenant.current_tenant.nil?
raise ActsAsTenant::Errors::NoTenantSet
end
if ActsAsTenant.current_tenant
where(fkey.to_sym => ActsAsTenant.current_tenant.id)
else
all
end
}
...
end
其中有些代码被我省略了,可见,acts_as_tenant
自动包含belongs_to
的语句,而且where(fkey.to_sym => ActsAsTenant.current_tenant.id)
这一句就可以解释为什么执行Article.all
的时候,会带上group_id
,原理是用上了default_scope
。
完结。
本站文章均为原创内容,如需转载请注明出处,谢谢。
© 汕尾市求知科技有限公司 | Rails365 Gitlab | 知乎 | b 站 | csdn
粤公网安备 44152102000088号 | 粤ICP备19038915号
Top