Ruby tips

明示してなければたぶん 1.8.7 / 1.9.2 で動作するはず。

Rails については Rails tips 参照。

多重代入で一部の要素を捨てる

# 通常の多重代入
first, second, third = [1, 2, 3]

# 先頭の要素を捨てる
*, second, third = [1, 2, 3]
*, third = [1, 2, 3]

# 末尾の要素を捨てる
first, second, * = [1, 2, 3]
first, * = [1, 2, 3]

# 中間の要素を捨てる
first, *, third = [1, 2, 3]

正規表現でキャプチャした部分を取得する

$~ あるいは Regexp.last_match は、次に match を呼ぶと上書きされ、また、$1 や $~[1] は可読性も悪いので、即座に変数に移してしまったほうがよい。$~.to_a だと、最初の要素はマッチした文字列全体なので、多くの場合不要。$~.captures を使うとキャプチャした部分のみの配列が得られる。。

# encoding: UTF-8
"2011年10月9日" =~ /(\d+)年(\d+)月(\d+)日/
year, month, day = $~.captures

String#inspect で16進数表記を返す

バイナリを扱うプログラムの開発時などに。16バイト毎に改行します。

class String
  def inspect
    s, l = '', ''
    self.each_byte do |b|
      l << b.to_s(16).upcase.rjust(2, '0') + ' '
      if l.size >= 16 * 3
        s << l.strip + "\n"
        l = ''
      end
    end
    s << l.strip if l != ''
    s
  end
end
# Usage
p 'labocho' #=> 6C 61 62 6F 63 68 6F

Base64 エンコード / デコード

エンコード

# Base64 Encoder
# Usage: ruby base64_encoder.rb binary_file
print [ARGF.binmode.read].pack("m").gsub(/[\r\n]/, '')

デコード

# Base64 Decoder
# Usage: ruby base64_decoder.rb base64_file
STDOUT.binmode.print ARGF.binmode.read.unpack("m")[0]

IO#binmode は Windows 環境で必要 (別の環境では無視される)。

相対パスで require するイディオム

# 同じディレクトリの foo.rb を require
# "/../foo" としてしまうと、絶対パスになり、ルートの 1 階層上になってしまうので注意 (ルートの 1 階層上はないのでルートになる)
require File.expand_path "../foo", __FILE__

# すこし冗長だが、こっちのほうがわかりやすいかも
require File.expand_path "foo", File.dirname(__FILE__)
require File.expand_path File.dirname(__FILE__) + "/foo"

定数の探索

モジュール / クラス内の定数 (もちろんモジュール / クラスも含む) を、外側のモジュール / クラス名::定数名 で参照できる。クラスの定義 / オープン時にもこの記法は使えるが、素朴に module / class 文をネストした場合と定数の探索範囲が異なる。

モジュールを名前空間として使う場合、同じ名前空間内のクラスを参照するときに名前空間名を省略したいので、module / class 文のネストに統一したほうがよさそう。

# モジュールで定数を定義
module M
  MODULE_CONSTANT = "MODULE_CONSTANT"
end

# ネストして記述した場合、外側のモジュール / クラスの定数が探索される
module M
  class C
    puts MODULE_CONSTANT # MODULE_CONSTANT
  end
end

# ::で記述した場合、外側のモジュール / クラスの定数が探索されない
class M::C
  puts MODULE_CONSTANT # NameError: uninitialized constant M::C::MODULE_CONSTANT
end

Ruby リファレンスマニュアルの記述を参照すると、定数の探索は次の順で行われる。

  1. そのモジュール / クラス
  2. ネストしていれば外側のモジュール / クラス
  3. 継承元のクラスを近い方から

トップレベルで定義した定数は Object クラスに属するので、3 の最後に探索される。 2 の「ネストしていれば」は、定数を参照するときのコンテキストが module / class 文でネストしていれば、の意味。定数の定義時は module / class 文でネストしていようが、:: を使っていようが関係ない。

instance_eval と class_eval (module_eval)

Object#instance_eval とは別に Module#class_eval が定義されている。クラス / モジュールをレシーバとする場合の挙動の違いをみる。

なお、Module#class_evalModule#module_eval のエイリアスだが、ここでは個人的な好みで class_eval で統一する。

まず instance_evalclass_eval ともに self はクラス自身を指す。

String.instance_eval do
  p self # => String
end

String.class_eval do
  p self # => String
end

違いは def でメソッドを定義したときの挙動。

# instance_eval ではクラスの特異メソッドが定義される
# (class << String ... end と同様)
String.instance_eval do
  def foo
    puts "foo"
  end
end
String.foo # => foo

# class_eval ではインスタンスメソッドが定義される
# (class String ... end と同様)
String.class_eval do
  def bar
    puts "bar"
  end
end
String.new.bar # => bar

なお、define_methodClass のインスタンスメソッドなので self に依存する。 self は同じなので、どちらもインスタンスメソッドが定義される

String.instance_eval do
  define_method :baz do
    puts "baz"
  end
end
String.new.baz # => baz

String.class_eval do
  define_method :foobar do
    puts "foobar"
  end
end
String.new.foobar # => foobar

class ... end 文と同じように扱えるので、普通は class_eval を使った方がわかりやすい。

instance_exec と class_exec (module_exec)

Object#instance_execModule#class_exec はそれぞれ instance_evalclass_eval と同じように動作するが、引数をとり、実行するブロックに渡すことができる。

クラスの利用者からブロック / Proc を受け取って、しかるべき時にインスタンス / クラスのコンテキストで評価する、というようなケースで引数を渡せるので便利。

なお、例によって Module#class_execModule#module_exec のエイリアス。

class Person
  attr_accessor :name
  
  def self.register_greeting_proc(&block)
    @block = block
  end
  
  def self.block
    @block
  end
  
  def greeting(message)
    instance_exec(message, &self.class.block)
  end
end

Person.register_greeting_proc do |message|
  # instance_exec により self が Person のインスタンスになるので name を呼べる
  # また instance_exec の引数を仮引数 message で受け取れる
  puts "#{message}, my name is #{name}"
end

john = Person.new
john.name = "John"
john.greeting "Hello" # => Hello, my name is John

false をとりうる変数と nil ゲートに関する注意

a ||= b や a = b if b のような、nil ゲートはよく使うイディオムだが、false をとりうる変数について使用する際には注意が必要。

class Unexpected1
  attr_writer :flag

  def flag
    return @flag ||= true
  end
end

u = Unexpected1.new
u.flag # => true
u.flag = false
u.flag # => true

ここでは @flag が設定されていなければ (nil なら) true をセットして返すことを想定しているが、flag= で false を代入しても、@flag は falsy であるため、再度 true をセットして返してしまう。

return @flag.nil? ? (@flag = true) : @flag

のように明確に nil かどうかをみる必要がある。

class Unexpected2
  attr_accessor :flag
    
  def initialize
    @flag = true
  end

  def set(hash)
    @flag = hash[:flag] if hash[:flag]
  end
end

u = Unexpected2.new
u.flag # => true
u.set(:flag => false)
u.flag # => true

ここでは、hash[:flag] があれば (nil でなければ) @flag にセットしているが、hash[:flag] が false なら if 節は成立しないため、@flag に false がセットされることはない。

@flag = hash[:flag] if hash.has_key?(:flag)

のように、明確にそのキーの値があるかを調べた方がよい (この方法なら nil が指定されていても調べられる)。

YAML や JSON のような構造のオブジェクトに対して再帰的に処理を加えるイディオム

def foo(obj)
  case obj
  when Array
    obj.map{|e| foo(e)}
  when Hash
    hash = {}
    obj.each do |k, v|
      hash[k] = foo(v)
    end
    hash
  else
    # なんらかの処理
    obj
  end
end

YAML から XML への変換

active_support/core_ext で Hash#to_xml が追加されるので、それを使う。

require "active_support/core_ext"
require "yaml"

puts YAML.load_file(ARGV.shift).to_xml

Kernel#open / IO.open の第 2 引数でのエンコーディング指定

Kernel#open あるいは IO#open では、第 2 引数にモードと外部・内部エンコーディングを指定できる。モードについては Kernel#open に詳しく記述されているが、エンコーディングについてはどのような文字列が指定できるか言及されていない。

実装は C で書かれてて読むのが大変そうだったので、Rubinius のソース rubinius/kernel/common/io19.rb をみると、IO#initialize から IO#set_encoding を呼んでて、エンコーディング文字列を Encoding.find に渡して Encoding オブジェクトを得ている。

Encoding.find について調べてみると rubinius/kernel/common/encoding.rbEncoding::EncodingMap (これは CRuby にはない) のキーと一致したものを返している。このさい、キーはシンボルなので文字列に変換した上で、両方 upcase して比較している。

# rvm use rubinius
# ruby -X19
Encoding::EncodingMap.keys.map{|k| k.to_s.upcase}.sort == Encoding.name_list.map{|n| n.upcase}.sort # => true

だったので、Encoding.name_list に含まれる値なら Encoding.find で探せて、open の第2引数のエンコーディング文字列として使える。比較の際 upcase してるため case-insensitive であり、shift_jis でも SHIFT_JIS でも Shift_JIS でもいいが、アンダースコアとハイフンは区別するので shift-jis ではだめ。

CRuby でも Encoding.name_list.each{|n| Encoding.find(n) } で例外が起こらないことを確認した。

Time#strftime

よく使う ISO8601 や RFC2822 については、標準添付ライブラリ time を使う。

require "time"
Time.new(2012, 1, 1).iso8601 #=> "2012-01-01T00:00:00+09:00"
Time.new(2012, 1, 1).rfc2822 #=> "Sun, 01 Jan 2012 00:00:00 +0900"

一覧 (分類別にソート)

分類 表記 意味
世紀 %C 世紀 (2009年であれば 20)
%Y 西暦を表す数
%y 西暦の下2桁(00-99)
%B 月の名称(January, February … )
%b 月の省略名(Jan, Feb … )
%h %b と同等
%m 月を表す数字(01-12)
%U 週を表す数。最初の日曜日が第1週の始まり(00-53)
%V ISO 8601形式の暦週 (01..53)
%W 週を表す数。最初の月曜日が第1週の始まり(00-53)
%d 日(01-31)
%e 日。一桁の場合、半角空白で埋める ( 1..31)
曜日 %A 曜日の名称(Sunday, Monday … )
曜日 %a 曜日の省略名(Sun, Mon … )
曜日 %u 月曜日を1とした、曜日の数値表現 (1..7)
曜日 %w 曜日を表す数。日曜日が0(0-6)
午前・午後 %P 午前または午後(am,pm)
午前・午後 %p 午前または午後(AM,PM)
%H 24時間制の時(00-23)
%I 12時間制の時(01-12)
%k 24時間制の時。一桁の場合、半角空白で埋める ( 0..23)
%l 12時間制の時。一桁の場合、半角空白で埋める ( 0..12)
%M 分(00-59)
%S 秒(00-60) (60はうるう秒)
秒未満 %L ミリ秒 (000.999)
秒未満 %N 秒の小数点以下。桁の指定がない場合は9桁 (ナノ秒)、%6N: マイクロ秒 (6桁)、%3N: ミリ秒 (3桁)
タイムゾーン %Z タイムゾーン
タイムゾーン %z タイムゾーン。UTCからのオフセット (例 +0900)
通算日 %j 年中の通算日(001-366)
通算秒 %s 1970-01-01 00:00:00 UTC からの経過秒
エスケープ %n 改行 (\n)
エスケープ %t タブ文字 (\t)
エスケープ %% %自身
組み合わせ %c 日付と時刻
組み合わせ %D 日付 (%m/%d/%y)
組み合わせ %F %Y-%m-%d と同等 (ISO 8601の日付フォーマット)
組み合わせ %R 24時間制の時刻。%H:%M と同等。
組み合わせ %r 12時間制の時刻。%I:%M:%S %p と同等。
組み合わせ %T 24時間制の時刻。%H:%M:%S と同等。
組み合わせ %v VMS形式の日付 (%e-%b-%Y)
組み合わせ %X 時刻
組み合わせ %x 日付

%title% ------- プログラマのための Ruby 入門。簡潔で正確。この本を読めばリファレンスマニュアルが理解できるようになり、かなりのことができるようになる。最初にこの本を読んでよかった。 [%url% %mediumimage%] [%url% %title% / %author% 著. %publisher%, %publishedyear%, %pages%p.] %title% ------- マニアックに見えるが、Ruby をちゃんと使うためには必須と思える内容。Ruby におけるクラス/モジュールとはどんなものなのか、特異メソッド/クラスとはなんなのか、Rails の魔法はどのようにできているのかが理解できる。抽象的な概念も順を追ってやさしく解説してくれる。 [%url% %mediumimage%] [%url% %title% / %author% 著. %publisher%, %publishedyear%, %pages%p.]