2018年2月27日 星期二
rails table_name_prefix
```rb
module Snapshot
def self.table_name_prefix
'snapshot_'
end
end
```
```rb
class Snapshot::Schedule < ActiveRecord::Base
end
```
會使用 snapshot_schesules table
2018年2月26日 星期一
想在 server 上持續性地跑個簡單的 script
screen
進到 screen
```sh
screen
```
Detach (離開 screen, 但是讓 screen 裡面的東西繼續執行)
```
ctrl+a d
```
Re-attach (重回 screen)
```sh
screen -r
```
如果有多個就先 screen -ls 找到 screen id 然後 `screen -r screen_id`, ex:
```sh
pungki@mint ~ $ screen -ls
There are screens on:
7849.pts-0.mint (10/06/2013 01:50:45 PM) (Detached)
5561.pts-0.mint (10/06/2013 11:12:05 AM) (Detached)
2 Sockets in /var/run/screen/S-pungki
```
```sh
pungki@mint ~ $ screen -r 7849
```
https://www.tecmint.com/screen-command-examples-to-manage-linux-terminals/
https://stackoverflow.com/questions/4797050/how-to-run-process-as-background-and-never-die
2018年2月24日 星期六
RSpec best practice
https://github.com/thoughtbot/guides/tree/dee16052f49cb4b41fb8d090ffdb75942512ddb1/best-practices#testing
https://robots.thoughtbot.com/lets-not
看到一個不錯的 mock example (RSpec)
之前一篇文章提到 RSpec 盡量不用 let, 那要用啥?可以用 method。
另外看到一個不錯的 mock example, 用 tap 來 return double 但是 yield Authentictor
```rb
def stub_authenticator(auth_hash)
double('authenticator').tap do |authenticator|
Authenticator.stub(:new).with(authenticator).and_return(authenticator)
end
end
def stub_find_user(attributes = {})
build_stubbed(:user, attributes).tap do |user|
User.stub(:find).with(user.to_param).and_return(user)
end
end
```
https://apidock.com/ruby/Object/tap
另外看到一個不錯的 mock example, 用 tap 來 return double 但是 yield Authentictor
```rb
def stub_authenticator(auth_hash)
double('authenticator').tap do |authenticator|
Authenticator.stub(:new).with(authenticator).and_return(authenticator)
end
end
def stub_find_user(attributes = {})
build_stubbed(:user, attributes).tap do |user|
User.stub(:find).with(user.to_param).and_return(user)
end
end
```
https://apidock.com/ruby/Object/tap
from https://thoughtbot.com/upcase/videos/rspec-best-practices
great feature spec example
```rb
feature 'User views all books' do
scenario 'list all books' do
create(:book, title: 'Practical Vim')
create(:book, title: 'The Giver')
visit books_path
expect(page).to have_book('Practical Vim')
expect(page).to have_book('The Giver')
end
# ...
def have_book(title)
have_selector('.book', text: title)
end
end
```
Mutation in Ruby, it tells why more and more people like pure functional (immutable function)
Some mind blown examples:
```rb
[1] pry(main)> list = Array.new(5, "foo")
# => ["foo", "foo", "foo", "foo", "foo"]
[2] pry(main)> list[4].clear
# => ""
[3] pry(main)> list
# => ["", "", "", "", ""]
```
因為每個 default foo 都是同一個 object
以及
```rb
[1] pry(main)> master_list = Hash.new([])
# => {}
[2] pry(main)> master_list[:evens] << 2
# => [2]
[3] pry(main)> master_list[:odds] << 3
# => [2, 3]
[4] pry(main)> master_list
# => {}
[5] pry(main)> master_list[:evens] <<= 4
# => [2, 3, 4]
[6] pry(main)> master_list
# => {:evens=>[2, 3, 4]}
[7] pry(main)> master_list[:odds] <<= 5
# => [2, 3, 4, 5]
[8] pry(main)> master_list
# => {:evens=>[2, 3, 4, 5], :odds=>[2, 3, 4, 5]}
```
一樣的原理,每個 default array 都是同一個 object
一個避免這樣的做法是用 proc 確保每個 default 值是不同 object,例如:
```rb
Array.new(5) { "foo" }
Hash.new { [] }
```
ex:
```rb
[1] pry(main)> Array.new(5) { "foo" }
=> ["foo", "foo", "foo", "foo", "foo"]
[2] pry(main)> a = Array.new(5) { "foo" }
=> ["foo", "foo", "foo", "foo", "foo"]
[3] pry(main)> a[2].clear
=> ""
[4] pry(main)> a
=> ["foo", "foo", "", "foo", "foo"]
[5] pry(main)>
```
另外還有更有趣的「以你為 freeze 了是吧?Boom~!」
```rb
[1] pry(main)> CONSTANT = ["foo"]
# => ["foo"]
[2] pry(main)> CONSTANT << "bar"
# => ["foo", "bar"]
[3] pry(main)> CONSTANT.freeze
# => ["foo", "bar"]
[4] pry(main)> CONSTANT << "baz"
# RuntimeError: can't modify frozen Array
# from (pry):4:in `__pry__'
[5] pry(main)> CONSTANT[1] << "baz"
# => "barbaz"
[6] pry(main)> CONSTANT
# => ["foo", "barbaz"]
```
這是因為 freeze 是 freeze CONSTANT 這個 Array 的 object,沒有 freeze 裡面的 string,所以 string 還是 mutable
(Ice Nine gem for deep freezing)
為了拯救 developers 於水深火熱的加班 debug 地獄中,不需要 mutate 的時候就不要 mutate,寫每個 function 都想一下「這真的有必要 mutate 嗎?」
```rb
[1] pry(main)> list = Array.new(5, "foo")
# => ["foo", "foo", "foo", "foo", "foo"]
[2] pry(main)> list[4].clear
# => ""
[3] pry(main)> list
# => ["", "", "", "", ""]
```
因為每個 default foo 都是同一個 object
以及
```rb
[1] pry(main)> master_list = Hash.new([])
# => {}
[2] pry(main)> master_list[:evens] << 2
# => [2]
[3] pry(main)> master_list[:odds] << 3
# => [2, 3]
[4] pry(main)> master_list
# => {}
[5] pry(main)> master_list[:evens] <<= 4
# => [2, 3, 4]
[6] pry(main)> master_list
# => {:evens=>[2, 3, 4]}
[7] pry(main)> master_list[:odds] <<= 5
# => [2, 3, 4, 5]
[8] pry(main)> master_list
# => {:evens=>[2, 3, 4, 5], :odds=>[2, 3, 4, 5]}
```
一樣的原理,每個 default array 都是同一個 object
一個避免這樣的做法是用 proc 確保每個 default 值是不同 object,例如:
```rb
Array.new(5) { "foo" }
Hash.new { [] }
```
ex:
```rb
[1] pry(main)> Array.new(5) { "foo" }
=> ["foo", "foo", "foo", "foo", "foo"]
[2] pry(main)> a = Array.new(5) { "foo" }
=> ["foo", "foo", "foo", "foo", "foo"]
[3] pry(main)> a[2].clear
=> ""
[4] pry(main)> a
=> ["foo", "foo", "", "foo", "foo"]
[5] pry(main)>
```
另外還有更有趣的「以你為 freeze 了是吧?Boom~!」
```rb
[1] pry(main)> CONSTANT = ["foo"]
# => ["foo"]
[2] pry(main)> CONSTANT << "bar"
# => ["foo", "bar"]
[3] pry(main)> CONSTANT.freeze
# => ["foo", "bar"]
[4] pry(main)> CONSTANT << "baz"
# RuntimeError: can't modify frozen Array
# from (pry):4:in `__pry__'
[5] pry(main)> CONSTANT[1] << "baz"
# => "barbaz"
[6] pry(main)> CONSTANT
# => ["foo", "barbaz"]
```
這是因為 freeze 是 freeze CONSTANT 這個 Array 的 object,沒有 freeze 裡面的 string,所以 string 還是 mutable
(Ice Nine gem for deep freezing)
為了拯救 developers 於水深火熱的加班 debug 地獄中,不需要 mutate 的時候就不要 mutate,寫每個 function 都想一下「這真的有必要 mutate 嗎?」
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)