首页ruby
海外散仙厉飞雨 · 真仙

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

完结。

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

0 条回复
暂无回复~~
喜欢

© 汕尾市求知科技有限公司 | 粤ICP备19038915号 | 在线学员:92

Top