blog.waterlow.work

Ruby, Rails, js, etc...

Rails アンチパターン - モノリシックなコントローラ(Monolithic Controllers)

引き続きRails AntiPatternsという本を読んでいます。

https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

前回は7 Testの1つめ「Fixture Blues」についてまとめました。

waterlow2013.hatenablog.com   今回は4 Controllersの4つめ「Monolithic Controllers」についてまとめます。

Monolithic Controllers

解決策有りきなのだが、REST原則に乗っていないコントローラをRESTに乗せる。 以下のようなサンプルがあったとする。

class AdminController
  def users
    #...
    if not params[:operation].nil?
      if params[:operation] == "reset_password"
        # ...
      end
      if params[:operation] == "activate_user"
        #...
      end
      if params[:operation] == "show_user"
        #...
      end
    end
    user_order = 'username'
    if not params[:user_sort_field].nil?
      user_order = params[:user_sort_field]
      # ...
    end
  end
end

URLは以下になります。

POST /admin/users?operation=reset_password?id=x
POST /admin/users?operation=delete_user?id=x
POST /admin/users?operation=activate_user?id=x
GET /admin/users?operation=show_user?id=x
GET /admin/users

usersはアクションではなくリソース名であるべきなのでうつす。また、operationの中身をアクションに移動する。 urlは以下のように変わる。

POST /admin/users/:id/password
DELETE /admin/users/:id
POST /admin/users/:id/activation
GET /admin/users/:id
GET /admin/users

コードも変わる

class UsersController < ApplicationController
  def index
    # ...
  end

  def destroy
    # ...
  end

  def show
    # ...
  end
end

class PasswordsController < ApplicationController
  def create
    # ...
  end
end

class ActivationsController < ApplicationController
  def create
    # ...
  end
end

config/routes.rbは以下のようになる。

namespace :admin do
  resources :users do
    resource :passwords
    resource :activations
  end
end

まとめ

index, new, create, edit, update, show, destroy以外のアクションがある場合や、アクションが7つに含まれていても1つのアクションが大きくなっている場合はRESTに寄せつつコントローラを分け、先に書いた7つのアクションに乗せるのがいいと思います。

DHHのコントローラの作り方の記事にも書いてましたね! postd.cc

Rails アンチパターン - Fixtureの憂鬱(Fixture Blues)

引き続きRails AntiPatternsという本を読んでいます。

https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

前回は4 Controllersの3つめ「Bloated Sessions」についてまとめました。 waterlow2013.hatenablog.com

今回は7 Testの1つめ「Fixture Blues」についてまとめます。

Fixture Blues

fixtureはRailsにデフォルトで組み込まれているテストデータ作成機能だが、以下のような難点がある。

  • 通化すると変更したときに壊れやすい。
  • 毎回作っているとfixtureが大量にできてしまう。

Solution: Make Use of Factories

  • FactoryGirl gemを使おう。
  • 各フィールド のオーバーライドが容易なので、fixture量産しなくていい。

正直fixtureをそこまでちゃんと使ったことがいので、fixture vs factoryに関しては何も言えません。rspec bookもfactory girlを取り上げつつも反対する意見があることも書かれています。 fixture vs factoryに関してはググると色々出てきますが、以下の記事がわかりやすいかも。 blog.jnito.com

Solution: Refactor into Contexts

  • testunitでもcontext分けが出来るようになるgemの紹介と、context入れると明示的でかつDRYになるという話。特にテストデータのセットアップ。

これもrspecしか使ったことがないので、良さに関してはよくわからず…。

rspecにおけるdescribe, context辺の使い方は以下のwillnet氏のrspec coding stileを読むと良さそう。 github.com

まとめ

Rspecやfactory gilrに関してまだまだ知らない事があるなと思ったのでドキュメント読んだりして身につけていく必要があるなと思いました。 あとはfixtureやminitestもどこかでやりたい。

Rails アンチパターン - 太ったセッション(Bloated Sessions)

引き続きRails AntiPatternsという本を読んでいます。

https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

前回は10 Building for Failureの2つめ「Inaudible Failures」についてまとめました。 waterlow2013.hatenablog.com

今回は7 Controllersの3つめ「Bloated Sessions」についてまとめます。

Bloated Sessions

セッションに情報を入れておくとユーザやDBとの通信を節約でき、コードがシンプルになる可能性もある。 しかし、RESTの原則から外れるし、スケールアウトに向かない。またActiveRecordのオブジェクトをセッションやキャッシュに入れておくと、Railsのバージョン変更のときに動かなくなることがある。

Solution: Store References Instead of Instances

  • sessionにはidのみを入れる  残りはDBに入れる。  current_userとか。Railsチュートリアルにもあったと思う。
  • hiddenタグに入れてクライアントに持たせる。  formとかでよくやるやつですね。

まとめ

今回も反論の余地なしの回でした。 キャッシュにARのオブジェクト入れるのはよくやってしまうので気をつけたいです!

Rails アンチパターン - 無視されるエラー(Inaudible Failures)

引き続きRails AntiPatternsという本を読んでいます。

https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

前回は10 Building for Failureの1つめ「Continual Catastrophe」についてまとめました。

waterlow2013.hatenablog.com

今回は10 Building for Failureの2つめ「Inaudible Failures」についてまとめます。

Inaudible Failures

エラー対策はUXに影響を与える。

class Ticket < ApplicationRecord
  def self.bulk_change_owner(user)
    all.each do |ticket|
      ticket.owner = user
      ticket.save
    end
  end
end

たとえばこのようなコードでは、Ticketモデルのバリデーションでエラーになってしまってもスルーしてしまう。 ユーザにとって同じ結果でも削除されていたりされていなかったりする。

Solution: Never Fail Quietly

そんなことはやめようという話。

上のサンプルコードでれば、saveは保存できなかったときに例外をあげるsave!に変える。 またエラーが発生した場合にロールバックするためにトランザクションで囲む。そのほうが一貫性がある。

class Ticket < ApplicationRecord
  def self.bulk_change_owner(user)
    transaction do
      all.each do |ticket|
        ticket.owner = user
        ticket.save!
      end
    end
  end
end

その他以下のようなtipsがある。

  • controllerのrescue_fromで500用エラー画面を出す。
  • rescue nilは使わない。
  • 外部サービスを使ってエラーを蓄積・通知する。

まとめ

この章もその通りだなという感じでした。rescue_from使うとか、外部サービス使うとかは確実にやっていきたいです。
rescueについては前も書いたのですが、StandardErrorではなく限定された例外をrescueするのが良さそう。

Rails アンチパターン - 継続的な大災害(Continual Catastrophe)

引き続きRails AntiPatternsという本を読んでいます。

https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

前回は6 Using Third-Party Codeの1つめ「Recutting the Gem」についてまとめました。

waterlow2013.hatenablog.com

今回は10 Building for Failureの1つめ「Continual Catastrophe」についてまとめていきます。

Continual Catastrophe

サンプルとして以下のようなスクリプトを挙げる。

cd /data/tmp/
rm-rf*

/data/tmp/がない場合に、ディレクトリ移動せず2行目が実行されたらどうなるか。ディレクトリが存在しない場合そこで終了するのが望ましい。(bashには-eや&&演算子がある)

Solution: Fail Fast

以下のようなコードがあるとする。

class Portfolio < ApplicationRecord
  def self.close_all!
    all.each do |portfolio|
      unless portfolio.photos.empty?
        raise "Can't close a portfolio with photos."
      end
      portfolio.close!
    end
  end
end

これは途中でエラーになった場合に中途半端にcloseされてしまうので、初めにチェックするようにする。

class Portfolio < ApplicationRecord
  def self.close_all!
    all.each do |portfolio|
      unless portfolio.photos.empty?
        raise "Can't close a portfolio with photos."
      end
    end

    all.each do |portfolio|
      portfolio.close!
    end
  end
end

更に早くチェックする方法として、UIからclose_allできないようにする,コントローラでリクエストを弾くなどがあげられる。

Fail Fastのメリットは

  • 大災害を防げる
  • はじめにチェックが宣言的に書かれているのでわかりやすい
  • よくわからんところでぬるぽがでるのはうんざりだ!

たとえばARのfind_byとかも、レコードあり前提であればfind_by!で適切なエラーで失敗させるのがわかりやすいかなと思います。(ただし、例外を制御フローに組み込むのは絶対NG)createとかも同様ですね。(controllerのcreate, update内は別)

まとめ

その通りだなと思っていました。(それ以上の感想がない…。)

Rails アンチパターン - gemを使わない(Recutting the Gem)

引き続きRails AntiPatternsという本を読んでいます。

https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

前回は5 Servicesの1つめ「Fire and Forget」についてまとめました。

waterlow2013.hatenablog.com

今回は6 Using Third-Party Codeの1つめ「Recutting the Gem」についてまとめていきます。

Recutting the Gem

いくつかのRailsアプリケーションに関わっていると、共通のパターンまたは機能を実装していることがあります。 その機能を必要としている人は世界にたくさんいるはずです。

「だから、gem作りましょう」という話ではないんですよね笑 Solution見ます。

Solution: Look for a Gem First

  • コードを書く前にgemを探しましょうという話
  • 同じような実装を何回もやっていたとしたら、それはgemが使えるかもしれないというサイン

自分も新しいアプリや新しい機能をRailsで実装するときは、めちゃくちゃググりまくってgemや実装、アーキテクチャ等参考にできないか調べているような感じがします。 探し方にもコツが有るのですが、それは次の章で書いてくれているらしいのでそのときに触れようと思います。

まとめ

「ぼくのかんがえたさいきょうのRails開発」みたいなやつどっかでまとめたくなった!

Rails アンチパターン - ファイア・アンド・フォーゲット(Fire and Forget)

引き続きRails AntiPatternsという本を読んでいます。

https://www.amazon.co.jp/dp/B004C04QE0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

前回は4 Controllersの2つめ「Fat Controller」についてまとめました。 waterlow2013.hatenablog.com

今回は5 Servicesの1つめ「Fire and Forget」についてまとめていきます。 ここで言うServicesとは外部サービスのことで、サービスクラスやアプリケーションサービスのことではありません。

Fire and Forget

ファイア・アンド・フォーゲット - Wikipedia

外部サービスを利用するとき、レスポンスの扱いとしては以下のようなものがある。

  • レスポンスをチェックし、エラーごとに適切な処理を行う。
  • 成功、エラーのみチェックする。
  • 何もチェックしない。

今回のアンチパターンは3つ目のケース。コントローラで直接呼んでいるとなるとユーザに500エラーを返すことになってしまう。

Solution: Know What Exceptions to Look Out For

発生するエラーを列挙しておき、適切にハンドリングする。たとえばHTTPクライアントなら以下のようなエラーがある。

HTTP_ERRORS = 
  [Timeout::Error,
   Errno::EINVAL,
   Errno::ECONNRESET,
   EOFError,
   Net::HTTPBadResponse,
   Net::HTTPHeaderSyntaxError,
   Net::ProtocolError]

rescue => eしないのは、Timeout::ErrorRuntimeErrorを継承していないためらしいが、今ならRuntimeErrorを継承しているので、雑にやりたければrescue => eでもいいと思う。

Message in a Bottle

  • config.action_mailer.raise_delivery_errorsの設定に気をつける
  • エラーには大きく分けて2種類ある。クライアント側とサーバ側
  • クライアント側のエラー(メールアドレスがおかしい等)はユーザに即通知する
  • サーバ側のエラーはスタッフに通知する等して、何か対応を入れる。

You Don’t Know What You Don’t Know

  • rescueしすぎない
  • 少なめのrescueから初めて必要なものを追加する
  • エラーログサービス(NewRelic, errbit等)も重要
  • エラーハンドリングの詳しくはchapter10を

まとめ

特に違和感を感じたところはなかったです!外部サービスを叩くものは常にエラーが起こりうるという扱いをするのが自然なんだなと思いました。
あとは早めにchapter10を読みたい。