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」についてまとめました。
今回は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」についてまとめました。
今回は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」についてまとめました。
今回は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
外部サービスを利用するとき、レスポンスの扱いとしては以下のようなものがある。
- レスポンスをチェックし、エラーごとに適切な処理を行う。
- 成功、エラーのみチェックする。
- 何もチェックしない。
今回のアンチパターンは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::Error
がRuntimeError
を継承していないためらしいが、今なら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を読みたい。