4.3.1 配列と範囲演算子
配列を理解することは、ハッシュやRailsのデータモデルを理解するための重要な基盤
splitメソッドを使うと、文字列を自然に変換した配列を得ることができます。
>> "foo bar baz".split => ["foo", "bar", "baz"] >> "fooxbarxbaz".split('x') => ["foo", "bar", "baz"]
多くのコンピュータ言語の慣習と同様、Rubyの配列でもゼロオリジンを
採用しています。これは、配列の最初の要素のインデックスが0から始まり、
2番目は1…と続くことを意味します。添字のこと
a = [42, 8, 17] >> a[-1] => 17 # 配列の添字はマイナスにもなれる! >> a.last === a[-1] => true
配列の要素にアクセスするには角カッコ[]!
配列の内容を変更したい場合は、
そのメソッドに対応する「破壊的」メソッドを使います。
>> a => [42, 8, 17] >> a.sort! => [8, 17, 42] >> a => [8, 17, 42]
pushメソッド (または同等の<<演算子) を使って配列に要素を追加することもできます。
a.push(6)=>[42, 8, 17, 6] >> a << "foo" << "bar" # 配列に連続して追加する => [42, 8, 17, 6, 7, "foo", "bar"]
Rubyでは異なる型が配列の中で共存できる(上の場合は整数と文字列)。
文字列を配列に変換するのにsplitを使いました。
joinメソッドはこれと逆の動作で文字を連結できます。
>> a => [8, 17, 42, 6, 7, "foo", "var"] >> a.join => "8174267foovar" >> a.join(',') => "8,17,42,6,7,foo,var"
範囲 (range) は、配列と密接に関係しています。to_aメソッドを使って
配列に変換すると理解しやすいです。
>> 0..9 => 0..9 >> (0..9).to_a => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>> a = %w[foo bar baz quux] # %wを使って文字列の配列に変換 => ["foo", "bar", "baz", "quux"] >> a[0..2] => ["foo", "bar", "baz"]
インデックスに-1という値を指定できるのは極めて便利。-1を使うと、
配列の長さを知らなくても配列の最後の要素を指定することができ、
これにより配列を特定の開始位置の要素から最後の要素までを一度に
選択することができる。
>> a = (0..9).to_a => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >> a[2..(a.length-1)] # 明示的に配列の長さを使って選択 => [2, 3, 4, 5, 6, 7, 8, 9] >> a[2..-1] # 添字に-1を使って選択 => [2, 3, 4, 5, 6, 7, 8, 9]
字列に対しても範囲オブジェクトが使える。
>> ('a'..'e').to_a => ["a", "b", "c", "d", "e"]
演習
1:文字列「A man, a plan, a canal, Panama」を “, ” で分割して
配列にし、変数aに代入してみて。
a = "A man, a plan, a canal, Panama".split(', ')
2:今度は、変数aの要素を連結した結果 (文字列) を、変数sに代入して
みてください。
s = a.join => "A mana plana canalPanama"
3:変数sを半角スペースで分割した後、もう一度連結して文字列にしてください。
回文をチェックするメソッドを使って、変数sが回文ではないことを確認。
downcaseメソッドを使って、s.downcaseは回文であることを確認してください。
> s.gsub!(" ","") => "AmanaplanacanalPanama"
gsubは第1引数で指定したものを第2引数で全て置き換える。スペースを「無」にしたということ
def palindrome_tester(s) if s == s.reverse puts "It's a palindrome!" else puts "It's not a palindrome." end end :palindrome_tester palindrome_tester(s) s.downcase! def palindrome_tester(s = "amanaplanacanalpanama" ) if s == s.reverse puts "It's a palindrome!" else puts "It's not a palindrome." end end palindrome_tester 別解 def palindrome_tester(s) if s == s.reverse puts "It's a palindrome!" else puts "It's not a palindrome." end end :palindrome_tester palindrome_tester(s.gsub(" ","").downcase) 更に別解 def palindrome_tester(s) if s == s.reverse puts "It's a palindrome!" else puts "It's not a palindrome." end end :palindrome_tester puts palindrome_tester(s.split.join.downcase)
4:aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出して
みてください。同様にして、後ろから7番目の要素を取り出してみて。
f = ('a'..'z').to_a f[6] f[-7]
4.3.2 ブロック
配列と範囲はいずれも、ブロックを伴うさまざまなメソッドに対して
応答することができます。ブロックは、Rubyの極めて強力な機能であり、
かつわかりにくい機能でもあります。
>> (1..5).each { |i| puts 2 * i } 2 4 6 8 10 => 1..5
メソッドに渡されている{ |i| puts 2 * i }が、ブロックと呼ばれる部分
(1..5).each do |i| ?> puts 2*i >> end 2 4 6 8 10 => 1..5
両方同じ意味
ブロックには複数の行を記述できるよ(実際ほとんどのブロックは複数行)。
RailsチュートリアルではRuby共通の慣習に従って、短い1行のブロックには波カッコを使い、長い1行や複数行のブロックにはdo..end記法を使っている。
>> (1..5).each do |number| ?> puts 2 * number >> puts '--' >> end 2 -- 4 -- 6 -- 8 -- 10 -- => 1..5
今度はiの代わりにnumberを使っていることに注目。
この変数(ブロック変数) の名前は固定されていない。
ブロックは見た目に反して奥が深く、ブロックを十分に理解するためには
相当なプログラミング経験が必要です。
参考のため、mapメソッドなどを使ったブロックの使用例をいくつか挙げてみます。
ブロックとはメソッドに渡されている{と中身}がブロックと呼ばれる部分です。
do..endの間もブロックと呼ばれる|i|ように|で囲まれているものを
ブロック変数ともいう。
慣習的に短い1行の場合は波かっこを使って
複数行の場合はeach do..endのように複数行にまたがって記述される。
>> 3.times { puts "Betelgeuse!" } "Betelgeuse!" # 3.timesではブロックに変数を使っていない "Betelgeuse!" "Betelgeuse!" => 3 >> (1..5).map { |i| i**2 } # 「**」記法は冪乗 (べき乗) => [1, 4, 9, 16, 25]
mapメソッドは、渡されたブロック(1..5)を配列や範囲オブジェクトの
各要素に{ |i| i**2 } 対して適用し、結果を返す。[1, 4, 9, 16, 25]
>> %w[a b c] # %w で文字列の配列を作成 => ["a", "b", "c"] >> %w[a b c].map { |char| char.upcase } => ["A", "B", "C"] >> %w[A B C].map { |char| char.downcase } => ["a", "b", "c"]
↑の2つの例では、mapのブロック内で宣言した引数(char)に対して
メソッドを呼び出してるよ。こういったケースでは省略記法が一般的で、
次のように書くこともできる(この記法を“symbol-to-proc”と呼ぶ)。
a = ["A", "B", "C"] >> a.map { |cha| cha.downcase} => ["a", "b", "c"]
まあようするにこういうこと
>> %w[A B C].map { |char| char.downcase } => ["a", "b", "c"] >> %w[A B C].map(&:downcase) => ["a", "b", "c"]
Ruby on Rails独自の記法だったけども多くの人がこの記法を好むよう
になったので、今ではRubyのコア機能として逆輸入されているよ。
ブロックの例として、単体テストにも目を向けてみよう
test "should get home" do get static_pages_home_url assert_response :success assert_select "title", "Ruby on Rails Tutorial Sample App" end
テストコードにdoというキーワードがあることに気付き、そこからテストの
本体が「そもそもブロックでできている」ことに気付くことで
このtestメソッドは文字列 (説明文) “should get home”とブロックを引数にとり、
テストが実行されるときにブロック内の文が実行される、ということが理解できます。
↓の意味を説明せよ
(‘a’..’z’).to_a.shuffle[0..7].join
演習
1:範囲オブジェクト0..16を使って、各要素の2乗を出力してください。
(0..16).map{ |i| i**2 }
2:yeller (大声で叫ぶ) というメソッドを定義して。このメソッドは
文字列の要素で構成された配列を受け取り、各要素を連結した後、
大文字にして結果を返します。例えばyeller([‘o’, ‘l’, ‘d’])
と実行したとき、”OLD”という結果が返ってくれば成功です。
ヒント: mapとupcaseとjoinメソッドを使ってみましょう。
mapなしでやってみると
def yeller(a) a.join.upcase end puts yeller(['o', 'l', 'd'])
注意!先にjoinを書かないとoldにならないのでエラーになります。
mapありだと
def yeller(a) a.map(&:upcase).join end puts yeller(['o', 'l', 'd'])
OLD >> %w[A B C].map { |char| char.downcase } => ["a", "b", "c"] >> %w[A B C].map(&:downcase) => ["a", "b", "c"]s
3:random_subdomainというメソッドを定義してください。
このメソッドはランダムな8文字を生成し、文字列として返します。
('a'..'z').to_a.shuffle[0..7].join
4:「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。
ヒント:split、shuffle、joinメソッドを組み合わせると、
メソッドに渡された文字列 (引数) をシャッフルさせることができます。
def string_shuffle(s) s.?('').?.? end puts string_shuffle("foobar") "oobfra" #答え split,shuffle,join
ハッシュは本質的には配列と同じですが、インデックスとして整数値以外のものも使える点が配列と異なります (Perlなどのいくつかの言語ではハッシュを連想配列と呼ぶこともあります)。
ハッシュのインデックス (キーと呼ぶのが普通です)は、
通常何らかのオブジェクトで、次のように文字列をキーとして使えます。
>> user = {} # {}は空のハッシュ => {} >> user["first_name"] = "Michael" # キーが "first_name" で値が "Michael" => "Michael" >> user["last_name"] = "Hartl" # キーが "last_name" で値が "Hartl" => "Hartl" >> user["first_name"] # 要素へのアクセスは配列の場合と似ている => "Michael" >> user # ハッシュのリテラル表記 => {"last_name"=>"Hartl", "first_name"=>"Michael"}
4.3.3 ハッシュとシンボル
ハッシュは、キーと値のペアを波カッコで囲んで表記します。
キーと値のペアを持たない波カッコの組 ({}) は空のハッシュで、
ハッシュの波カッコは、ブロックの波カッコとはまったく別物であるという点
ハッシュは配列と似ていますが、1つの重要な違いとして、ハッシュでは要素の
「並び順」が保証されないという点がある。もし要素の順序が重要である場合は、
配列を使う必要があるよ。
ハッシュの1要素を角カッコを使って定義する代わりに、次のようにキーと値を
ハッシュロケットと呼ばれる=> によってリテラル表現するほうが簡単です。
>> user = { "first_name" => "Michael", "last_name" => "Hartl" } => {"last_name"=>"Hartl", "first_name"=>"Michael"}
Railsでは文字列よりもシンボルを使う方が普通です。
シンボルは文字列と似ていますが、クォートで囲む代わりにコロンが前に置かれている点が異なります。
例えば:nameはシンボル。もちろん、余計なことを一切考えずに、
シンボルを単なる文字列とみなしても構いません。
シンボルは、Ruby以外ではごく一部の言語にしか採用されていない特殊なデータ形式
最初は奇妙に思うかもしれませんが、
Railsではシンボルをふんだんに使っているのですぐに慣れるでしょう。
ただし文字列と違って、全ての文字が使えるわけではないことに注意してください。
ハッシュのキーとしてシンボルを採用する場合、user のハッシュは次のように定義できます。
user = { :name => "Michael Hartl", :email => "michael@example.com" } {:name=>"Michael Hartl", :email=>"michael@example.com"} user[:name] # :name に対応する値にアクセスする "Michael Hartl" user[:password] # 未定義のキーに対応する値にアクセスする nil
未定義のハッシュ値は単純にnilになります。
シンボルとハッシュロケットの組み合わせを、
次のようにキーの名前の(前ではなく)後にコロンを置き、その後に値が続くように置き換えたものです。
{ name: "Michael Hartl", email: "michael@example.com" }
JavaScriptなど他の言語のハッシュ記法により近いものになっている
:nameはシンボルとして独立していますが、
引数を伴わないname:では意味が成り立ちません。
次のコードの:name =>とname:は、ハッシュとしてのデータ構造は全く同じです。
{ :name => "Michael Hartl" } { name: "Michael Hartl" }
というコードは等価になります
一般的には省略記法が好まれますが、明示的に接頭にコロンをつけてシンボル
(:name) であることを強調するという考え方もあります)
ハッシュの値にはほぼ何でも使うことができ、他のハッシュを使うことすらできます。
ハッシュの中のハッシュ
params = {} #'params'というハッシュを定義する('parameters'の略)。 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[:name] => nil >> params[:email] => nil >> params[:user][:name] => "Michael Hartl" >> params[:user][:email] => "mhartl@example.com" >> params[:user] => {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
params = () やparams = []で入れようとするとエラーになります。
ハッシュのハッシュ(またはネスト(入れ子))されたハッシュ)が大量に使われていて
配列や範囲オブジェクトと同様、ハッシュもeachメソッドに応答します。例えば、
:successと:dangerという2つの状態を持つ flash という名前のハッシュに
ついて考えてみょう。
>> 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."
ハッシュのeachメソッドでは、ブロックの変数はキーと値の2つになっている
>> puts (1..5).to_a # 配列を文字列として出力 1 2 3 4 5 >> puts (1..5).to_a.inspect # 配列のリテラルを出力 [1, 2, 3, 4, 5] >> puts :name, :name.inspect name :name >> puts "It worked!", "It worked!".inspect It worked! "It worked!"
inspectを使うことは非常によくあることなので、
pメソッドというショートカットもあります。
>> p :name # 'puts :name.inspect' と同じ :name
演習
1:キーが’one’、’two’、’three’となっていて、それぞれの値が’uno’、
‘dos’、’tres’となっているハッシュを作成してください。
その後ハッシュの各要素をみて、
それぞれのキーと値を”‘#{key}’のスペイン語は’#{value}'”
といった形で出力してみてください。
params = {one: 'uno',two: 'dos', three: 'tres'} => {:one=>"uno", :two=>"dos", :three=>"tres"} params.each do |key, value| puts "#{key.inspect} のスペイン語は#{value.inspect}" end :one のスペイン語は"uno" :two のスペイン語は"dos" :three のスペイン語は"tre
2:person1、person2、person3という3つのハッシュを作成し、それぞれの
ハッシュに:firstと:lastキーを追加し、適当な値 (名前など) を入力して。
その後、次のようなparamsというハッシュのハッシュを作ってみてください。
1. キーparams[:father]の値にperson1を代入、
2. キーparams[:mother]の値にperson2を代入、
3. キーparams[:child]の値にperson3を代入。
最後に、ハッシュのハッシュを調べていき、
正しい値になっているか確かめてみてください。
(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめる)
>> person1 = {first: "tarou",last: "nakamura"} => {:first=>"norimiti", :last=>"nakamura"} >> person2 = {first: "chiko",last: "nakamura"} => {:first=>"machiko", :last=>"nakamura"} >> person3 = {first: "takahiro",last: "nakamura"} => {:first=>"hiro", :last=>"nakamura"} >> params = {} => {} >> params[:father] = person1 => {:first=>"tarou", :last=>"nakamura"} >> params[:mother] = person2 => {:first=>"chiko", :last=>"nakamura"} >> params[:child] = person3 => {:first=>"hiro", :last=>"nakamura"} >> params[:father][:first] => "tarou" >> person1[:first] => "tarou"
3:userというハッシュを定義してみてください。
このハッシュは3つのキー:name、:email、:password_digestを持っていて、
それぞれの値にあなたの名前あなたのメールアドレス、
そして16文字からなるランダムな文字列が代入されています。
まず16文字のランダムな文字列を作るよ!
('a'..'z').to_a.shuffle[0..15].join user = {name: tuki, email: rails@example.com, :password_digest: ('a'..'z').to_a.shuffle[0..15].join}
4:Ruby API(もしくはるりまサーチ) を使って、Hashクラスのmergeメソッドについて調べて見てください
次のコードを実行せずに、どのような結果が返ってくるか推測できますか?
推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。
{ "a" => 100, "b" => 200 }.merge({ "b" => 300 }) {"a"=>100, "b"=>300}
4.3.4 CSS、再び
stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
↑のコードで、このメソッドを呼んでいる。ここで不思議な点がいくつもある。
第一に、丸カッコがありません。
実は、Ruby では丸カッコは使用してもしなくても構いません。
次の2つの行は等価
# メソッド呼び出しの丸カッコは省略可能。
stylesheet_link_tag('application', media: 'all', 'data-turbolinks-track': 'reload') stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
media引数はハッシュのようですが、波カッコがない点が不思議です。
実は、ハッシュがメソッド呼び出しの最後の引数である場合は、
波カッコを省略できます。次の2つの行は等価です。
# 最後の引数がハッシュの場合、波カッコは省略可能。
stylesheet_link_tag 'application', { media: 'all', 'data-turbolinks-track': 'reload' } stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
最後に、Rubyが次のようなコードを正常に実行できているのが不思議です。
Rubyは改行と空白を区別していない、行を分割した理由は、
ソースコードを読みやすくするため
stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
stylesheet_link_tagメソッドを2つの引数で呼んでいる。
最初の引数である文字列は、スタイルシートへのパスを示しています。
次の引数であるハッシュには2つの要素があり、
最初の要素はメディアタイプ(全て)を示し、
次の要素はRails 4.0で追加されたturbolinksという機能をオンにしています。
この結果、<%= %> で囲まれているコードを実行した結果が
ERbのテンプレートに挿入されるようになり、
ブラウザ上でこのページのソースを表示すると、
必要なスタイルシートが含まれていることを確認できます。
(CSSファイル名の後に、?body=1のような行が余分に表示されていることがあります。。
これらはRailsによって挿入されているもので、サーバー上で変更があった場合にブラウザがCSSを再読み込みするのに使っている。)
ちなみに読み込まれたCSSによって生成されたHTMLソース。
<link data-turbolinks-track="true" href="/assets/application.css"media="all" rel="stylesheet" />