2017/10/26

Rails - Apartment 的使用方法

簡介

Apartment : https://github.com/influitive/apartment

這個套件的功能是讓你在 rails 可以透過切換 db schema,來存取不同的資料。

在 postgresql 中,預設的 schema 名稱為 public ,而所有的 table 都在 schema 'public' 下。

你可以簡單地把 schema 看成 namespace,在 apartment 中,這被稱為是 tenant。

安裝步驟

請參考 : https://github.com/influitive/apartment

使用方法

在 rails c 環境下

新增 tenant

Apartment::Tenant.create('abc')

這個指令會在 schema 'abc' 下複製 public 上的所有表格。所以 tenant 就是具有相同表格的 schema。
在以下的示範,看到 abc 的地方,代表的是要填入你的 schema name。

顯示目前所在的 tenant

Apartment::Tenant.current
# "public"

切換 tenant

Apartment::Tenant.switch!('abc')
Apartment::Tenant.current
# "abc"
Apartment::Tenant.switch!('public')
Apartment::Tenant.current
# "public"

Apartment::Tenant.switch('abc') do
  Apartment::Tenant.current
  # "abc"
end

Apartment::Tenant.current
# "public"

有暫時切換跟永久切換兩種

在 rails db 環境下

列出所有 schema

\dn
#  List of schemas
#  Name  |  Owner   
#--------+----------
# public | postgres
# abc    | etrex
# (2 rows)

顯示目前所在的 schema

SHOW search_path;
#   search_path   
#-----------------
# "$user", public
# (1 row)

切換 schema

SET search_path TO abc;
# SET
SET search_path TO 'abc';
# SET

字串要不要加引號都可以,當 search_path 的值不在 schema 的列表上時不會跳 error,可以想像成連接到一個空的 schema,而這樣並不代表新增了一個 schema。

在 select 的當下使用 schema

SELECT * FROM schema_name.table_name;

關於 excluded_models

在 apartment 提供的功能中有一個功能,這個功能是可以設定在切換 schema 的時候,讓某些表格不要被切換,也就是做成全域的表格。

舉例來說,假設我有兩個 table,是使用以下 code 所生成。

# 這裡是 bash
rails g model a
rails g model b a:references
rails db:migrate

然後我將 B 做成全域的表格

# 這裡是 /config/initializers/apartment.rb 約 18 行的位置
config.excluded_models = %w{ B }

然後建立一個 tenant 'abc' 並且切換過去,在這個環境下做測試

# 這裡是 rails c 環境下
Apartment::Tenant.create('abc')
Apartment::Tenant.switch!('abc')

對 A 做一些事情

A.count
# (3.8ms)  SELECT COUNT(*) FROM "as"
# => 0 

對 B 也做一些事情

B.count
# (0.5ms)  SELECT COUNT(*) FROM "public"."bs"
# => 0 

在這裡可以看到,當你對 B 進行操作時,其實是會強制加上 "public" 的,也就是說,對於 B ,不管在哪一個 tenant 下,都只會去存取 schema 'public' 下的表格,所以其他的 schema 的表格 bs 應該會都是空的。

嘗試在 tenant 'abc' 下新增資料

a = A.create()
#   (0.2ms)  BEGIN
#  SQL (0.4ms)  INSERT INTO "as" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2017-10-25 17:16:22.084365"], ["updated_at", "2017-10-25 17:16:22.084365"]]

b = B.create(a:a)
#   (0.2ms)  BEGIN
#  SQL (1.5ms)  INSERT INTO "public"."bs" ("a_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["a_id", 3], ["created_at", "2017-10-25 17:16:24.957753"], ["updated_at", "2017-10-25 17:16:24.957753"]]
#   (0.2ms)  ROLLBACK
#ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR:  insert or update on table "bs" violates foreign #key constraint "fk_rails_ddf8c0c4b5"
#DETAIL:  Key (a_id)=(3) is not present in table "as".
#: INSERT INTO "public"."bs" ("a_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"
# from (irb):31

這時候出大問題了,因為 A 建立在 abc 下,但是 B 建立在 public 下。而 public.bs 有一個外來鍵限制是 a_id 必須要在 public.as 中有出現。

使用 pgAdmin 連進去看,對 dbname/Schemas/public/Tables/bs/Constraints/fkrails_ddf8c04b5 按右鍵選 CREATE Script: 。

會看到生成的 script 內容為:

-- Constraint: fk_rails_ddf8c0c4b5

-- ALTER TABLE public.bs DROP CONSTRAINT fk_rails_ddf8c0c4b5;

ALTER TABLE public.bs
    ADD CONSTRAINT fk_rails_ddf8c0c4b5 FOREIGN KEY (a_id)
    REFERENCES public."as" (id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE NO ACTION;

在這裡明確地指出跟 public.bs 有關係的表格是 public.as ,而不是其他 schema 下的 as,但是 Apartment 不處理,所以會出問題。

1 則留言:

路過的兔子 提到...

求 拆掉 Apartment 的 Guideline XDDDD