Rails controllerで早期return

アプリケーションの規模が大きくなるにつれてcontrollerもfatになってくることが多いと思いますが、 リファクタする際に早期returnを使われる方も多いと思います。 controllerで早期returnをするTipsをまとめました。

Guard Clause(早期return)とは

wiki

ガード (プログラミング) - Wikipedia

によると下記のような説明がありました。

ガード (Guard) とは、コンピュータ・プログラミング言語において、条件式ないし条件分岐のような意味を持つもので、ある分岐で処理を続けるために真 (true) と評価されなければならない[1]式である。

つまり、条件分岐や関数の途中、早期に脱出する手段の一つです。ソースコードの可読性が向上し、性能もアップすることが期待されています。「ガード節」、「早期復帰」、「早期リターン」なんて呼んだりすると思います。

controllerで早期リターンするパターン

例に使ってるコード自体に意味はありません。あくまで早期returnの方法を説明することに関心を置いています。

古典的な方法

まずは古典的な方法です。

class UsersController < ApplicationController
  def show
    unless @user.logged_in?
      redirect_to sign_in_path and return
    end

    if invalid_user?
      redirect_to users_path and return
    end

    # 何らかの処理
  end
end

これは古典的な方法だと思います。 ある条件が真だった場合に、redirectさせるために return を使っています。

メソッド and 早期リターン(パターン2)

次はメソッドに切りだしたパターンです。

class UsersController < ApplicationController
  def show
    verify_user and return
    

    # 何らかの処理
  end
  
  private

  def  verify_user
    unless @user.logged_in?
      redirect_to sign_in_path and return true
    end

    if invalid_user?
      redirect_to users_path and return true
    end
  end
end

メソッドに処理を切り出した場合、例えば concerns などに切り出した処理を置いていろいろな controller で使いたい場合には注意が必要です。 このパターンはメソッド内で return true しないと新たなバグを生み出す可能性があります。 メソッドに切り出して早期リターンする場合、 true を返さないと呼び出し元の and の左辺が偽あつかいになるので処理が途中で中断されません。その場合、 DoubleRenderError が発生する場合があるので注意です。

verify_user and return という書き方が直感に反する気がしました。

メソッド or 早期リターン(パターン3)

メソッドに切り出した場合に and ではなく or を使うパターンもあります。

class UsersController < ApplicationController
  def show
    verify_user or return
    

    # 何らかの処理
  end
  
  private

  def  verify_user
    unless @user.logged_in?
      redirect_to sign_in_path and return
    end

    if invalid_user?
      redirect_to users_path and return
    end

    return true
  end
end

この場合は、メソッドに切り出したときにメソッドの最後に return true を追加する必要があります。 しかし、切り出した部分に関しては修正する必要がないので良いですね。

メソッド { return }(パターン4)

次はブロックをとるパターンです。

class UsersController < ApplicationController
  def show
    verify_user { return }
    

    # 何らかの処理
  end
  
  private

  def  verify_user
    unless @user.logged_in?
      redirect_to sign_in_path and yield
    end

    if invalid_user?
      redirect_to users_path and yield
    end
  end
end

メソッド内で return ではなく yield を使います。ブロックで return を実行している部分が yield で実行されます。 これは好みが分かれる書き方かもしれませんね。

メソッド; return if performed?(パターン5)

最後は ActionController::Metal#performed? メソッドを使用するパターンです。 このメソッドは render あるいは redirect が実行されているかどうかを判定するメソッドです。 個人的にはこの方法が1番Rails Wayっぽい感じもするけどどうなんでしょう。

class UsersController < ApplicationController
  def show
    verify_user; return if performed?
    

    # 何らかの処理
  end
  
  private

  def  verify_user
    unless @user.logged_in?
      redirect_to sign_in_path and return
    end

    if invalid_user?
      redirect_to users_path and return
    end
  end
end

まとめ

controller内で早期returnをする方法を見てきました。 特にメソッドに切り出す必要がない場合は古典的な方法を使うのが1番シンプルですね。

curlオプション備忘録

よくDockerfileとかを書いているときに -sSfL というオプションを使ったりするのだけれど、よく意味を忘れるのでまとめておこうと思いました。

curl -sSfL https://xxx.com

-s(--silent)

進捗の表示をしない

-S(--show-error)

エラーを表示

-s オプションはURLが間違っていたり、エラーがあった場合、何も表示しません。 進捗の表示はしたくないけど、エラーがあったら表示したいときには、

-sS

のようにセットで使ってあげると良い。

-L(--location)

リダイレクトする

-f(--fail)

400、500等のエラーの場合、標準出力にエラー文字を出さない

rbenvでRubyを管理する

f:id:whitechocolate27:20191207124847p:plain

僕はRubyのバージョン管理に rbenv を使っています。 他のバージョン管理ツールには RVM なんかもありますね。 最近、自宅のPCを新しくして、環境を構築したのでメモがわりに残してます。

何のためにバージョンを管理するか

例えば、ポートフォリオなんかを作成しようと思って、Aというアプリを作ったとします。仮に、そのアプリではrubyのバージョンが 2.5.0 だったとします。 新しくBというアプリはrubyのバージョンを 2.6.5 に上げて作りたいなという状況があったとします。 そんなときに便利なのがrbenvです。

要は一つのrubyバージョンに縛られることがなくなるということですね。

rbenvの導入手順

rbenvのインストール

今回は vi を使用してログインシェルのファイルに記述していく方法をとります。

~ ❯❯❯ vi ~/.zshrc # bashの方は .zshrc を .bash_profile に置き換えてください

下記のように PATHeval を追記する。

省略

# rbenv path
PATH="~/.rbenv/shims:/usr/local/bin:$PATH"
eval "$(rbenv init -)"
~
~
~
~
~
"~/.zshrc" 19L, 452C

ちなみに vi で編集するときはインサートモードにしなければならないのでファイルを開いた後に I(i) を押して追記するなりコピペするなりしてください。 編集した後は、インサートモードを解除してあげないといけないので esc キーを押して : を押して、 wq で保存してください。

esc キーを押した後は、"~/.zshrc" 19L, 452C の部分にコマンドを打てるようになります。

# rbenv path
PATH="~/.rbenv/shims:/usr/local/bin:$PATH"
eval "$(rbenv init -)"
~
~
~
~
~
:wq # 設定の変更を保存する

設定の変更を反映します。

~ ❯❯❯ source ~/.zshrc # bashの方は .zshrc を .bash_profile に置き換えてください

rbenvをインストールします

~ ❯❯❯ brew install rbenv ruby-build

ここまでくると rbenv コマンドが実行できるようになります。 確認してみます。

~ ❯❯❯ rbenv
rbenv 1.1.2
Usage: rbenv <command> [<args>]

Some useful rbenv commands are:
   commands    List all available rbenv commands
   local       Set or show the local application-specific Ruby version
   global      Set or show the global Ruby version
   shell       Set or show the shell-specific Ruby version
   install     Install a Ruby version using ruby-build
   uninstall   Uninstall a specific Ruby version
   rehash      Rehash rbenv shims (run this after installing executables)
   version     Show the current Ruby version and its origin
   versions    List installed Ruby versions
   which       Display the full path to an executable
   whence      List all Ruby versions that contain the given executable

See `rbenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/rbenv/rbenv#readme
~ ❯❯❯

こんな感じになっていたら成功です。 次にインストールできるrubyのバージョン一覧を見てみます。

~ ❯❯❯ rbenv install -l # --list でも実行できます

たくさん数字が書かれたものが表示されたと思います。 後は、インストールしたいrubyのバージョンを指定してコマンドを実行します。

~ ❯❯❯ rbenv install 2.7.0-dev
Downloading openssl-1.1.1d.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2
Installing openssl-1.1.1d...
Installed openssl-1.1.1d to /Users/kuritakazuki/.rbenv/versions/2.7.0-dev

Cloning https://github.com/ruby/ruby.git...
Installing ruby-master...
ruby-build: using readline from homebrew
Installed ruby-master to /Users/kuritakazuki/.rbenv/versions/2.7.0-dev

インストールするのに若干時間がかかると思います。 最後に忘れずに下記のコマンドを実行すること!

~ ❯❯❯ rbenv rehash # 変更を反映させる

ここまでくるとrubyのバージョンを管理できるようになります。

Ruby技術者認定試験Silverに受かったのでやったことをまとめてみた

先日Ruby シルバーに合格した。 受験するきっかけは、受験しろという上司の半ばパワハラを1年間受けてきてさすがにウザくなってきた事と同僚が公式の一問一答が出てると教えてくれて、解いてみたら割といい点数を取れたことがきっかけ。

基本的に業務でやってることプラス試験用に覚えないとけないメソッドが少しあるといった印象。 Rubyを割とやっていたら受かるんじゃないかなと思った。

やったこと

  • 試験本を購入して問題を解く(当たり前)
  • 解答は理由を答えられるように
  • 絶対出る分野があるのでおさえる
  • 一応、合格記事をググって眺める

前提

試験前の僕のスペックは以下の通り。

  • Ruby歴2年
    • 社会人になってプログラミングを始めた
    • 文系大学院卒
  • Rubyが初めての学習言語
  • IT系の知識はあまりない
    • 一応基本情報は持ってる(試験に受かるための勉強しかしてないので身になってない)

学習期間

  • 3日

学習期間3日とか書くと再現性低いじゃんと思われるかもしれないが、一応業務でも毎日Rubyを使ってるのでそこそこ知識はある状態。 基本的ことはわかってて普段業務で使う機会に恵まれなかったメソッドたち scan とか shift とかを必死に覚えた。 正直3日で受かると思ってなかった。

学習方法

1. 試験本を購入して問題を解く

Ruby技術者認定試験合格教本 という本を買って、巻末についている基礎問題とシルバー用の問題を2回解いた。なぜか僕には基礎問題の方が難しく感じられた。ちなみに1回目解いた際は、基礎問題は19/30問で、シルバー用の問題は37/50問くらい。 試験の概要も記載されているので買って損はないと思う。注意点がいくつかあって、Rubyのバージョンが2.1(試験もこのバージョン)とかなり古いのと誤植が多い。業務では2.5以上のRubyしか扱ったことなかったので、「なんでこの書き方」みたいなところが若干あった。

Ruby歴が浅い人はRubyの基本的なことを理解することも兼ねて基本書を一通りやることをオススメします。 僕が新人時代にRubyを勉強した書籍載っけときます。

チェリー本です。この書籍を通読すればRubyに関してはかなり理解できるんじゃないかと思います。

ゼロからわかる Ruby 超入門 (かんたんIT基礎講座)

ゼロからわかる Ruby 超入門 (かんたんIT基礎講座)

五十嵐さんの書籍。勉強会でお見かけしたことありますが、物腰柔らかくてすごくいい人そうでした。 図を多く用いていて、視覚的に理解できるような構成になっています。 かなり初心者向けなのである程度知識がある人はチェリー本からやればいいと思います。

2. 解答は理由を答えられるように

上記の問題集をやって、正解した問でも必ずなぜ正解なのかを紙に書き出した。 言わずもがな間違った問題は実際にirbで動かしてみて挙動を確かめる。 これを繰り返していく。無心で筋トレするのと同じ感じで。

やっていくうちに自ずと自分の苦手な分野がわかってくるのでそこを潰していく。受験勉強と似た感じの進め方をやった。 ちなみに僕の場合はIOクラスと組み込み系のクラスのメソッドで細かい挙動を覚えられていないことが弱点だった。

3. 絶対出る分野があるのでおさえる

何回か問題集を解いていくとこういうとこ絶対出るなっていうのがわかってくる。 例えば、

  • IOクラス
  • String/Array/Hashクラスのインスタンスメソッド
  • 演算子の優先順位に関して
  • 破壊的or非破壊的か
    • ! がつかない破壊的なメソッド
  • 同じ意味を持つメソッド(mapやcollect等)
  • Timeクラス

etc...

4. 一応、合格記事を眺める

最後に勉強方法とか試験の情報を集める意味でも3~4記事くらい眺めてみるのがオススメ。 多分同じようなことが書かれているので自分の勉強方法ややってることが間違ってないと安心できる。心の栄養剤みたいなもんです。

最後に

だいたい問題集と同じような問題が7割くらいは出るんじゃないかという感じだったが、僕が受験した時は sort に関する問題がやたら出た。業務で使ったことねーわ!!と心の中で憤怒しながらなんとか合格できたのでよかった(試験中は結構間違えた気がしてテンション下がった)。 試験を終えた瞬間にPCに合格か否かが出力されるので受かった時は気分いいけど落ちてたら失うものが大きい(受験料1.6万とその日のテンション)ので十分準備して受験されることをオススメします。3日しか勉強してない僕が言うのもなんですが。

次はGoldとります!