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

Ruby初心者(つまり昨日の僕)に伝えたいRubyの7つのハマり所

Ruby

書くのが楽しいプログラミング言語であるRubyですが、それでも「落とし穴」がいくつも存在します。

その中から私が最近ハマった落とし穴を7つ紹介します。

シンボルと文字列は異なる

Rubyには「文字列(String)」("a" とか 'Hello world!')の他に、文字列に似た「シンボル(Symbol)」(:aとか:hello_worldとか)があります。*1。両者は異なる型なので、例えば 'a' != :a となります。

そのため、キーがシンボルであるハッシュ(Hash)から、文字列を使って値を取り出そうとするとnilが返されます。

h = { :ja => '日本語', :en => '英語', :fr => 'フランス語' }

p h['ja'] # => nil
p h[:ja] # => "日本語"

ちなみに、シンボルはハッシュのキーとしてよく使われるため、=> を使わない短縮記法があります。

# これは・・・
h = { :ja => '日本語', :en => '英語', :fr => 'フランス語' }

# こうも書ける
h = { ja: '日本語', en: '英語', fr: 'フランス語' }

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)があります。*2。両者は異なる型なので、例えば 'a' != :a となります。

ハッシュのキーにはシンボルがよく使われますが、例えばRailsparams(HTTPのクエリパラメータ)のキーは文字列です。 したがって、文字列を使ってparamsから値を取り出そうとするとnilが返されます。

class XYZController
  def search
    params[:lang] # => nil
  end
end

なお、Railsはハッシュのキーをシンボルに変換するHash#symbolize_keys等の一連のメソッドを追加します。

また、JSON.parseにはJSONのキーをシンボルに変換するsymbolize_namesパラメータがあります。

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)があります。*3。両者は異なる型なので、例えば 'a' != :a となります。

そのため、シンボルを文字列と連結したり、文字列のメソッドを呼びだそうとするとエラーになります。

a = 'hello'
b = :world

a + b # => TypeError: no implicit conversion of Symbol into String

b.tr 'o', 'x' # => NoMethodError: undefined method `tr' for :world:Symbol

このような時は.to_s.to_symで、文字列とシンボルを相互に変換します。

また、文字列連結の代わりに式展開を使うこともできます。 式展開はシンボルだけでなく数値や他の型つかえますし、文字列連結を使うよりも可読性が高くなります。 そのため、Rubyでは文字列連結を使う機会はあまり多くありません。

# これよりも
a + ' ' + b

# こうするべき
"#{a} #{b}"

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)が・・・すみません、これで最後にします。

method_missingの引数はシンボルです。実際のところ、シンボルに文字列操作を施す機会はメタプログラミング以外はあまりありません。

なので、method_missingでこんなコードを書くとエラーになります。

def method_missing(name, *args)
  # エラーになるコード
  if name.end_with?('!')
     # ...
  end
  super(name, *args)
end

privateメソッドをクラス外から呼び出すとmethod_missingが呼ばれる

黒魔術だなんだと言われつつ使わざるをえないmethod_missing について。実はmethod_missingが呼ばれるのはメソッドが無い場合だけではないとことです。

呼ぼうとしたメソッドprotectedprivateであった場合もmethod_missingは呼ばれます。

class Foo
  def method_missing(name)
    p name
  end

  private

  def greeting
    p 'hello'
  end
end

foo = Foo.new
foo.greeting # => :greeting

だから、method_missingは黒魔術なのであって、method_missingなんて使うのは止めましょう。

Module#define_method が作るメソッドはpublicになるとは限らない

黒魔術でもうひとつ。メソッドを動的に定義するdefine_methodは、privateの後ではprivateなメソッドを定義します。

class FooController
  def index
    # 普通のメソッド
  end

  private # あとから foo_params を追加!

  def foo_params
    #...
  end

  %w(name email age).each do |name|
    define_method "show_#{name}".to_sym do
      # ...
    end
  end

なので、「ゴチャゴチャした処理は後ろに書こう」とクラスの後ろでdefine_methodを書いていて、 途中にprivateなメソッドを追加したりすると、思わぬ動作になります。

ちなみに「privateの後ではprivateになる」のはattr_readerなども同様です。

Structはキーワード引数を取らない

最後に、真面目に罠だと思うことを。

たまに便利なStructは、実はコンストラクタにキーワード引数を使えないことです。 間違ってキーワード引数を使うと、それらは1個のハッシュを渡したかのように扱われます。

Dog = Struct.new(:name, :age) # => Struct::Dog

# キーワード引数は使えない
Dog.new(name: 'ポチ', age: 1) # => #<struct Struct::Dog name={:name=>"ポチ", :age=>1}, age=nil>

# ハッシュを渡したかのように扱われる。
Dog.new({name: 'ポチ', age: 1}) # => #<struct Struct::Dog name={:name=>"ポチ", :age=>1}, age=nil>

# なのでStructでは通常の呼び出しをするしかない
Dog.new('ポチ', 1) # => #<struct Struct::Dog name="ポチ", age=1>

正直な所、これは仕様のバグだと思います。後方互換性が失われるので直しにくいと思いますが。

*1:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

*2:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

*3:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。