世界上最伟大的投资就是投资自己的教育

全场限时 5 折

首页Ruby
随风 · 练气

gem 介绍及源码解析系列之 acts_as_tenant (一)

随风发布于4095 次阅读

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

完结。

本站文章均为原创内容,如需转载请注明出处,谢谢。

0 条回复
暂无回复~~
喜欢
统计信息
    学员: 29203
    视频数量: 1985
    文章数量: 489

© 汕尾市求知科技有限公司 | Rails365 Gitlab | Qiuzhi99 Gitlab | 知乎 | b 站 | 搜索

粤公网安备 44152102000088号粤公网安备 44152102000088号 | 粤ICP备19038915号

Top