give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

Mateus Asatoのギタークリニックに行きました(2017年8月24日 三木楽器アメリカ村店)

はじめに

2017年8月24日、三木楽器アメリカ村店で開催されたMateus Asatoのギタークリニックに行ってきました。
このエントリではこのギタークリニックの様子と僕の感想を書いてみます。

【20名様限定】Mateus Asato ギター・クリニック@三木楽器アメリカ村店(大阪) | Peatix
f:id:JunichiIto:20170827074127j:plain

Mateus Asatoって誰?

Mateus Asato(マテウス・アサト)はブラジル出身の日系人ギタリストです(本人のツイートによると、父方が完全に日本人の家系とのこと)。
1993年生まれの23歳(2017年8月時点)で、現在はアメリカのロサンゼルスを拠点に活動しています。

InstagramやYouTube、Facebook等のSNSで卓越したテクニックと繊細さを兼ね備えたギタープレイを積極的に発信し、多くのフォロワーを獲得しています(Instagramのフォロワーは35万人、YouTubeのチャンネル登録者は12万人)。

f:id:JunichiIto:20170827054405j:plain

僕とMateusとの出会い

僕がMateus Asatoを知ったのはFacebookでした。
たしか「この人のギタープレイはギタリストから見てすごい!と思わせるだけでなく、ギターを弾かない人からもいいね、と思われるようなギタープレイ」というコメントとともにMateusの演奏動画が紹介されていました。

どの動画だったか、はっきり覚えてはいませんが、たぶんこんな感じの動画だったと思います。


The Bridge - Mateus Asato

本当にそのコメントのとおりで、めちゃくちゃテクニカルなのは間違いないんだけど、メロディやハーモニーがすごく美しくて純粋なインストゥルメンタル曲としても聴いてて心地よい。

「うおー、この人すごい!!」と一気にハマり、Mateusの大ファンになってしまいました。

しかし、Mateusはアメリカで活動しているギタリスト。
SNS上では世界中でカリスマ的な人気を誇っていますが、日本国内の知名度はまだまだ低いです。
「生でギタープレイが見られる機会なんてないんだろうな~」と思っていたら、なんと突然アジアツアーの発表が!!

これは何が何でも行かねば!!ということで、速攻でチケットを予約したのでした。

ギタークリニックの様子

僕は当日、2番目に会場に着いていたので、Mateusのど真ん前に座ることができました。
こちらはMateusが登場する前に撮った写真です。

f:id:JunichiIto:20170827060414j:plain

この距離であのMateus Asatoが見られるなんてすごすぎる・・・!!!

Mateusの登場前にスタッフの方から説明があり、「写真撮影、動画撮影OK、SNSへの拡散もどんどんやってMateusを盛り上げてください」とのこと。
なんとSNS拡散までOKとはびっくり!
こんな太っ腹なライブ(正確にはギタークリニックだけど)は初めてです。

そしていよいよMateusが登場し、参加者全員で拍手でお出迎え。
「うおー、本物だ!!すげえ!!」と、年甲斐もなく大興奮してしまいました。

「と、とりあえず、写真を一枚・・・」と、演奏前にギターのチューニングをするMateusをパシャリ。

f:id:JunichiIto:20170827061123j:plain

この日のギタークリニックは最初に2~3曲デモ演奏し、それからギターを始めてから現在に至るまでのギター歴(ギターにまつわるエピソード)をMateusが語り、その後、Q&Aタイムとデモ演奏を2回ほど繰り返して終了、という構成でした。
(ちなみにMateusは英語でしゃべって、主催者の方が通訳していました)

こちらはギタークリニック当日のデモ演奏の一部です。


印象に残ったMateusの話

SNSではギターを演奏している様子しか見ていないので、人となりはわからなかったのですが、本物のMateusはとてもフレンドリーで気さくに話してくれる好青年でした。

Mateusはいろんな話をしてくれたのですが、その中から印象に残っている話をいくつかピックアップします。

  • エレキギターが弾きたかったのに最初に渡されたギターはアコギだった。当時9歳か10歳ぐらいだったMateusはFがうまく押さえられなくて泣いたことがある。
  • 19歳の時にアメリカへ渡り、ロサンゼルスのMI(Musicians Institute、有名な音楽学校)に入学した。MIに行った意味は非常に大きかった。理論を学ぶことで自分の音楽の幅をすごく広げることができた。
  • MIで最初に習ったのは指板上のドの音を全部覚える、というレッスンだった。今まではフォーム(形)で弾き方を覚えていたので、指板を縦(1弦から6弦の方向)に動くことが多かった。しかし、このレッスンを受けてからは指板の上を横方向(ネック側からブリッジ側)にも動けるようになった。
  • 曲を作るときは思い浮かんだメロディをループさせながら、意外性のあるコードをいろいろ重ねて実験をする。(実際にやってみましょう、とその場で実演もしてくれた。これがすごくMateusの曲っぽかった!)
  • 音作りはオーバードライブが基本。ディストーションは使わない。ギターのボリュームやピッキングの強さ、選択するピックアップで同じセッティングのまま、クリーンサウンドからオーバードライブサウンドまでを弾きわけている。(これは僕が出した質問に対する回答です!)

Mateusの話を聞いて感じたこと

Mateusの話を聞いて感じたのは、「あ、この人も同じ人間なんだ」ということです。

「Fがなかなか押さえられなかった」とか、「MIに入る前はフォームで弾き方で覚えていた」みたいなことはギターを弾く人なら誰でもよくある話です。
Mateusのギタープレイはたしかに「指板の上を自由自在に動くな-」と思っていたのですが、それも「MIで習ったレッスンが役に立っている」と聞いて、「ああ、なるほど」と思いました。

作曲に関しても「どうやったらあんな曲が作れるんだろう?」と思っていましたが、その場で曲作りのやり方を実演してもらうと、「あ、こんなふうにして作るとたしかにMateusっぽくなるなあ」と思いました。
しかも曲作りの実演中、メロディに当てたコードがちょっと外れておかしな響きになってしまい、「実験だからこんなふうに失敗することもあるんだよ」と笑っていたのも「あ、普通の人やん」と感じたところです。

1%の才能・センスと99%の努力

InstagramやYouTubeで見えるのは「完成されたギタープレイ」なので、そこに至るまでの過程は見えません。
なので、「突然現れた天才ギタリスト!!」とか「曲も空から降ってきてるのでは!?」と思ってしまいがちです。

でも、こうやってギターで苦労したエピソードや曲作りの裏話を教えてもらうと、そこに至るまでの過程が見えるので、「努力したからこれだけ弾けるんだ」「試行錯誤を繰り返しながら曲ができるんだ」ということを理解できました。

もちろん生まれ持っての才能やセンスも大きいとは思いますが、エジソンが「天才は1%のひらめきと99%の努力」と言ったのと同様に、Mateusのギタープレイも「1%の才能・センスと99%の努力」の結果なのかもしれません。

本物のMateusに会って、本人の話を直接聞くことで、「天才・神・仙人」みたいなイメージだったMateus Asatoから、自分と同じ「人間」のMateus Asatoに印象が変わったのが、僕にとって一番大きな収穫でした。

最後にツーショット写真とサインをもらった

ギタークリニックが終わった後に全員で集合写真を撮り、それから一人ずつツーショット写真を撮ってもらいました。

ちなみに僕は仕事で使っているパソコンのキーボードの裏にサインを書いてもらいました。

f:id:JunichiIto:20170827070410j:plain
f:id:JunichiIto:20170827070436j:plain

なんでパソコンのキーボード?と思われるかもしれませんが、キーボードの裏ならずっと使っていても消えにくいだろうと思ったからです。
あと、仕事で使っている道具なので、いつでもすぐに見ることもできますし。

何はともあれ、大満足の1時間半でした。
Thank you, Mateus!!

まとめ

というわけで、このエントリでは三木楽器アメリカ村店で開催されたMateus Asatoのギタークリニックの様子と感想を書いてみました。

Mateus Asatoを知らなかった人はぜひInstagramやYouTubeで彼のギタープレイをチェックしてみてください!

www.youtube.comwww.instagram.com

参考:このブログを書いている人・伊藤淳一

普段はWebプログラマーとして働く、ギター好き・音楽好きの元バンドマンです。
ギターは学生時代に弾いていて、しばらくブランクがありましたが、最近またギター熱が高まりつつある今日この頃。

f:id:JunichiIto:20170827072536j:plain:w200 f:id:JunichiIto:20170827072500j:plain:w200

あわせて読みたい

東京会場のレポート記事です。
こちらも興味深い話がたくさん載っているのでオススメです!

youngguitar.jp

「プロを目指すRailsエンジニアのための公開コードレビュー」という発表をしました #railsdm

はじめに

2017年8月24日にRails Developers Meetup #4という勉強会で「プロを目指すRailsエンジニアのための公開コードレビュー」という発表をしてきました。

このエントリではこの勉強会の発表内容を紹介します。

f:id:JunichiIto:20170826085817p:plain

発表のテーマを決めるまでの経緯

以前、「WEB+DB PRESS Vol.99の「良いコード」を本気でコードレビューしてみた」というエントリを書いて結構な反響があったのですが、この記事を読まれた主催者の平野さん(@yoshi_hirano)から「良いコードとは何か、というテーマで体系的に語ってもらいたい」と声をかけてもらったのが登壇のきっかけでした。

ただ、「良いコードを体系的に語る」となると、僕の中ではおそらく「CODE COMPLETEやリーダブルコードを読め。以上!」になってしまうので、それよりも具体的なコード例を見ながら「ここはこう」「このコードはこう」と説明する発表の方がいいだろうなと思いました。

また、具体的なコードを見ながら良し悪しを語るにしても「お互いに知っているコード」でないと、なかなかぱっと理解することができません。
そこで、

  • 僕の方で簡単なプログラミング問題を作る
  • それをみんなに解いてもらう
  • さらに、いただいた解答をいくつかピックアップして発表の中でコードレビューする

というスタイルにするのはどうか、と提案しました。

平野さんも「いいですね。面白そう!」と言ってくれたので、ちょっと珍しい「公開コードレビュー」という形式の発表をやることになりました。

CODE COMPLETE 第2版 上 完全なプログラミングを目指して

CODE COMPLETE 第2版 上 完全なプログラミングを目指して

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

  • 作者: Dustin Boswell,Trevor Foucher,須藤功平,角征典
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2012/06/23
  • メディア: 単行本(ソフトカバー)
  • 購入: 68人 クリック: 1,802回
  • この商品を含むブログ (137件) を見る

問題の概要

僕が作った問題はTrainTicketRailsという、電車の改札口を簡易的にシミュレートしたRailsアプリです。
あらかじめある程度コードができていて、そこに指定された仕様を満たすコードを書いてもらう、というのが今回のお題になります。

詳しくは以前書いたこちらのエントリをご覧ください。

blog.jnito.com

ちなみにこの問題は2017年11月に発売予定の書籍「プロを目指す人のためのRuby入門」のために作った例題をRails向けにモディファイしたものです。

blog.jnito.com

当日は自宅からリモート登壇!

この勉強会はなんと、僕は自宅から発表する「リモート登壇者」でした。
さらに、登壇者だけでなく、勉強会の会場もメイン会場の東京会場に加えて、東京会場と中継でつなく大阪会場と、自宅から視聴可能なリモート会場もありました。

発表する側も、発表を聞く側も、リモートでつながる勉強会とは何とも未来的ですね~。

ちなみに当日はGoogleハングアウトとYouTubeライブを使ってリモート登壇&リモート視聴をしていました。
音声や映像が途切れがちになったりしないかちょっと心配でしたが、結構きれいに中継できていたみたいです。

当日の発表スライドはこちら

当日使ったスライドはこちらです。

ただ、当日はGitHub上のpull requestを見ながらお話をしたので、スライドにはあまりコードが出てきません。
そこでこのブログの中でざっくりとレビュー内容を紹介していきます。

一人目の方のコード

最初はこちらの方のコードをレビューさせてもらいました。

https://github.com/JunichiIto/train-ticket-rails/pull/27

コントローラのコードについて

コントローラのコード(tickets_controller.rb)はこんなふうに実装されていました。

   def edit
+    load_ticket
+    redirect_to root_path, notice: '降車済みの切符です。' if @ticket.used?
   end
 
   def update
-    if @ticket.update(ticket_update_params)
-      redirect_to root_path, notice: '降車しました。😄'
+    load_ticket
+    exited_gate = Gate.find(ticket_update_params[:exited_gate_id])
+
+    if @ticket.used?
+      redirect_to root_path, notice: '降車済みの切符です。' if @ticket.used?
+    elsif exited_gate.exit?(@ticket)
+      update_ticket
     else
+      @ticket.errors[:base] << '降車駅 では降車できません。'
       render :edit
     end
   end

 (略)

   def load_ticket
     @ticket = Ticket.find(params[:id])
   end
+
+  def update_ticket
+    if @ticket.update(ticket_update_params)
+      redirect_to root_path, notice: '降車しました。😄'
+    else
+      render :edit
+    end
+  end

こちらのコードに対しては以下のようなコメントをさせてもらいました。

  • load_ticketメソッドはbefore_actionで呼ばれているので、editupdateの中で呼び出す必要はない。
  • “降車済みの切符です。"のメッセージを表示するコードが2回出てきているので、DRYにするためにメソッドに切り出してbefore_actionで呼び出すようにしたい。
  • Gate.findのようにfindメソッドを使っている部分があるが、ここは@ticket.exited_gateのようにRailsの関連をうまく使ってデータを取ってくるようにしたい。
  • updateメソッド内にたくさん条件分岐が出てきて、「ファットコントローラ」になっている。ロジックはできるだけモデルに持っていって、「薄いコントローラ」を目指したい。
  • コントローラでデータのチェックをすると、モデル単体でデータを保存したときに「同じ駅で降りるな」「料金不足なら降車不可」といったデータのチェックを実行できない。確実にデータがチェックされるよう、検証ロジックはモデルに持ってくるべき。

モデルのコードについて

モデルのコード(gate.rb)はこんなふうに実装されていました。

   def exit?(ticket)
-    true
+    gate_interval = fetch_gate_interval(ticket.entered_gate_id)
+
+    # 運賃不足ではないかチェック
+    if gate_interval.zero?
+      false
+    elsif FARES[gate_interval - 1] <= ticket.fare
+      true
+    else
+      false
+    end
+  end
+
+  private
+
+  def fetch_gate_interval(entered_gate_id)
+    (
+      entered_station_number(entered_gate_id) - station_number
+    ).abs
+  end
+
+  def entered_station_number(gate_id)
+    Gate.find(gate_id).station_number
   end

僕の指摘内容は以下のとおりです。

  • このコードでもGate.findが登場しているが、ticket.entered_gate.station_numberのようにしてRailsの関連を使った方がスマート。
  • exit?メソッドではtruefalseを明示的に返しているが、<=の比較結果をそのまま返せばtruefalse明示的に返す必要がない。

二人目の方のコード

二本目はこちらのコードをレビューさせてもらいました。

https://github.com/JunichiIto/train-ticket-rails/pull/15/

コントローラのコードについて

コントローラは次のようになっていました。

   def load_ticket
     @ticket = Ticket.find(params[:id])
+    redirect_to root_path, alert: '降車済みの切符です。' if @ticket.exited_gate_id
   end

僕のコメントは以下のとおりです。

  • コントローラにほとんど手を加えず、たった1行の変更で済ませた点は非常にスマート(「薄いコントローラ」を維持できている)。
  • ただし、load_ticketメソッドの中で、データをチェックして問題があればリダイレクトさせるのは、load_ticketメソッドの責務を超えているように思う。
  • メソッドの責務が増えると再利用性も低下してしまうので、ここは別メソッドに分けてbefore_actionで呼び出す方がベターではないか。

モデルのコードについて

改札口モデル(gate.rb)のコードは次のようになっていました。

   def exit?(ticket)
-    true
+    price = calculate(ticket)
+    price > 0 && ticket.fare - price >= 0
+  end
+
+  private
+
+  def calculate(ticket)
+    section = (station_number - ticket.entered_gate.station_number).abs
+    section > 0 ? FARES[section - 1] : 0
   end

以下は僕からのコメントです。

  • こちらもシンプルでスマート。ticket.entered_gate.station_numberのように、Railsの関連もちゃんと使えている。
  • ただし、calculateメソッドが0(ゼロ)を返すところが気になる。この0は「無料だから降車OK」の意味ではなく、「同じ駅で降りようとしているので降車不可」のフラグになってしまっている。
  • 作った人だけがわかる暗黙の意味、暗黙のルールはできるだけ避けるべき。プログラムが大きくなったり、開発する人が増えたりすると、「0の運賃」がそのまま「無料」と解釈される恐れがある。

もう一つ、切符モデル(ticket.rb)にも次のような変更が入っていました。

 class Ticket < ApplicationRecord
   belongs_to :entered_gate, class_name: 'Gate', foreign_key: 'entered_gate_id'
-  belongs_to :exited_gate, class_name: 'Gate', foreign_key: 'exited_gate_id', required: false
+  belongs_to :exited_gate, class_name: 'Gate', foreign_key: 'exited_gate_id', optional: true
   validates :fare, presence: true, inclusion: Gate::FARES
   validates :entered_gate_id, presence: true
+
+  validate :must_be_exit, if: :exited_gate_id
+
+  private
+
+  def must_be_exit
+    errors.add(:exited_gate, 'では降車できません。') unless exited_gate.exit?(self)
+  end
 end

僕のコメントは以下のとおりです。

  • requiredオプションが非推奨になって、optinalオプションを使うようになっていたのは知らなかった(僕の方が勉強になった!)
  • must_be_exitメソッドの実装は問題なし。ただし、must_be_exitだと「be動詞 + 一般動詞」になってしまうので、must_be_exitablecan_exitのようにした方が英文法的には良さそう。

僕の解答例

僕の解答例はGitHubのanswerブランチに置いてあります。

https://github.com/JunichiIto/train-ticket-rails/compare/master…answer

コントローラのコードについて

コントローラのコードは以下のとおりです。

「使用済みのチケットが指定されたらトップページへリダイレクト」のコードはbefore_actionを使って実現しています。

 class TicketsController < ApplicationController
   before_action :load_ticket, only: %i(edit update show)
+  before_action :abort_exited_ticket, only: %i(edit update)
 
   def index
     redirect_to root_path

 (略)
 
   private
 
+  def abort_exited_ticket
+    if @ticket.exited?
+      redirect_to root_path, notice: '降車済みの切符です。'
+    end
+  end
+
   def ticket_create_params
     params.require(:ticket).permit(:fare, :entered_gate_id)
   end

モデルのコードについて

改札口モデルのコードは以下のとおりです。

exit?メソッドではまず、「同じ駅で降りようとしていたらfalseを返す」処理を行い、それから運賃を計算して切符の運賃と大小比較しています。

  def exit?(ticket)
-    true
+    return false if ticket.entered_gate == self
+    ticket.fare >= calc_fare(ticket)
+  end
+
+  private
+
+  def calc_fare(ticket)
+    from = ticket.entered_gate.station_number
+    to = station_number
+    distance = (to - from).abs
+    FARES[distance - 1]
   end

続いて、切符モデルのコードも見てみましょう。

切符モデルでは独自の検証メソッド(gate_exit_should_be_successful)を定義して、仕様上おかしなデータが保存されないようにチェックしています。

また、exited?メソッドは「使用済みかどうか(降車済みかどうか)」を返すインスタンスメソッドです。

+  validate :gate_exit_should_be_successful, if: -> { exited_gate.present? }
+
+  def exited?
+    exited_gate.present?
+  end
+
+  private
+
+  def gate_exit_should_be_successful
+    unless exited_gate.exit?(self)
+      errors.add(:exited_gate, 'では降車できません。')
+    end
+  end

さらに:全員分のコードレビューもしてみました!

もともとはこちらでピックアップした2名のコードをレビューして終わり、とする予定でした。
ですが、他のみなさんのコードも非常に興味深いものが多かったので、思い切って全員分レビューしてみることにしました!

勉強会の時と同じようにpull requestを見ながら口頭でコメントし、その様子を動画に撮ってYouTubeで公開しています。

ちょっと長いので前編と後編の2部構成になっています。

本編では話していない話題もたくさん出てくるので、こちらもぜひチェックしてみてください!
(ただし、長いので1.5倍速ぐらいで視聴することをオススメします)

Twitter上の反響

Twitterの反響を見ていると思った以上に楽しんでいただけたようで、登壇者としても非常に嬉しかったです。

勉強会全体のツイートをご覧になる場合はこちらのTogetterをどうぞ。
僕が発表した時間帯のツイートは3ページ目から9ページ目あたりにあります。

togetter.com

まとめ

というわけでこのエントリではRails Developers Meetup #4で発表した「プロを目指すRailsエンジニアのための公開コードレビュー」の内容をあれこれ書いてみました。

主催者の平野さんをはじめ、運営スタッフのみなさん、どうもありがとうございました。 入念な準備のおかげでスムーズに発表することができました!

また、公開コードレビューで解答をピックアップさせてもらったお二人をはじめ、解答を投稿してくれたみなさんもどうもありがとうございました!
解答が来なかったらどうしよう?と心配していたのですが、解答を投稿してもらったおかげでとても実りのあるコードレビューができたと思います。

これからRubyやRailsの勉強をやっていこうと思われている方は、ぜひ今回の発表内容を参考にしてみてください。
あと、2017年11月に発売される「プロを目指す人のためのRuby入門」も良いコードを書くのに役立つと思うので、こちらもぜひよろしくお願いします!(宣伝)

blog.jnito.com

告知:Rubyプログラミングキャンプ2017、参加者募集中です!

僕とAkiさん(@spring_aki)で主催している西脇.rb&神戸.rbでは現在「Rubyプログラミングキャンプ 2017」の参加者を募集中です!

このイベントは2日間、泊まり込みで思いっきりRubyプログラミングに没頭しようという楽しいイベントです。
まだ若干枠が残っているので、興味がある方はぜひご参加ください。

Ruby初心者の方や、僕たちの勉強会に一度も参加したことがない、という方も大歓迎です。
もちろん僕も参加するので、「伊藤さんに良いコードの書き方を教わってみたい」という方もぜひ!(苦笑)

nishiwaki-koberb.doorkeeper.jp

あわせて読みたい

今回登壇するきっかけになったコードレビューのエントリです。
自分で読み返してもかなり細かいな・・・。

blog.jnito.com

僕のコードレビューが細かくて、良いコードや良い設計へのこだわりが強いのはこんな背景があるからです。

blog.jnito.com

Everyday Rails(Rails 4.1版) 第9章「モックとスタブ」に関するQ&A(とお詫び)

はじめに

Everyday RailsのQ&Aページで本の内容に関する質問をいただきました。
回答を書いているとかなり長くなってしまったのと、他の読者のみなさんにも知っておいてもらいたい内容なので、このブログに回答を書きます。

質問

Everyday RailsのQ&Aページより)

本とGithubのコードが異なります。
テストの実行結果が変わらないことと私の理解不足で成否が判断できないのですが本の方が正しいのでしょうか?

https://github.com/everydayrails/rails-4-1-rspec-3-0/blob/master/spec/controllers/contacts_controller_spec.rb

コードでは58行から全部「Contact」が大文字

allow(Contact).to receive(:persisted?).and_return(true)
allow(Contact).to \
  receive(:order).with('lastname, firstname').and_return([contact])
allow(Contact).to \
  receive(:find).with(contact.id.to_s).and_return(contact)
allow(Contact).to receive(:save).and_return(true)

本では小文字「contact」もある

allow(contact).to receive(:persisted?).and_return(true)
allow(Contact).to \
  receive(:order).with('lastname, firstname').and_return([contact])
allow(Contact).to \
  receive(:find).with(contact.id.to_s).and_return(contact)
allow(contact).to receive(:save).and_return(true)

回答

最初に結論からいうと、実はallowで始まる4行のうち、ここで必要となるコードは以下の行だけです。

allow(Contact).to \
  receive(:find).with(contact.id.to_s).and_return(contact)

つまり、本のコードもGitHubのコードも間違っています(というか無駄なコードを書いています。ごめんなさい)。

試しに次のように他の行をコメントアウトしてからテストを実行してください。
おそらくテストはパスするはずです。

before :each do
  # allow(Contact).to receive(:persisted?).and_return(true)
  # allow(Contact).to \
  #   receive(:order).with('lastname, firstname').and_return([contact])
  allow(Contact).to \
    receive(:find).with(contact.id.to_s).and_return(contact)
  # allow(Contact).to receive(:save).and_return(true)

  get :show, id: contact
end

もし全部コメントアウトするとエラーが発生すると思います。
なので、findのスタブだけは必要なことがわかります。

before :each do
  # allow(Contact).to receive(:persisted?).and_return(true)
  # allow(Contact).to \
  #   receive(:order).with('lastname, firstname').and_return([contact])
  # allow(Contact).to \
  #   receive(:find).with(contact.id.to_s).and_return(contact)
  # allow(Contact).to receive(:save).and_return(true)

  get :show, id: contact
end
ActiveRecord::RecordNotFound: Couldn't find Contact with 'id'=1031
./app/controllers/contacts_controller.rb:75:in `set_contact'
./spec/controllers/contacts_controller_spec.rb:65:in `block (4 levels) in <top (required)>'
./spec/rails_helper.rb:40:in `block (3 levels) in <top (required)>'
./spec/rails_helper.rb:39:in `block (2 levels) in <top (required)>'
-e:1:in `<main>'

もう少し詳しく

このスペックで実行されるコントローラのアクションはContactsControllerのshowです。

ContactsControllerからshowアクションで実行される部分だけを抜粋すると次のようになります。

class ContactsController < ApplicationController
  before_action :authenticate, except: [:index, :show]
  before_action :set_contact, only: [:show, :edit, :update, :destroy]

  # 省略

  def show
  end

  # 省略

  private

    def set_contact
      @contact = Contact.find(params[:id])
    end

    # 省略
end

このうち、ActiveRecordに関連する処理はset_contactメソッド内のContact.findだけになります。
なので、データベースへのアクセスをなくして高速化したいのであれば、この処理をスタブに置き換える必要があります。
そのためのコードをスペックのbeforeフックに書きます。

# Contactクラスのfindメソッドが呼ばれたら、モックの連絡先を返す
# (withはfindメソッドの引数の条件指定。この引数と一致したときにモックの連絡先を返す)
allow(Contact).to \
  receive(:find).with(contact.id.to_s).and_return(contact)

この行までコメントアウトしてしまうと、Contact.findは実際にデータベースにアクセスしてしまいます。
しかし、データベースにはテストデータを登録していないので、レコードが見つからずにエラー(ActiveRecord::RecordNotFound)が起きます。

また、ContactsControllerのshowアクションではContacts.find以外、ActiveRecord関連の処理が実行されないため、これ以外のモック化(allowで始まるコード)は必要ありません。

今回の「無駄なコード」が必要になるケース

さらに、今回「無駄なコード」とされた以下の3つのコードについても見ていきます。
以下は本のコードからの抜粋です。

allow(contact).to receive(:persisted?).and_return(true)

allow(Contact).to \
  receive(:order).with('lastname, firstname').and_return([contact])

allow(contact).to receive(:save).and_return(true)

Contactとcontactの違い

まず、Contactcontactの違いを確認しておきましょう。
大文字のContactはContactクラスそのもの(クラスオブジェクト)です。
一方、小文字のcontactlet(:contact)で作成されたモックの連絡先オブジェクトです。

この点を押さえた上で、3つの「無駄なコード」が必要になる場面を考えていきます。

1つめのコードについて

それでは最初のコードの意味を確認しましょう(コードの意味はコメントに記述しています)。

# モックの連絡先に対してpersisted?メソッドが呼ばれたら、trueを返す
allow(contact).to receive(:persisted?).and_return(true)

このコードに関しては以下のようなことが言えます。

  • テスト実行時にpersisted?メソッドが呼び出されるようなアクションがあるなら、このコードには意味が出てくる。
  • trueを返しているので、モックの連絡先はレコードが保存済みであることを示す。つまり、もしこのコードを使うなら、おそらくeditアクションやupdateアクション、destroyアクションになるはず。
    • ただし、本書のサンプルアプリケーションの場合、persisted?メソッドはフレームワーク内で呼ばれることになるので、どのアクションで呼ばれるのか正確に予測するのは難しい。
  • persisted?メソッドはインスタンスメソッドなので、Contact.persisted?をスタブ化しても意味がない(そんなクラスメソッドはないのでどこからも呼ばれない)。つまり、allowメソッドの引数はContactではなく、contactが正解。

2つめのコードについて

2つめのコードも同じように見ていきます。

# Contactクラスに対してorderメソッドが呼ばれたら、配列に入ったモックの連絡先を返す
# (orderメソッドの引数が'lastname, firstname'の場合のみ発動する)
allow(Contact).to \
  receive(:order).with('lastname, firstname').and_return([contact])

つまり、実行時にContact.order('lastname, firstname')が呼ばれるテストでスタブを使うなら、このコードには意味が出てきます。

また、ContactsControllerのindexアクションを見てみると次のようになっているので、もしこのコードを使うのであれば「頭文字を指定せずに連絡先を検索するスペック」になるはずです。

def index
  if params[:letter]
    @contacts = Contact.by_letter(params[:letter])
  else
    @contacts = Contact.order('lastname, firstname')
  end
end

3つめのコードについて

続いて、最後のコードです。

# モックの連絡先に対してsaveメソッドが呼ばれたら、trueを返す
allow(contact).to receive(:save).and_return(true)

ContactsController内でsaveメソッドが呼ばれるのはcreateアクションだけです。

def create
  @contact = Contact.new(contact_params)

  respond_to do |format|
    if @contact.save
      format.html { redirect_to @contact, notice: 'Contact was successfully created.' }
      format.json { render :show, status: :created, location: @contact }
    else
      format.html { render :new }
      format.json { render json: @contact.errors, status: :unprocessable_entity }
    end
  end
end

なので、もし使うのであれば「連絡先を新規作成するスペック」になります。
さらに、この場合だとContact.newがモックの連絡先を返すようにスタブ化する必要もあります。

また、persisted?メソッドと同様、saveメソッドもインスタンスメソッドなので、allowメソッドの引数はContactではなく、contactが正解です。

まとめ

というわけで内容をまとめます。

  • allowメソッドを使って特定のメソッド呼び出しをスタブ化する場合は、テスト実行時にどのメソッドが呼ばれるのかをチェックし、実際に呼ばれるメソッドだけをスタブ化する(呼ばれないメソッドをスタブ化しても意味がない)。
  • クラスメソッドとインスタンスメソッドの区別を付けた上で、allowメソッドに渡す引数を決める(クラスメソッドをスタブ化するならallow(Contact)、インスタンスメソッドならallow(contact))。

長々と書きましたが、ひとことで言うと「本もGitHubも間違いでした」ということになります。
疑問を抱かせてしまって大変申し訳ありませんでした🙇

なお、修正についてですが、本やGitHubのコードを修正しようと思うと原文の修正も必要になるため、Aaronさんとの調整に時間がかかりそうなのと、英語版はすでにRails 5版に移行しているという観点から、本の内容もGtiHubのコードもいったんこのままにさせてください。

Rails 5版ではモックやスタブを使うサンプルコードも完全にリニューアルしています。
具体的なスケジュールは未定ですが、日本語版もRails 5版に無料アップデートする予定です。

blog.jnito.com

あわせて読みたい

RSpecのモックについては僕もQiitaに記事を書いているので、よかったらこちらも参考にしてください。

qiita.com