2018年2月24日 星期六
2018年2月22日 星期四
用 foreign_key 確保資料相依正確性 (referential integrity)
Rails 4.2 以後開始支援 database 的 foreign key,很好的文章:
https://robots.thoughtbot.com/referential-integrity-with-foreign-keys
我們會用
```rb
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
class Post < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end
```
這種做法來確保 post 有 user,看似沒問題但這些 validation 都在 rails 的 application 層級,而 rails 提供了很多方式讓你跳過這些 validations 和 callbacks,所以是不可信任的。ex:
User.delete_all 會跳過 `dependent: :destroy` callback
更別提我們常常連 `dependent: :destroy` callback 都忘記加(oops~)
所以應該讓 database 加上這個限制確保我們不會不小心搞爆這些資料
```rb
def change
add_foreign_key :posts, :users
end
```
另外也可以讓 database 幫我們做到 `dependent: destroy` 一樣的事情
```rb
add_foreign_key :posts, :users, on_delete: :cascade
```
會產生 SQL (postgres):
```sql
ALTER TABLE `posts`
ADD CONSTRAINT `posts_user_id_fk`
FOREIGN KEY (`user_id`) REFERENCES `users`(id)
ON DELETE CASCADE;
```
Caveats:rails 的 Polymorphic associations 不適用
https://robots.thoughtbot.com/referential-integrity-with-foreign-keys
我們會用
```rb
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
class Post < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end
```
這種做法來確保 post 有 user,看似沒問題但這些 validation 都在 rails 的 application 層級,而 rails 提供了很多方式讓你跳過這些 validations 和 callbacks,所以是不可信任的。ex:
User.delete_all 會跳過 `dependent: :destroy` callback
更別提我們常常連 `dependent: :destroy` callback 都忘記加(oops~)
所以應該讓 database 加上這個限制確保我們不會不小心搞爆這些資料
```rb
def change
add_foreign_key :posts, :users
end
```
另外也可以讓 database 幫我們做到 `dependent: destroy` 一樣的事情
```rb
add_foreign_key :posts, :users, on_delete: :cascade
```
會產生 SQL (postgres):
```sql
ALTER TABLE `posts`
ADD CONSTRAINT `posts_user_id_fk`
FOREIGN KEY (`user_id`) REFERENCES `users`(id)
ON DELETE CASCADE;
```
Caveats:rails 的 Polymorphic associations 不適用
避免在 migration 裡面用其他 class,最常誤用 model class
為什麼呢?因為 migration 是永久留在那裡的,但是某些 class 是在未來可能被刪掉的,所以一個好的 migration file 要能夠讓 scope 維持在 migration file 裡
> While migrations contain the full history of the database schema for a project, they are always run in the context of the current codebase. Referencing a model constant, while tempting, can lead to issues down the road. - Upcase
Bad example
```rb
class AddAdminFlagToUsers < ActiveRecord::Migration
def up
add_column :users, :admin, :boolean, default: false
User.update_all(admin: false)
change_column_null :users, :admin, false
end
def down
remove_column :users, :admin
end
end
```
1. User 可能未來不存在,這樣 migration 會 failed
2. 一個好的 practice 在裡面,migration 要分兩階段,設完 boolean all false 才設 null: false
與其用 class, 直接使用 connection instance 執行 sql 在這裡會是比較好的做法:
```rb
class AddAdminFlagToUsers < ActiveRecord::Migration
def up
add_column :users, :admin, :boolean, default: false
connection.update(<<-SQL)
UPDATE users SET admin = 'f'
SQL
change_column_null :users, :admin, false
end
def down
remove_column :users, :admin
end
end
```
https://thoughtbot.com/upcase/decks/4/flashcards/23
> While migrations contain the full history of the database schema for a project, they are always run in the context of the current codebase. Referencing a model constant, while tempting, can lead to issues down the road. - Upcase
Bad example
```rb
class AddAdminFlagToUsers < ActiveRecord::Migration
def up
add_column :users, :admin, :boolean, default: false
User.update_all(admin: false)
change_column_null :users, :admin, false
end
def down
remove_column :users, :admin
end
end
```
1. User 可能未來不存在,這樣 migration 會 failed
2. 一個好的 practice 在裡面,migration 要分兩階段,設完 boolean all false 才設 null: false
與其用 class, 直接使用 connection instance 執行 sql 在這裡會是比較好的做法:
```rb
class AddAdminFlagToUsers < ActiveRecord::Migration
def up
add_column :users, :admin, :boolean, default: false
connection.update(<<-SQL)
UPDATE users SET admin = 'f'
SQL
change_column_null :users, :admin, false
end
def down
remove_column :users, :admin
end
end
```
https://thoughtbot.com/upcase/decks/4/flashcards/23
Test 4 phases
Setup -> Exercise -> Verify -> Teardown
Setup: 準備資料
Exercise: 做事情
Verify: 確認事情正確執行
Teardown: 還原成 setup 前的狀態
好的 test 是依序執行這四個步驟
And also, a good test should avoid mystery guess , 意思就是 test 應該一眼就看懂
https://robots.thoughtbot.com/mystery-guest
像是 rspec 的let 雖然很方便,但很容易讓人在讀單一 test 的時候不知道這些變數是怎麼生成的,before block 也是,所以應該想辦法避免。
https://robots.thoughtbot.com/four-phase-test
2018年2月3日 星期六
rails after_create and after_commit
after_create 有時候觸發的時候 database 還沒寫入,所以要確定 database 寫入以後才執行的話應該放到 after_commit
例如
```ruby
ActiveRecord::Base.transaction do
order = build_order
order.save!
do_something_1
do_something_2
end
```
上述do_something 1 跟 2 可能花很久時間,但這時 after_create 已經觸發了,這樣就會有可能發生 error,尤其是 after_create 執行的動作需要在 order 已經建立為前提的話。
另外還有一個 activerecord lifecycle 的坑就是當 server restart 時可能會中斷 lifecycle,所以其實這種東西應該要盡可能放到 sidekiq 執行,確保不會漏掉。
例如
```ruby
ActiveRecord::Base.transaction do
order = build_order
order.save!
do_something_1
do_something_2
end
```
上述do_something 1 跟 2 可能花很久時間,但這時 after_create 已經觸發了,這樣就會有可能發生 error,尤其是 after_create 執行的動作需要在 order 已經建立為前提的話。
另外還有一個 activerecord lifecycle 的坑就是當 server restart 時可能會中斷 lifecycle,所以其實這種東西應該要盡可能放到 sidekiq 執行,確保不會漏掉。
2018年2月1日 星期四
rails bigdecimal and postgres scale
從 icoinfo 和 otcbtc 上線以來一直被小數點精度問題搞到,原因是因為直接 `.to_d` 的設定和 rails ActiveRecord 的設定不一樣,導致一堆小 bug
假設我們 postgres decimal 欄位設 scale 為 18,那麼假設我要算 order price:
```rb
price = 2.315.to_d / 1.333
# => 1.736684171042760690172543136
order.price = price
order.save
order.price
# => 1.73668417104276069
```
這就有問題了,假設我有些地方 order 還沒被建立出來,那我要怎麼模擬 scale 只有 18 的狀態?到底第 19 位會怎麼處理? floor? round?
今天受不了了查了一下 soure code:
rails source code:
假設我們 postgres decimal 欄位設 scale 為 18,那麼假設我要算 order price:
```rb
price = 2.315.to_d / 1.333
# => 1.736684171042760690172543136
order.price = price
order.save
order.price
# => 1.73668417104276069
```
這就有問題了,假設我有些地方 order 還沒被建立出來,那我要怎麼模擬 scale 只有 18 的狀態?到底第 19 位會怎麼處理? floor? round?
今天受不了了查了一下 soure code:
rails source code:
- round value /Users/wayne/Codes/rails/activemodel/lib/active_model/type/decimal.rb:63
- precision settings /Users/wayne/Codes/rails/activemodel/lib/active_model/type/value.rb:9
- initialize decimal object: /Users/wayne/Codes/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb:515
簡言之,scale 的後一位會被用 `round` 方法四捨五入
chmod 改變檔案權限
Linux檔案的基本權限就有九個,分別是owner/group/others三種身份各有自己的read/write/execute權限, 先複習一下剛剛上面提到的資料:檔案的權限字元為:『-rwxrwxrwx』, 這九個權限是三個三個一組的!其中,我們可以使用數字來代表各個權限,各權限的分數對照表如下:
r:4每種身份(owner/group/others)各自的三個權限(r/w/x)分數是需要累加的,例如當權限為: [-rwxrwx---] 分數則是:
w:2
x:1
owner = rwx = 4+2+1 = 7
group = rwx = 4+2+1 = 7
others= --- = 0+0+0 = 0
chmod 777 index.js
http://linux.vbird.org/linux_basic/0210filepermission.php
http://www.macinstruct.com/node/415
訂閱:
文章 (Atom)