# 10. 模式 Schema
#### 1. Schema
[PostgreSQL-schemas](http://www.postgresql.org/docs/9.4/static/ddl-schemas.html)这里介绍了Schema的概念和用法。
简而言之,Schema是一种命名空间,它可以用来隔离表,隔离数据,又避免了连接多个数据库。在同一个数据库下,不同的Schema可以有相同名字的表(table),每个数据库默认都有public这个Schema,还可以对Schema进行权限的限制等。对Schema的操作也很简单,比如,创建`CREATE SCHEMA myschema;`、`DROP SCHEMA myschema;`,要对Schema下的表进行操作只需要加上前缀就好了。比如,`CREATE TABLE public.products ( ... );`。
还有个比较重要的东西要说,那就是search\_path。它是表的搜索路径,相当于linux系统的$PATH变量,找可执行程序的,不过它是找表的,一般来说,找表可以加Schema,如果不加就找search\_path指定的Schema,查看search\_path可以用`SHOW search_path;`,而使用`SET search_path TO myschema,public;`可以更改搜索路径。
Schema特别适合于以下几种场合。
- 管理员管理自己所属分公司的数据。
- 隔离不同幼儿园的数据。
其实[acts\_as\_tenant](http://www.rails365.net/articles/2015-10-08-gem-jie-shao-xi-lie-acts-as-tenant)就可以实现类似上面的效果,不过acts\_as\_tenant相对简单,只是代码级加上少量数据级的控制,而Schema就是数据库级别的真正数据隔离,也是原生支持,所以更好,不过只支持PostgreSQL数据库。
#### 2. multi-tenancy
我们使用[apartment](https://github.com/influitive/apartment)这个gem来实现多Schema的系统,也叫做multi-tenancy。
##### 2.1 安装
添加下面这一行到Gemfile文件。
```
gem 'apartment'
```
生成配置文件config/initializers/apartment.rb。
```
bundle exec rails generate apartment:install
```
##### 2.2 创建新的Tenants
使用ruby代码来创建PostgreSQL Schema是这样的。在`rails console`中执行下面这行。
```
Apartment::Tenant.create('tenant_name')
```
执行这行命令会有很多输出,其实它首先会创建一个PostgreSQL Schema叫tenant\_name,然后会把以前的表也在这个叫tenant\_name的Schema下生成一遍。
我们来验证一下。用`rails db`进入数据库。
执行`\dn`来查看所有的Schema。
```
rails365_pro=# \dn
List of schemas
Name | Owner
-------------+----------
public | postgres
tenant_name | postgres
(2 rows)
```
使用`\dt`来查看所有tenant\_name下的表(table)。
```
rails365_pro=# \dt tenant_name.*;
List of relations
Schema | Name | Type | Owner
-------------+----------------------+-------+----------
tenant_name | admin_exception_logs | table | postgres
tenant_name | articles | table | postgres
tenant_name | friendly_id_slugs | table | postgres
tenant_name | groups | table | postgres
tenant_name | photos | table | postgres
tenant_name | schema_migrations | table | postgres
tenant_name | taggings | table | postgres
tenant_name | tags | table | postgres
(9 rows)
```
有Apartment::Tenant.create这个命令,结合数据库维护起整个Schema就很灵活了,比如,`School.create`的时候也顺便`Apartment::Tenant.create :school`。
##### 2.3 切换Tenants
Schema避免了连接不同的数据库,但也是要切换默认的Tenants。使用`Apartment::Tenant.switch!`。
```
# 先切换到public下查看数据
Apartment::Tenant.switch!('public')
Article.all
# 切换到tenant_name下验证数据
Apartment::Tenant.switch!('tenant_name')
Article.all
```
有`create`命令和`switch!`命令,结合起来再灵活地配合application\_controller.rb等文件就可以很好地实现multi-tenancy系统了。原理就是先用`create`创建好Schema,然后到查数据或资源地方`switch!`,切换到正确的Schema来查就好,而这个可以用controller中的before\_action之类的方法搞定,设定一个当前的tenant即可。怎么来设定当前的tenant,那就可以结合传过来的参数或子域名等来处理了。
##### 2.4 删除Tenants
有创建就有删除的,那就是drop,用这个命令可以来维护Schema。
```
Apartment::Tenant.drop('tenant_name')
```
##### 2.5 通过子域名来切换Tenants
默认情况下,是通过子域名来切换Tenants的,这个可以通过配置文件`config/initializers/apartment.rb`查看到。
```
require 'apartment/elevators/subdomain'
Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'
```
意思就是,假如是foo.example.com,就会切换到foo这个Schema,如果是bar.example.com,就会切换到bar这个Schema,是这个gem提供的功能,是自动切换的,如果不需要这个功能也可以注释掉上面两行代码即可。
更加详细的功能可以看[apartment](https://github.com/influitive/apartment)的github官方readme文档或查看其源码。
完结。