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

from https://thoughtbot.com/upcase/videos/rspec-best-practices

find out bin/setup is quite helpful


https://github.com/ajkamel/cover/blob/master/bin/setup


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 嗎?」

builder pattern




http://rockssdlog.blogspot.jp/2012/05/design-pattern-builder-pattern.html

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 不適用




避免在 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

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 執行,確保不會漏掉。

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:


  • 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
w:2
x:1
每種身份(owner/group/others)各自的三個權限(r/w/x)分數是需要累加的,例如當權限為: [-rwxrwx---] 分數則是:
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