2017年11月19日 星期日

用 Ruby 實作 Elixir 的 cond statement

## Elixir:

```
defmodule Validator do
  def validate_age(age) do
    if age < 18 do
      "Under 18"
    else
      if age < 21 do
        "Under 21"
      else
        "Adult"
      end
    end
  end
end
```

變成

```
defmodule Validator do
  def validate_age(age) do
    cond do
      age < 18 -> "Under 18"
      age < 21 -> "Under 21"
      true -> "Adult"
    end
  end
end
```

## Ruby

Implement 1

```ruby
class Elixir

  def cond(&block)
    assertions = block.call()
    return assertions.compact[0]
  end

  def cassert(assertion, block)
    block.call() if assertion == true
  end

end



RSpec.describe Elixir do
  let(:elixir) { described_class.new }

  describe "cond" do
    specify do
      result = elixir.cond do
        [
          elixir.cassert(false, -> { return 1 }),
          elixir.cassert(true, -> { return 2 }),
          elixir.cassert(true, -> { return 3 })
        ]
      end

      expect(result).to eq(2)
    end
  end


  describe "cassert" do
    specify do
      result = elixir.cassert(true, -> { return 1 })

      expect(result).to eq(1)
    end

    specify do
      result = elixir.cassert(false, -> { return 1 })

      expect(result).to eq(nil)
    end
  end
end

```



Implement 2

```ruby

class Elixir
  def cond(*assertions)
    return assertions.compact.first
  end

  def cassert(assertion, block)
    return block.call() if assertion == true
  end
end



RSpec.describe Elixir do
  let(:elixir) { described_class.new }

  describe "cond" do
    specify do
      result = elixir.cond(
        elixir.cassert(false, -> { return 1 }),
        elixir.cassert(true, -> { return 2 }),
        elixir.cassert(true, -> { return 3 })
      )

      expect(result).to eq(2)
    end
  end

  describe "cassert" do
    specify do
      result = elixir.cassert(true, -> { 1 })

      expect(result).to eq(1)
    end

    specify do
      result = elixir.cassert(false, -> { 1 })

      expect(result).to eq(nil)
    end
  end
end

```


Implement 3

```ruby

class Elixir
  def cond(*assertions)
    return assertions.compact.first
  end

  def if(assertion)
    yield if block_given? && assertion == true
  end
end

RSpec.describe Elixir do
  let(:elixir) { described_class.new }

  describe "cond" do
    specify do
      result = elixir.cond(
        elixir.if(false) { 1 },
        elixir.if(true) { 2 },
        elixir.if(true) { 3 }
      )

      expect(result).to eq(2)
    end
  end

  describe "if" do
    specify do
      result = elixir.if(true) { 1 }

      expect(result).to eq(1)
    end

    specify do
      result = elixir.if(true)

      expect(result).to eq(nil)
    end

    specify do
      result = elixir.if(false) { 1 }

      expect(result).to eq(nil)
    end
  end
end
```




2017年11月18日 星期六

Capistrano forward agent


Capistrano forward agent 可以讓我們不用把 server 上的key 加到 github 也能拉 private repo


今天遇到 permission deny 的問題,結果是因為 mac 重開機就清空了 ssh agents ,導致沒有 key 可以 forward ,要永久加到 ssh agent 裡面,要執行

```
ssh-add -K ~/.ssh/id_rsa
```

之後再 deploy 就行了

https://chodounsky.net/2015/07/27/forward-ssh-keys-for-capistrano-deploys/

設定 domain

A record: 把這個 domain 綁到特定 IP
CNAME: 別名,例如 hello.waynechu.com,hello 就是



https://www.oyag.com/9501/websys


Nginx


/etc/nginx/site-availables/myconfig

```
server {
  listen 80;
  server_name www.example.com;
  location / {
    proxy_pass 127.0.0.1:8000;
  }
}
```

cd /etc/nginx/site-enabled

sudo ln -s /etc/nginx/site-availables/myconfig

https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-16-04

2017年11月17日 星期五

加速 git clone

最近網速慢到快死,尤其是 git clone,竟然不到 10kb/s 所以 google 了一下加速的方法


https://codeyarns.com/2016/01/12/slow-speed-on-git-clone/

在 Linode 跑 lending bot

Ubuntu 16.04 LTS

先參考裝機筆記 https://everyday1percent.blogspot.hk/2017/10/blog-post.html 把基本的機器安全設定設好


把機器加到github https://help.github.com/articles/connecting-to-github-with-ssh/

安裝 docker https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-repository

安裝 docker compose https://docs.docker.com/compose/install/

```
sudo docker login
```

server
```
mkdir projects
cd projects
git clone xxxxx
cd xxxxx
sudo docker-compose up -d
```

成功的log
```
Creating lendingbot_nginx_1 ...
Creating lendingbot_nginx_1 ... done
Creating lendingbot_poloniex_1 ...
Creating lendingbot_bitfinex_1 ...
Creating lendingbot_poloniex_1
Creating lendingbot_poloniex_1 ... done
```



測試一下是不是可以連線,進 http://poloniex.yourdomain

通常要等一陣子,但如果有出現 nginx 就是成功了

## 加上 HTTP auth

先進VPS機器裡面生成密碼檔案

bash -c “echo -n ‘USERNAME:’ >> /path/to/htpasswd/subdomain.doamin”
bash -c “openssl passwd -apr1 >> /path/to/htpasswd/subdomain.doamin”

建立好之後,修改 docker-compose.yaml

在 services nginx volumes 下加入
- /path/to/htpasswd:/etc/nginx/htpasswd

重啟 docker 就會有了

可以自動放貸的機器人 poloniexlendingbot 的筆記

可以自動放貸的機器人,支援 Bitfinex 和 Poloniex,而且有 docker compose 可以一鍵跑完還有 Web UI 介面,潮。

https://github.com/BitBotFactory/poloniexlendingbot


## 重要的參數:

- gapbottom
- gaptop
- gapMode

gapbottom 跟 gaptop

gapbottom 就是指要「跳過」多少 loan offers,這裡的「跳過」是指數量

例如下圖:現在共有 194 顆 BTC 想要用 0.017 的 rate 借出,那如果我 gapbottom 設 200 就是指我想要用比前 200 顆 BTC 想要借 rate 還要更好的 rate 借出,在此例就是會用 0.018 以上的 rate 借出。

gaptop 就是我最多借到多少 rate,像以下圖為例如果 gaptop 超過 4509 (超多人掛在 0.02 rate) 那基本上就是不可能借出了

那為什麼要有 gaptop 呢?這就跟 spreadlend 有關。

spreadlend 就是指在 gapbottom 和 gaptop 之間你要拆成幾個 loan offers 去借,假設是 10 好了,那麼就會幫你在這個區間平均 create 10 個 offer。

例如我有 10 BTC,spreadlend 10,那就會幫我建立 10 個出借 1 BTC 且 rate 在 gaptop 到 gapbottom 之間的 loan offers,至於 rate 是多少則是參考其他 offers 來算出來的。



那什麼是 gapMode? gapMode 就是指「算 gapbottom 和 gaptop」的「算法」,目前有三種 gapMode

* Raw

這是最簡單的一種,假設我選 `gapMode = Raw` 然後 gapbottom 設 10,那就是如果我借 ETH 就跳過 10 ETH,如果我借 BTC 就跳過 10 BTC

* RawBTC

這跟 Raw 很像,但唯一不同的點是他都是以 BTC 為單位,以上面選 `gapMode = RawBTC` 然後 gapbottom 設 10 的例子來說好了,如果我借 BTC 就跳過 10 BTC,但如果我借 ETH 他就會跳過「等值 10 BTC 的 ETH」,假設 1 BTC = 9 ETH,那就會跳過 90 ETH

* Relative

相較於 RawBTC 是以 BTC 為基礎,Relative 則是已你有的錢為基礎,假設我有 20 BTC 100 ETH,那選 `gapMode = Relative ` 然後 gapbottom 設 10 的例子來看的話就是我要跳過

- 出借 BTC 時,跳過 10% * 20 BTC = 2 BTC
- 出借 ETH 時,跳過 10% * 100 ETH = 10 ETH



## 次重要的參數

* mindailyrate: 最低接受多少 rate
* spreadlend: 拆幾單
* xdays: 借款週期,設 0 就是 2 days
* minloansize: 最低的 loan offer amount,因為程式會一直拆 loan offers,有可能會拆到很碎,所以需要設這個阻止程式一直往下拆


## Bonus

* 另外這個 bot 還支援各種通知,例如 slack, telegram, email, irc.... etc,只要在設定檔加些東西就行了,很簡單 http://poloniexlendingbot.readthedocs.io/en/latest/configuration.html#notifications

* Web UI 介面,潮




2017年11月16日 星期四

Ruby keyword arguments


TL;DR:


```ruby
  def log_operator(operator: nil, **options)
    self.operator = operator
  end
```

可以:

log_operator(operator: user, greeting: "hello")

因為

**options

會變成

{
  greeting: "hello"
}

https://www.justinweiss.com/articles/fun-with-keyword-arguments/

2017年11月13日 星期一

ActiveSupport::Callbacks

延續上一篇,這邊用 ActiveSupport::Callbacks 來做 callbacks

官方教學:

http://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html


下一個問題:

所以怎麼做到下面這個效果呢?

```ruby
class Test
extend ActiveModel::Callbacks

define_model_callbacks :execute

after_execute :log_something, only: [:index]

def index
puts "Hi World!"
end

def log_something
puts "Log something..."
end
end
```

或者是:

```ruby
class Test
include ActiveSupport::Callbacks
define_callbacks :execute


after_execute :log_something, only: [:index]

def index
puts "Hi World!"
end

def log_something
puts "Log something..."
end
end

```
呢?

用 ActiveModel::Callbacks 做 before, around, after callbacks

extend ActiveModel::Callbacks

```ruby
class Test
extend ActiveModel::Callbacks

define_model_callbacks :execute

before_execute :log_something

def index
run_callbacks :execute do
puts "Hi World!"
end
end

def log_something
puts "Log something..."
end
end
```

運行結果:
```
Development [1] rocket(main)> Test.new.index
Log something...
Hi World!
nil
```

rails before_actoin design pattern - Aspect-oriented_programming (AOP)

想要對 ruby class 實作 before_action ,查到

https://stackoverflow.com/questions/23444964/is-it-possible-to-do-a-before-action-in-ruby-like-in-rails

於是看了一下 Aspect-oriented_programming (AOP)

https://en.wikipedia.org/wiki/Aspect-oriented_programming

看更簡單的解釋:
https://stackoverflow.com/questions/242177/what-is-aspect-oriented-programming

也不用解釋太多,其實就是 before_action 在做的事。

2017年11月8日 星期三

rails bootsnap 加速啟動



https://medium.com/@icarus4/speed-up-your-rails-project-boot-time-4d88c075f75d

2017年11月7日 星期二

前端怎麼透過 API 送 file 到後端比較好呢?

這幾天在做前端的 form,遇到了不知道該怎麼樣讓 JS 透過 API 送 file 比較好的問題,當然答案是「It depends」在這裡把我的想法記錄下來

送 file 的方法直覺想到會有幾種:

https://imququ.com/post/four-ways-to-post-data-in-http.html


基本上原生的 form 可以支援兩種方式:

* application/x-www-form-urlencoded
* multipart/form-data

但我想要讓我們的 API 都是走 application/json 的格式該怎麼辦呢?

另外一個方法是可以把 file encode 成 base64 的字串傳給後端

要了解每個方法的 pros and cons 可以看以下的討論串:

https://stackoverflow.com/questions/33279153/rest-api-file-ie-images-processing-best-practices

這次我還是選用 base64 走 application/json,原因跟上述很類似,因為

1. 可以讓所有 endpoint 都走 json
2. base64 是很好理解的格式,對 client 不太會造成太大困擾
3. 可以支援同時傳 file 和其他 input
4. performance 比較差沒錯,但可以接受




至於 JS client 轉 base64 就用 FileReader 就行了

```js
      const reader = new FileReader()
      reader.onload = (event) => {
        $.post({
          url: "/api/private/messages?order_token=aa325ea3-2f86-4866-865d-6c0771922a59",
          data: {
            message: {
              attachment: event.target.result
            }
          }
        }).done((data) => {
          console.log(data)
        })
      }
      reader.readAsDataURL(file)
```

2017年11月2日 星期四