Try T.M Engineer Blog

多摩市で生息するエンジニアが「アウトプットする事は大事だ」と思って始めたブログ

【Ruby on Railsチュートリアル(第4版)】第4章 Rails風味のRuby(演習と解答)

はじめに

このブログ記事は、私(Kodak)自身のRailsの勉強記録として書いています。
Ruby on Railsチュートリアル』の演習と解答をもくもくと書いているだけの記事なので、興味の無い方は軽くスルーしてあげてください。他にも、まだ『Railsチュートリアル』の演習に挑戦していない方、これから『Railsチュートリアル』をやるぞ!という方は(演習の解答の)ネタバレになりますので、スルーしてください。

Ruby on Rails チュートリアルとは?

Ruby業界でRailsを使い始めるなら、まず最初に初めるRails入門サイトです。
電子書籍版は有料ですが、Webサイトにあるオンライン版は無料なので、誰でも読む(Railsにチャレンジする)事ができます。
railstutorial.jp
全部で14章あり、かなりのボリュームですが、これを読む事でRailsの基礎を学ぶ事ができ、ちょっとしたWebアプリケーションを作れるレベルにはなれる?とのこと。
各章毎に演習問題が複数あり、これを解いていく事でRailsへの理解を深めていく事ができる様になっています。

環境について

Ruby 2.5.0-dev
Rails 5.1.4
・バージョン管理ツール:GitHub(https://github.com/Kodak4400)

演習と解答

4.2.2 文字列<演習>
1. city変数に適当な市区町村を、prefecture変数に適当な都道府県を代入してください。
【解答】以下の通り。

irb(main):008:0> city = "Tama"
=> "Tama"
irb(main):009:0> prefecture = "Tokyo"
=> "Tokyo"
irb(main):010:0>

2. 先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。
【解答】以下の通り。

irb(main):005:0> puts "#{prefecture}都 #{city}市"
Tokyo都 Tama市
=> nil
irb(main):006:0>

3. 上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です)
【解答】以下の通り。

irb(main):006:0> puts "#{prefecture}都\t#{city}市"
Tokyo都    Tama市
=> nil
irb(main):007:0>

4. タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?
【解答】以下の通り。

irb(main):007:0> puts '#{prefecture}都\t#{city}市'
#{prefecture}都\t#{city}市
=> nil
irb(main):008:0>

4.2.3 オブジェクトとメッセージ受け渡し<演習>
1. "racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。
【解答】以下の通り。

irb(main):013:0> "racecar".length
=> 7
irb(main):014:0>

2. reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。
【解答】以下の通り。

irb(main):014:0> "racecar".reverse
=> "racecar"
irb(main):015:0>

3. 変数sに "racecar" を代入してください。その後、比較演算子 (==) を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。
【解答】以下の通り。

irb(main):007:0> s = "racecar"
=> "racecar"
irb(main):008:0> if s == s.reverse
irb(main):009:1> "同じ"
irb(main):010:1> else
irb(main):011:1> "違う"
irb(main):012:1> end
=> "同じ"
irb(main):013:0>

4. リスト 4.9を実行すると、どんな結果になるでしょうか? 変数sに "onomatopoeia" という文字列を代入するとどうなるでしょうか? ヒント: 上矢印 (またはCtrl-Pコマンド) を使って以前に使ったコマンドを再利用すると一からコマンドを全部打ち込む必要がなくて便利ですよ。)
【解答】以下の通り。

irb(main):033:0> puts "It's a palindrome!" if s == s.reverse
It's a palindrome!
=> nil
irb(main):034:0> s = "onomatopoeia"
=> "onomatopoeia"
irb(main):035:0> puts "It's a palindrome!" if s == s.reverse
=> nil
irb(main):036:0>

4.2.4 メソッドの定義<演習>
1. リスト 4.10のFILL_INの部分を適切なコードに置き換え、回文かどうかをチェックするメソッドを定義してみてください。ヒント: リスト 4.9の比較方法を参考にしてください。
【解答】以下の通り。

irb(main):049:0> def palindrome_tester(s)
irb(main):050:1>  if s == s.reverse
irb(main):051:2>   puts "It's a palindrome!"
irb(main):052:2>  else
irb(main):053:2>   puts "It's not a palindrome."
irb(main):054:2>  end
irb(main):055:1> end
=> :palindrome_tester
irb(main):056:0>

2. 上で定義したメソッドを使って “racecar” と “onomatopoeia” が回文かどうかを確かめてみてください。1つ目は回文である、2つ目は回文でない、という結果になれば成功です。
【解答】以下の通り。

irb(main):057:0> puts palindrome_tester("racecar")
It's a palindrome!
=> nil
irb(main):058:0> puts palindrome_tester("onomatopoeia")
It's not a palindrome.
=> nil
irb(main):059:0>

3. palindrome_tester("racecar")に対してnil?メソッドを呼び出し、戻り値がnilであるかどうかを確認してみてください (つまりnil?を呼び出した結果がtrueであることを確認してください)。このメソッドチェーンは、nil?メソッドがリスト 4.10の戻り値を受け取り、その結果を返しているという意味になります。
【解答】以下の通り。

irb(main):059:0> puts palindrome_tester("racecar").nil?
It's a palindrome!
true
=> nil
irb(main):060:0>

4.3.1 配列と範囲演算子<演習>
1. 文字列 “A man, a plan, a canal, Panama” を ", " で分割して配列にし、変数aに代入してみてください。
【解答】以下の通り。

irb(main):019:0> a = "A man, a plan, a canal, Panama".split(',')
=> ["A man", " a plan", " a canal", " Panama"]
irb(main):020:0>

2. 今度は、変数aの要素を連結した結果 (文字列) を、変数sに代入してみてください。
【解答】以下の通り。

irb(main):020:0> s = a.join
=> "A man a plan a canal Panama"
irb(main):021:0>

3. 変数sを半角スペースで分割した後、もう一度連結して文字列にしてください (ヒント: メソッドチェーンを使うと1行でもできます)。リスト4.10で使った回文をチェックするメソッドを使って、(現状ではまだ) 変数sが回文ではないことを確認してください。downcaseメソッドを使って、s.downcaseは回文であることを確認してください。
【解答】以下の通り。

irb(main):024:0> s = s.split(' ').join
=> "AmanaplanacanalPanama"
irb(main):025:0>

irb(main):098:0> def palindrome_tester(s)
irb(main):099:1>   if s == s.reverse
irb(main):100:2>     puts "It's a palindrome!"
irb(main):101:2>   else
irb(main):102:2>     puts "It's not a palindrome."
irb(main):103:2>   end
irb(main):104:1> end
=> :palindrome_tester
irb(main):105:0> palindrome_tester(s)
It's a palindrome!
=> nil
irb(main):106:0>

4. aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。同様にして、後ろから7番目の要素を取り出してみてください。(ヒント: 範囲オブジェクトを配列に変換するのを忘れないでください)
【解答】以下の通り。

=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
irb(main):006:0> obj[6]
=> "g"
irb(main):007:0> obj[-7]
=> "t"
irb(main):008:0>

4.3.2 ブロック<演習>
1. 範囲オブジェクト0..16を使って、各要素の2乗を出力してください。
【解答】以下の通り。

irb(main):010:0> (0..16).map { |i| i**2 }
=> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256]
irb(main):011:0>

2. yeller (大声で叫ぶ) というメソッドを定義してください。このメソッドは、文字列の要素で構成された配列を受け取り、各要素を連結した後、大文字にして結果を返します。例えばyeller([’o’, ’l’, ’d’])と実行したとき、"OLD"という結果が返ってくれば成功です。ヒント: mapとupcaseとjoinメソッドを使ってみましょう。
【解答】以下の通り。

irb(main):016:0> def yeller(a)
irb(main):017:1>   a.map(&:upcase).join
irb(main):018:1> end
=> :yeller
irb(main):019:0> yeller(['o','l','d'])
=> "OLD"
irb(main):020:0>

3. random_subdomainというメソッドを定義してください。このメソッドはランダムな8文字を生成し、文字列として返します。ヒント: サブドメインを作るときに使ったRubyコードをメソッド化したものです。
【解答】以下の通り。

irb(main):029:0> def random_subdomain
irb(main):030:1>   8.times { print ('A'..'Z').to_a[rand(26)] }
irb(main):031:1> end
=> :random_sobdomain
irb(main):032:0> random_subdomain
XKIANEHV=> 8
irb(main):033:0>

4. リスト 4.12の「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。ヒント:split、shuffle、joinメソッドを組み合わせると、メソッドに渡された文字列 (引数) をシャッフルさせることができます。
【解答】以下の通り。

irb(main):033:0> def string_shuffle(s)
irb(main):034:1>   s.split('').shuffle.join
irb(main):035:1> end
=> :string_shuffle
irb(main):036:0> string_shuffle("foobar")
=> "broofa"
irb(main):037:0>

4.3.3 ハッシュとシンボル<演習>
1. キーが’one’、’two’、’three’となっていて、それぞれの値が’uno’、’dos’、’tres’となっているハッシュを作ってみてください。その後、ハッシュの各要素をみて、それぞれのキーと値を"’#{key}’のスペイン語は’#{value}’"といった形で出力してみてください。
【解答】以下の通り。

irb(main):017:0> h = { one: "uno", two: "dos", three: "tres" }
=> {:one=>"uno", :two=>"dos", :three=>"tres”}
irb(main):018:0> h.each do |key, value|
irb(main):019:1*  puts "#{key}のスペイン語は#{value}"
irb(main):020:1> end
oneのスペイン語はuno
twoのスペイン語はdos
threeのスペイン語はtres
=> {:one=>"uno", :two=>"dos", :three=>"tres"}
irb(main):021:0>

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]と一致しているか確かめてみてください)
【解答】以下の通り。

irb(main):041:0> person1 = { first:'tanaka', last:'tarou' }
=> {:first=>"tanaka", :last=>"tarou"}
irb(main):042:0> person2 = { first:'yamada', last:'takashi' }
=> {:first=>"yamada", :last=>"takashi"}
irb(main):043:0> person3 = { first:'fukuda', last:'shinya' }
=> {:first=>"fukuda", :last=>"shinya"}
irb(main):044:0> params = { father:person1, mother:person2, child:person3 }
=> {:father=>{:first=>"tanaka", :last=>"tarou"}, :mother=>{:first=>"yamada", :last=>"takashi"}, :child=>{:first=>"fukuda", :last=>"shinya"}}
irb(main):045:0> params[:father][:first]
=> "tanaka"
irb(main):046:0> person1[:first]
=> "tanaka"
irb(main):047:0>

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

irb(main):001:0> user = { name:"Kodak", email:"Kodak@email.com", password_digest:"0123456789101112" }
=> {:name=>"Kodak", :email=>"Kodak@email.com", :password_digest=>"0123456789101112"}
irb(main):002:0>

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

irb(main):047:0> { "a"=>100, "b"=>200 }.merge({ "b"=>300 }) #{"a"=>100, "b"=>300}という解が出力されると推測。
=> {"a"=>100, "b"=>300}
irb(main):048:0>

4.4.1 コンストラクタ<演習>
1. 1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか? (復習です)
【解答】以下の通り。

irb(main):006:0> a = 1..10
=> 1..10

2. 今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります
【解答】以下の通り。

irb(main):007:0> range1 = Range.new(1, 10)
=> 1..10

3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
【解答】以下の通り。

irb(main):006:0> a = 1..10
=> 1..10
irb(main):007:0> range1 = Range.new(1, 10)
=> 1..10
irb(main):008:0> range1 == a
=> true
irb(main):009:0>

4.4.2 クラス継承<演習>
1. Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください。
【解答】以下の通り。

irb(main):011:0> s = Range.new(1,10)
=> 1..10
irb(main):012:0> s.class
=> Range
irb(main):013:0> s.class.superclass
=> Object
irb(main):014:0> s.class.superclass.superclass
=> BasicObject
irb(main):015:0> s.class.superclass.superclass.superclass
=> nil
irb(main):016:0>

2. リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。 【解答】以下の通り。

irb(main):018:0> class Word < String
irb(main):019:1>   # 文字列が回文であればtrueを返す
irb(main):020:1>   def palindrome?
irb(main):021:2>     self == reverse
irb(main):022:2>   end
irb(main):023:1> end
=> :palindrome?
irb(main):024:0>
irb(main):025:0> b = Word.new("test")
=> "test"
irb(main):026:0> b.palindrome?
=> false
irb(main):027:0> c = Word.new("teet")
=> "teet"
irb(main):028:0> c.palindrome?
=> true
irb(main):029:0>

4.4.3 組み込みクラスの変更<演習>
1. palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? ヒント: downcaseメソッドで小文字にすることを忘れないで。
【解答】以下の通り。

irb(main):029:0> class String
irb(main):030:1>   # 文字列が回文であればtrueを返す
irb(main):031:1>   def palindrome?
irb(main):032:2>     self == self.reverse
irb(main):033:2>   end
irb(main):034:1> end
=> :palindrome?
irb(main):035:0> "racecar".palindrome?
=> true
irb(main):036:0> "onomatopoeia".palindrome?
=> false
irb(main):037:0> "Malayalam".downcase.palindrome?
=> true
irb(main):038:0>

2. リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。ヒント: リスト 4.12も参考になります。
【解答】以下の通り。

irb(main):056:0> class String
irb(main):057:1>   # 文字列をShuffleする
irb(main):058:1>   def shuffle
irb(main):059:2>     self.split('').shuffle.join
irb(main):060:2>   end
irb(main):061:1> end
=> :shuffle
irb(main):062:0> "racecar".shuffle
=> "aarrcce"
irb(main):063:0>

3. リスト 4.16のコードにおいて、self.を削除してもうまく動くことを確認してください。
【解答】以下の通り。

irb(main):063:0> class String
irb(main):064:1>   # 文字列をShuffleする
irb(main):065:1>   def shuffle
irb(main):066:2>     split('').shuffle.join
irb(main):067:2>   end
irb(main):068:1> end
=> :shuffle
irb(main):069:0> "racecar".shuffle
=> "arcacer"
irb(main):070:0>

4.4.4 コントロールクラス<演習>
1. 第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう。
【解答】以下の通り。

irb(main):001:0> u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):002:0>

2. 生成したuserオブジェクトのクラスの継承階層を調べてみてください。
【解答】以下の通り。

irb(main):008:0>
irb(main):009:0> u.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
irb(main):010:0> u.class.superclass
=> ApplicationRecord(abstract)
irb(main):011:0> u.class.superclass.superclass
=> ActiveRecord::Base
irb(main):012:0> u.class.superclass.superclass.superclass
=> Object
irb(main):013:0> u.class.superclass.superclass.superclass.superclass
=> BasicObject
irb(main):014:0> u.class.superclass.superclass.superclass.superclass.superclass
=> nil
irb(main):015:0>

4.4.5 ユーザークラス<演習>
1. Userクラスで定義されているname属性を修正して、first_name属性とlast_name属性に分割してみましょう。また、それらの属性を使って "Michael Hartl" といった文字列を返すfull_nameメソッドを定義してみてください。最後に、formatted_emailメソッドのnameの部分を、full_nameに置き換えてみましょう (元々の結果と同じになっていれば成功です)
【解答】以下の通り。

irb(main):001:0> require './example_user'
=> true
irb(main):002:0> example = User.new
=> #<User:0x00007fde00408e78 @first_name=nil, @last_name=nil, @email=nil>
irb(main):003:0> example.first_name
=> nil
irb(main):004:0> example.last_name
=> nil
irb(main):005:0> example.email
=> nil
irb(main):006:0> example.first_name = "Michael"
=> "Michael"
irb(main):007:0> example.last_name = "Hartl"
=> "Hartl"
irb(main):008:0> example.email = "user@example.com"
=> "user@example.com"
irb(main):009:0> example.formatted_email
=> "Michael Hartl <user@example.com>"
irb(main):010:0>

2. "Hartl, Michael" といったフォーマット (苗字と名前がカンマ+半角スペースで区切られている文字列) で返すalphabetical_nameメソッドを定義してみましょう。
【解答】以下の通り。

irb(main):010:0> example.alphabetical_name
=> "Hartl, Michael"
irb(main):011:0>

3. full_name.splitとalphabetical_name.split(’, ’).reverseの結果を比較し、同じ結果になるかどうか確認してみましょう。
【解答】以下の通り。

irb(main):011:0> example.full_name.split
=> ["Michael", "Hartl"]
irb(main):012:0> example.alphabetical_name.split(', ').reverse
=> ["Michael", "Hartl"]
irb(main):013:0>