文字列にするか、シンボルにするか、いったい何が違うのだ?

scaffoldのコードを追った時に、詳細に追ったつもりであったが、実は一つ理解できないことを曖昧なままにしていた。
Ruby on Railsのコードを見ていると頻繁に「:action => 'show'」のような形式*1のオプション設定の書き方が出てくる。意味としては「actionというキーは、showという値だよ。」ということになる。...のだが、悩んでしまうのは「'action' => 'show'」、「:action => :show」、「'action' => :show」と書いても、どれも同じように機能してしまうのだ...。
4通りの書き方が許されるのは、許容範囲が広くて良いのだが、それでは、:actionと'action'は全く同じものかというと、そういうことでもないようだ。'action'が文字列というのは素直に理解できるが、:actionはシンボルと呼ばれている。シンボルについて、分かる範囲でちょっと調べてみた。

シンボルとは何か*2

  • 例えば:actionシンボルは最初に利用した時に、一つのオブジェクトを生成*3して、そのオブジェクトを指し示すマークになる。
  • その後も同一のオブジェクトを指し示すマークであり続ける。
  • シンボルオブジェクトは文字情報を保持していて、その文字情報は不変である。

シンボルと対比した文字列

オブジェクトIDで確認してみる。
  • 'action'文字列も一つのオブジェクト*4だが、プログラムの中で利用する度に、新たなオブジェクトとして発生する。

irb*5で実験してみた。

irb(main):001:0> for i in 1..10
irb(main):002:1> p 'action'.object_id
irb(main):003:1> end
1337658
1337478
1337388
1337308
1337088
1336968
1336908
1336688
1336658
1336638

'action'文字列だと、このように10回とも違うobject_idなので、その都度、新たなオブジェクトが発生していることが分かる。
ちなみに:actionシンボルだと、以下のような結果になる。10回ともobject_idが同じだ。

irb(main):004:0> for i in 1..10
irb(main):005:1> p :action.object_id
irb(main):006:1> end
4002062
4002062
4002062
4002062
4002062
4002062
4002062
4002062
4002062
4002062
破壊的メソッドで変更可能な文字列
  • 文字列は変更可能*6なオブジェクト、シンボルなら文字情報が常に同じであることが保証される。
irb(main):012:0> a='ABC'
=> "ABC"
irb(main):013:0> b=a
=> "ABC"
irb(main):014:0> b.downcase 
=> "abc"
irb(main):015:0> p a
"ABC"
irb(main):016:0> p b
"ABC"
irb(main):017:0> b.downcase!
=> "abc"
irb(main):018:0> p a
"abc"
irb(main):019:0> p b
"abc"

このように、破壊的メソッドdowncase!を利用すると、変数bの内容はもちろん、同じオブジェクトを参照している変数aの値までも変更*7することが出来る。シンボルでは、このように変更することは不可能、文字情報が保証される。

文字列とシンボル、Ruby on Railsでは、どのようなスタンスで利用するべきか...。

これで、文字列とシンボルの違いは理解したつもりになったが、それではRuby on Rails環境ではどのような基準で使い分けるべきなのか...。いろいろなページを見ていると、いろいろな意見があり、どうするべきか本当に迷ってしまう。Ruby on Rails環境では、どちらも同じように機能する*8のだから、どのように書くか決めるのはコーディング規約を作るようなものだ。

自分自身の心の問題なので、今後は以下の方針で使い分けることにした。

  • ハッシュのキー*9にはシンボル、値には文字列を使う。
  • 上記以外は、なるべくシンボルを使う。(1文字入力が少ない、その都度objectを生成しないことにメリットを感じたため)

とは言ったものの、scaffoldが生成したコードを見ると、このルールに従ってないな...。

コントローラ softwares_controller.rbのverify
  verify :method => :post, :only => [ :destroy, :create, :update ],
         :redirect_to => { :action => :list }

自分流に修正してみた。シンボルを文字列にした。

  verify :method => 'post', :only => [ 'destroy', 'create', 'update' ],
         :redirect_to => { :action => 'list' }
モデル _form.rhtml
<%= error_messages_for 'software' %>

<!--[form:software]-->
<p><label for="software_title">Title</label><br/>
<%= text_field 'software', 'title', :size => 40  %></p>

<p><label for="software_description">Description</label><br/>
<%= text_area 'software', 'description', :cols => 40, :rows => 10  %></p>

<p><label for="software_url">Url</label><br/>
<%= text_field 'software', 'url', :size => 40  %></p>

<p><label for="software_keyword_id">Keyword</label><br/>
<%= collection_select :software, :keyword_id, Keyword.find(:all), :id, :name %></p>
<!--[eoform:software]-->

こちらも同じく自分流に。文字列をシンボルに直した。

<%= error_messages_for 'software' %>

<!--[form:software]-->
<p><label for="software_title">Title</label><br/>
<%= text_field :software, :title, :size => 40  %></p>

<p><label for="software_description">Description</label><br/>
<%= text_area :software, :description, :cols => 40, :rows => 10  %></p>

<p><label for="software_url">Url</label><br/>
<%= text_field :software, :url, :size => 40  %></p>

<p><label for="software_keyword_id">Keyword</label><br/>
<%= collection_select :software, :keyword_id, Keyword.find(:all), :id, :name %></p>
<!--[eoform:software]-->

scaffoldが作り出したお手本となるコードを果たしてこんな風に変更してしまって良いのだろうか?(scaffoldはどんな基準で文字列とシンボルを使い分けているのだろうか...。)最後の最後でちょっと疑問が残るなー。

*1:ハッシュと呼ばれるデータ形式

*2:Rubyのシンボルとは何かについては、こちらのページを参考にさせて頂いた。感謝です。

*3:symbolクラスのインスタンスで、文字情報としてactionを保持している。

*4:stringクラスのインスタンス

*5:ターミナルを起動して、irbと入力して実行すると、irb環境が始まる。対話型のRuby環境だ。irb環境を終了する時はctrlを押しながらz。

*6:文字列は破壊的メソッドを使うと自分自身を変化させることが出来る。この概念についてはこちらのページを参考にさせて頂いた。感謝です。

*7:同じオブジェクトを参照しているので当たり前の話だが...。

*8:Ruby on Rails以外ではシンボルのみOKとか、文字列しか使えないという状況はあると思う。それ以外のRuby環境は使ったことが無いので分からないが...。

*9:例えば、:action => 'show'では、actionがキー、showが値