読者です 読者をやめる 読者になる 読者になる

メタプログラミングRuby その7 [学習メモ]

プログラミング 学習メモ Ruby

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

4.4 instance_eval

class 定義の際に明確に指定しない限り class のインスタンス変数や private メソッドにはアクセスできないことになっている。
しかし instance_eval にブロックを渡すことでこれらの壁を越えることができる。

class Test
  def initialize
    @v = 1
  end
end

obj = Test.new

obj.instance_eval do
  @v
# -> 1
end

このようにいともたやすくカプセル化を破壊することができる。

4.5 呼び出し可能オブジェクト
何気なく使っていると気づかないが、ブロックはオブジェクトではない。
ブロックを変数に保存するためには Proc オブジェクトに変換する必要がある。

inc = Proc.new { |x| x + 1 }
inc.call(2)
# -> 3

& オペレータを使うと、ブロックを Proc に変換し、他のメソッドに渡すことが出来るようになる。

def cal(a, b)
  yield(a, b)
end

def do_cal(a, b, &operation)
  cal(a, b, &operation)
end

do_cal(2, 3) { |x, y| x + y }
# => 5

また、 Proc オブジェクトに & オペレータをつけることでブロックに戻すことも出来る。

p = Proc.new { |x| x > 2 }
[1, 2, 3].map &p
# => [false, false, true]

4.5.3 Method オブジェクト
ブロックとメソッドは構造が殆ど同じで、それはこれらが互いに変換可能であることを意味している。
前の章で見た define_method はブロックからメソッドを作成するメソッドだった。
また、メソッドをブロックのようにオブジェクトとして取得することも出来る。

class MyClass
  def initialize(value)
    @x = value
  end

  def my_method
    @x
  end
end

obj = MyClass.new(1)
m = obj.method :my_method
m.call
# => 1

メソッドがオブジェクトに出来るとは知らなかった。
この辺りは Lisp っぽい使い方が出来そうだ。

Slimv を Clojure に対応させるまで [Vim]

メモ Vim Clojure

環境: OS X 10.9.5

lisp 族用の vim プラグインに slimv と言う便利な物があり、補完からインデント、色分けまで一通りやってくれる。
これさえあれば他にプラグインはいらないくらいで、common lisp だけではなく schemeclojure にも対応している。
対応している、はずだったがちょっと clojure をいじろうと思って試してみた所全く反応しなかった。
どうやら mac のデフォルト vim が古すぎたらしく(7.3 2010 Aug 15) clojure を認識できなかったらしい。
そこで、そこそこ新しいであろう vim をインストールする。

brew install vim

バージョン確認

vim --version
-> VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jul  3 2016 19:10:20)

vi の方のリンクが古いままだったので、alias を作る。

# .bash_profile
alias vi="vim"

これで良し、と思いきや clojure ファイルを読み込むとエラーが発生した。

Error detected while processing /Users/name/.vim/.bundle/slimv.vim/ftplugin/lisp/slimv-lisp.vim

vim のバージョンアップに伴って vim script の書き方も変わっているらしく、これまでの slimv ではエラーになってしまうようだ。
エラーが出るのは起動時だけでそれ以後は普通に動くようだが、いかにもうっとうしい。
neobundle update などをやってみても変わらず、今 NeoBundle 自体が非推奨のためリポジトリのアップデートもされていないのだろうか。
そこで直接 slimv を置き換える。
ソースコードgithub にあるので、古い slimv.vim を削除し

git clone https://github.com/kovisoft/slimv .vim/.bundle/slimv.vim

で置き換える。パスは私の環境なので、適宜読み替える必要があるかもしれない。

vi test.clj

インデント、色分け、補完すべて完璧だ。心置きなく clojure を使える。

あとそのうち neobundle から dein に乗り換えよう。

メタプログラミングRuby その6 [学習メモ]

プログラミング 学習メモ Ruby

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

水曜日: ブロック

ruby おなじみのブロックはコードと束縛の固まりで、{ } や do ~ end で定義される。
each 族でよく使われるあれだ。

4.3 ブロックはクロージャ

ブロックには束縛が含まれる。つまり

def test
  name = "Alice"
  yeild
end

name = "Bob"
test { |name| "hello, #{name}!" }
# => "hello, Bob!"

のようになる。
ここでは test メソッドに渡されたブロックは name にローカルで定義された値を束縛しており、メソッド中の name が使われることはない。

4.3.3 スコープのフラット化

一般的に言って ruby のスコープは狭く、メソッドやクラスの外側の束縛にアクセスするのは結構面倒くさい。
もちろんそのおかげでコードが安全になるし、リファクタリングもしやすくなる訳なのだけれど、どうしてもスコープ外の束縛を使いたいときもある。
パッと思いつくのは引数として渡すと言うことだが、あまりにも不格好すぎる。
そこでメソッドでクラスやメソッドを定義することで、この問題をクリアできる。

out_var = "success"

Test = Class.new do
  puts "#{out_var} in the class!"

  define_method :test_method do
    "#{out_var} in the method!"
  end
end

複数のメソッドで変数を共有して、なおかつそれ以外からのアクセスは防ぎたい場合このやり方が使える。

def define_counter
  shared = 0

  Kernel.send :define_method, :counter do
    shared
  end

  Kernel.send :define_method, :inc do |x|
    shared += x
  end
end

define_counter

counter
# => 0
inc(4)
counter
# => 4

おなじみのクロージャ利用法だ。

index:true ではインデックスは張られない [Rails]

メモ Rails

Rails 4.2.5 でカラムを追加する時にインデックスを張りたかったので、

# db/migrate/timestamp_add_name_to_persons.rb
class AddNameToPersons < ActiveRecord::Migration
  def change
    add_column :persons, :name, :string, index: true
  end
end

としたのだが、db/schema.rb をチェックするとどうもうまく張られていないようだった。
表示されないだけで実際は張られているかもしれないと思いコンソールから調べてみると

Person.connection.index_exists?(:persons, :name)
# => false

やはり張られていない。

どうも index:true ではインデックスは張られないようだ。
参考: PSA: "index: true" in Rails migrations does not work as you'd expect - makandropedia

インデックスを張るときは add_index を使うとよい。

# db/migrate/timestamp_add_name_to_persons.rb
class AddNameToPersons < ActiveRecord::Migration
  def change
    add_column :persons, :name, :string
    add_index :persons, :name
  end
end

db/schema.rb とコンソールを確認して、OK!

メタプログラミングRuby その5 [学習メモ]

プログラミング 学習メモ Ruby

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

3.3 method_missing
ruby オブジェクトがメソッドを呼び出すと、継承チェーンに沿ってメソッドを探索し、見つからなければもとのオブジェクトの method_missing メソッドが呼び出される。
この辺りが他の言語でどうなっているのかは知らないが、ruby ではあくまで method_missing メソッドが呼び出されてからそのメソッドがエラーを返している。
そしてメソッドはオーバーライドが可能なので、method_missing も上書きが出来ると言うことになる。

class Dog
  def method_missing(method, *args)
    puts "called : #{method}(#{args.join(', ')})"
    puts "block" if block_given?
  end
end

hachi = Dog.new
hachi.meow(2) do
  # block
end

# -> called : meow(2)
#    block

method_missing をオーバーライドすることで、メソッドの呼び出しをここに集めることが可能になる。
ビットコインAPIruby から呼び出す際の手本としてこのやり方が使われていた。

# https://en.bitcoin.it/wiki/API_reference_%28JSON-RPC%29#Ruby
require 'net/http'
require 'uri'
require 'json'

class BitcoinRPC
  def initialize(service_url)
    @uri = URI.parse(service_url)
  end

  def method_missing(name, *args)
    post_body = { 'method' => name, 'params' => args, 'id' => 'jsonrpc' }.to_json
    resp = JSON.parse( http_post_request(post_body) )
    raise JSONRPCError, resp['error'] if resp['error']
    resp['result']
  end

# ...

以前見たときは何をしているのかがよく分からなかったが、ようやく理解できた。
ここでは BitcoinRPC オブジェクトが api に存在するメソッドを呼び出したとき、method_missing において捕捉され、外部のサーバにリクエストを投げ、結果かエラーを返している。
これにより個々の api に対応するメソッドを書く必要がなくなっている。

かなり過激なやり方だ。
method_missing のようなメソッドを書き換えるのはなにか聖人の墓を暴くような感じがするし、直感的に非効率なように思える。
その反面実行時にしか分からないような事にも対応できる柔軟性は見過ごせない。
api が数百有ってしょっちゅう変更されるような場合でも楽に対応できるだろう。
まさしく黒魔術だ。

Let's Encrypt で無料のSSL証明書を取得する[Ubuntu, Nginx]

メモ Linux Nginx

今や SSL(TLS) はデフォルトとなりつつあり、いちユーザとしてみるとすばらしいことだ。
しかし運営する側にしてみると安くないコストとなってのしかかってくる。
一件当たり年間数万円、それがいくつもあるとバカにならない。
かといって SSL 非対応にすると検索エンジンでの順位が下がり、ブラウザが警告を発し、
何よりセキュアではなくなりユーザの安全性や信頼性が低下する。
そこで仕方なく安くない認証料を支払っていたわけだが、最近いくつかの無料サービスが登場している。
今回はその中の1つ Let's Encrypt を使ってみることにした。
日本語の公式サイトも存在する。Let's Encrypt の使い方
もちろんこのサイト自体も Let's Encrypt で認証されている。

サーバサイドの環境は Ubuntu 16.04, nginx 1.10.0 で行った。
尚以下の手続はドメインの指定するサーバで行う必要がある。
example.com は証明書を取得するドメインで、適宜置き換えて欲しい。
まずはソースコードを入手

$ git clone https://github.com/certbot/certbot

必要なライブラリをインストールする。

$ cd certbot
$ ./certbot-auto

認証手続にはあらかじめ http, https 用のポートを開放しておく必要がある。
ドメインを指定して、クライアントを起動する。

./certbot-auto certonly --standalone -d example.com -d www.example.com

メールアドレスを入力して、規約に同意するとすぐに証明書が発行されダウンロードされる。

- Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem. Your cert
   will expire on 2016-08-22. To obtain a new version of the
   certificate in the future, simply run Certbot again.

nginx の場合は conf ファイルのパスをここに合わせる。

# /etc/nginx/nginx.conf
server {
        server_name www.example.com;
        listen 443 default_server;

        ssl on;
        ssl_certificate /etc/letsencrypt/archive/example.com/fullchain1.pem; # 公開鍵
        ssl_certificate_key /etc/letsencrypt/archive/example.com/privkey1.pem; # 秘密鍵
...

これで nginx で https 通信が行えるようになる。
証明書の期限は三ヶ月と短いが、これと同じ事を繰り返すことで更新ができる。
自動更新の方法もあるらしいが、まだ調べていない。
とにかく無料でSSL証明書が取得できた。
良い時代になったものだ。

メタプログラミングRuby その4 [学習メモ]

プログラミング 学習メモ Ruby

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

3章はメソッドに関する話題で、この本の肝とも言える部分だ。
Ruby のような動的な言語は実行時にメソッドを定義することができる。
初めはこれらの利点はわかりにくい。
漠然と Ruby を使っているとまず使わない機能だろうし、それで不便だと思うないだろう。
ただこれらをうまく使うことでメソッドの定義そのものすら省略することができるようになり、
多くの API を持つ外部のプログラムとやりとりするような状況では非常に有用になる。

3.2 動的メソッド

メソッドはドット記法だけではなく、 send を使って呼び出すことができる。

str = "String"
str.upcase
# -> "STRING"

str.send(:upcase)
# -> "STRING"

だからどうしたという感じだが、こうすることで実行時にメソッドを決定できる。
おかしな例になるが、

str = "String"
methods = [:upcase, :downcase, :to_sym]

methods.each do |m|
  p str.send(m)
end
# -> "STR" "str" :stR

のようにできる。
Lisp とは違い Ruby では関数はオブジェクトではないのでイテレータに入れることができない。
そこでシンボルと send を通すことで任意の関数を呼び出せるようになる。

send でメソッドが動的に呼び出せるが、動的に定義することもできる。

class String
  define_method :my_method do
    "hello, #{self}!"
  end
end

str = "Bob"
str.my_method
# -> "hello, Bob!"

ここも肝はシンボルを使い任意の名前が渡せるところだ。

class DS
  def initialize # データソースに接続

  def get_name(id) # ...
  def get_age(id) # ...
  # ...
end
data_source = DS.new

class Person
  def initialize(id, data_source)
    @id = id
    @data_source = data_source
    data_source.methods.grep(/^get_(.*)$/) { Person.define_component $1 }
  end

  def self.define_component(name)
    define_method(name) do
      @data_source.send "get_#{name}", @id
    end
  end
end

わかりにくいが data_source に存在する getter メソッドを呼び出すメソッドを動的に定義している。
こうすることで data_source に新たな getter メソッドが追加、あるいは削除されても Person クラスに変更を加える必要はなくなる。
目から鱗が落ちるようだが、慣れないうちはどの状況で使うべきか戸惑いそうだ。