Help us understand the problem. What is going on with this article?

Railsチュートリアル 第4章 - Rails風味のRubyを学ぶ…ハッシュとシンボル

ハッシュとは

一言で言えば、「インデックス(添字)として、整数(スカラー値)以外のデータ型を用いる事ができる配列」のことです。

Rubyでは「ハッシュ」という語が用いられていますが、以下のような別名も存在します。

  • 連想配列(Perlなど)
  • マップ(Javascriptなど)
  • 辞書(Pythonなど)

ハッシュを定義するには

以下は各要素を角カッコ[]を使って定義する方法です。

>> user = {}  # {}は空のハッシュ
=> {}
>> user["first_name"] = "Michael"  # キー…"first_name"、値…"Michael"
=> "Michael"
>> user["last_name"] = "Hartl"  # キー…"last_name"、値…"Hartl"
=> "Hartl"
>> user["first_name"]  # ハッシュの要素へは、配列の要素と類似する方法でアクセスできる
=> "Michael"
>> user  # ハッシュのリテラル表記
=> {"first_name"=>"Michael", "last_name"=>"Hartl"}

以下はハッシュロケット=>を使って定義する方法です。角カッコを使った定義よりも簡単な書き方です。Rubyにおいては、ハッシュの最初と最後にスペースを入れる記法が好まれているそうです。

>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"first_name"=>"Michael", "last_name"=>"Hartl"}

シンボル

シンボルとは、ソースコード上の文字列と内部処理に使う一意の数値を1対1に割り付けたものです。「シンボルとは何かを端的に説明するとどうなるか」というテーマについて、先人は以下のように言及しています。

  • 「文字列の皮を被った数値」( @Kta-M さん)
  • 「プログラム内の名前という概念を表すオブジェクト」( @yugui さん)

参考記事:Rubyの文字列とシンボルの違いをキッチリ説明できる人になりたい

>> :foobar.object_id
=> 7768988

>> a = :foobar
=> :foobar  # => 7768988
>> b = :foobar
=> :foobar  # => 7768988
>> a.equal?(b)
=> true

>> a = "foobar"
=> "foobar"  # => 47206601248540
>> b = "foobar"
=> "foobar"  # => 47206601227140
>> a.equal?(b)
=> false

文字列式は、同じ文字列リテラルを指すものであっても、評価されるたびに違うオブジェクトを生成します。一方で、同じシンボルに対しては、何度評価されても常に同じオブジェクトを返します。その違いは、equal?メソッドの戻り値の違いにも現れています。

ハッシュのキーとしてシンボルを用いる

>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name]  # :nameというキーに対応する値
=> "Michael Hartl"
>> user[:password]  # 未定義のキーに対する値
=> nil
>> user["password"]
=> nil

Rubyのハッシュでは、一般に、キーとしてシンボルを用います(「名前という概念を定義するための存在」というシンボルの存在意義からして自然な成り行きですよね)。定義済みのハッシュの未定義のキーに対する値を参照しに行った場合、nilを返します。

>> user = { name: "Michael Hartl", email: "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}

実用的には、上記のような「キーを表すシンボルとハッシュロケットの組を、シンボル名とコロンに置き換えた表記」も広く用いられます。Pythonなどで見る表記に似ていますね。

ハッシュのハッシュ

ハッシュの値として、配列や別のハッシュを与えることも可能です。Webレスポンス、JSON、YAMLなどを扱う場合に頻出するデータ構造です。Railsチュートリアル 第4章では、「ハッシュのハッシュ」を扱っています。

>> params = {}  # 空のハッシュを定義する
=> {}
>> params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
=> {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
>> params                                              
=> {:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
>> params[:user][:email]
=> "mhartl@example.com"

ハッシュに対してeachメソッドを実行し、その結果をブロックで受ける

以下は、ハッシュに対しeachメソッドを実行し、その結果をブロックで受けて実行する例です。

>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}

>> flash.each do |key, value|
?>   puts "Key #{key.inspect} has value #{value.inspect}"
>> end
Key :success has value "It worked!"
Key :danger has value "It failed."
=> {:success=>"It worked!", :danger=>"It failed."}

ハッシュに対するeachメソッドをブロックで受ける場合、ブロックの変数が2つあることがポイントですね。キーに対応する変数と、値に対応する変数の2つです。ハッシュに対するeachメソッドは、「キーと値のペア」を単位として行われるのです。

inspectメソッドと、モジュール関数p

inspectメソッドは、オブジェクトを人間が読める形式に変換して返すメソッドです。「人間が読める形式」が何を指すかは、オブジェクトの型によって様々です。

>> puts (1..5).to_a.inspect
[1, 2, 3, 4, 5]
=> nil

>> puts :name, :name.inspect
name
:name
=> nil

>> puts "It worked!", "It worked!".inspect
It worked!
"It worked!"
=> nil

puts Time.new.inspect
2019-09-12 13:26:36 +0000
=> nil

inspectメソッドと関係して、pというモジュール関数1 2が存在します。Rubyリファレンスマニュアルによれば、「引数のinspectメソッドの返り値と改行を順番に出力します」とのことです。戻り値がnilであるputsとは異なり、pの戻り値は「引数のオブジェクト」となります。以下、pの実行例です。

>> p :name
:name
=> :name

> p "It worked!"           
"It worked!"
=> "It worked!"

>> p Time.new
2019-09-12 13:29:48 +0000
=> 2019-09-12 13:29:48 +0000

演習 - ハッシュとシンボル

1.1 キーが’one’、’two’、’three’となっていて、それぞれの値が’uno’、’dos’、’tres’となっているハッシュを作ってみてください。

ハッシュロケットを使った記法でハッシュを作ってみます。

>> es = { 'one' => 'uno', 'two' => 'dos', 'three' => 'tres' }
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
>> p es
{"one"=>"uno", "two"=>"dos", "three"=>"tres"}
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}

ハッシュの変数名はesとしてみました。

1.2 その後、ハッシュの各要素をみて、それぞれのキーと値を"’#{key}’のスペイン語は’#{value}’"といった形で出力してみてください。

1.1の続きです。eachメソッドとブロック記法を用います。Rails Consoleで日本語入力が可能な環境を整備するのが面倒だったので、メッセージは英語です。

>> es.each do |eng, esp|
?>   p "#{eng} is #{esp} in Spanish."
>> end
"one is uno in Spanish."
"two is dos in Spanish."
"three is tres in Spanish."
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}

2.1 person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値 (名前など) を入力してください。

>> person1 = { :first => "Michael", :last => "Hartl" }
=> {:first=>"Michael", :last=>"Hartl"}

>> person2 = { :first => "Foo", :last => "Bar" }
=> {:first=>"Foo", :last=>"Bar"}

>> person3 = { first: "Hoge", last: "Fuga" }
=> {:first=>"Hoge", :last=>"Fuga"}

person3のみ、省略記法を用いて定義してみました。

2.2 その後、次のようなparamsというハッシュのハッシュを作ってみてください。

1.) キーparams[:father]の値にperson1を代入
2). キーparams[:mother]の値にperson2を代入
3). キーparams[:child]の値にperson3を代入
最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)

>> params = { father: person1, mother: person2, child: person3 }
=> {:father=>{:first=>"Michael", :last=>"Hartl"}, :mother=>{:first=>"Foo", :last=>"Bar"}, :child=>{:first=>"Hoge", :last=>"Fuga"}}

>> params[:father][:first].equal?(person1[:first])
=> true  # => 47206604256140

>> params[:child][:last].equal?(person3[:last])
=> true  # => 47206604128880
  • params[:father][:first]とperson1[:first]
  • params[:child][:last]とperson3[:last]

以上はオブジェクトとしても一致しています。

3. userというハッシュを定義してみてください。このハッシュは3つのキー:name、:email、:password_digestを持っていて、それぞれの値にあなたの名前、あなたのメールアドレス、そして16文字からなるランダムな文字列が代入されています。

今回は、空のハッシュから順を追って定義してみることにします。

>> user = {}
=> {}

>> user[:name] = 'Foo bar'
=> "Foo bar"

>> user[:email] = 'example@example.jp'
=> "example@example.jp"

>> user[:password_digest] = [*'0'..'9', *'a'..'z', *'A'..'Z'].shuffle[0..15].join
=> "DjaIs4nV1Jh6OYKt"

>> p user
{:name=>"Foo bar", :email=>"example@example.jp", :password_digest=>"DjaIs4nV1Jh6OYKt"}
=> {:name=>"Foo bar", :email=>"example@example.jp", :password_digest=>"DjaIs4nV1Jh6OYKt"}

「16文字からなるランダムな文字列」というお題を出されたので、[*'0'..'9', *'a'..'z', *'A'..'Z'].shuffle[0..15].joinという実装をしてみました。これは、「英大文字・英小文字・数字各1文字ずつから成る配列をシャッフルした上で、先頭15要素を取り出し、連結して文字列にしたものを返す」というメソッドチェーンです。

なお、例えば[*'a'..'z']は、内部的には('a'..'z').to_aを呼び出すことになります。

4. Ruby API (訳注: もしくはるりまサーチ) を使って、Hashクラスのmergeメソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。

演習コード
  { "a" => 100, "b" => 200 }.merge({ "b" => 300 })

以下の記述がポイントですね。

self と others に同じキーがあった場合はブロック付きか否かで 判定方法が違います。ブロック付きのときはブロックを呼び出してその返す値を重複キーに対応する値にします。ブロック付きでない 場合は常に others の値を使います。

ブロックらしき記法は見当たらないので、mergeメソッドの戻り値は{ "a" => 100, "b" => 300 }になるかと思います。

結果
>>  { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
=> {"a"=>100, "b"=>300}

予想通りでした。

余談 - ブロック付きのmergeメソッド

Rubyリファレンスマニュアル(るりま)におけるHash#mergeの解説には、以下の記述があります。

ブロック付きのときはブロックを呼び出してその返す値を重複キーに対応する値にします。

モノは試しなので、実際にやってみましょう。

>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 }) do |key, foo_val, bar_val|
?>   foo_val - bar_val
>> end
=> {"a"=>100, "b"=>-100}

200-300は-100です。重複するキー"b"の値に対し、確かにブロック内容通りの演算がされていますね。

Railsのstylesheet_link_tagメソッドの呼び出し

Railsチュートリアルには、以下のコードが登場します。

<%= stylesheet_link_tag 'application', media: 'all',
                                       'data-turbolinks-track': 'reload' %>

Railsチュートリアルによれば、この部分は、Railsのstylesheet_link_tagメソッドを2つの引数で呼び出しています。重要なのは以下の点です。

  • メソッド呼び出しの丸括弧()が省略されている
    • p({})のような特別な場合3を除き、Rubyでは、メソッドの引数を明示する丸括弧は必須ではない
  • ハッシュの波括弧{}が省略されている
    • ハッシュがメソッドの最後の引数である場合、波括弧{}は省略できる
  • 式が2行にわたっている
    • Rubyでは、式を完了させない状態で改行4させれば、複数行にわたる式を記述できる

引数が3つあるように見えますが、media: 'all', 'data-turbolinks-track': 'reload'というのは、実は一つのハッシュであり、これで一つの引数です。

よって、例えば以下の2つの式は等価となります。

stylesheet_link_tag 'application', media: 'all',
                                       'data-turbolinks-track': 'reload'
stylesheet_link_tag('application',
                    {media: 'all',
                    'data-turbolinks-track': 'reload' })

  1. ひとまず「メソッドと同様に実行できるが、レシーバを必要としないもの」と認識しています。putsもモジュール関数ですね。 

  2. Railsチュートリアルにおける「pメソッドというショートカット」という言い回しには、なんとなく違和感がありました。 

  3. p({})の場合、メソッド呼び出しの丸括弧が明示的に記述されていなければ、{}が引数として解釈されません。 

  4. 行の最後に演算子(+など)やカンマ,などがある場合、改行があっても式は完了しません。 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away