# 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文檔或查看其源碼。
完結。