個人的メモ:Rails AntiPatterns(7)
CHAPTER7 Testing
- railsはテストを比較的簡単に行えるのでTDD(test-driven development)やBDD(behavior-driven development)に向いている
- Shouldaというフレームワークを使ってテストフレームワークの機能を説明している。
AntiPattern: Fixture Blues
- テストを行う際にFixtureが肥大化してしまう問題。
- yamlファイルにテストケース毎にテストデータをどんどん追加していくと管理出来無くなる。
Solution: Make Use of Factories
- テスト用のFactoryクラスを作り、そのクラス内でデータの登録を行う。
- パターン毎に新たなFactoryクラスを作る必要があるが、Fixtureを管理するよりはまだいい。
- FactoryGirlというgemを使うともっとシンプルにテストデータ作れる。 -例えばテストデータの一部のみを変更して登録するということができる。
Solution: Refactor into Contextsまで読了
関連
個人的メモ:Rails AntiPatterns(6) - 猫の魔法
個人的メモ:Rails AntiPatterns(5) - 猫の魔法
個人的メモ:Rails AntiPatterns (4) - 猫の魔法
個人的メモ:Rails AntiPatterns(3) - 猫の魔法
個人的メモ:Rails AntiPatterns(6)
CHAPTER 5 Services
AntiPattern: Kraken Code Base
- ある程度の期間をかけて巨大になってしまったアプリケーションのこと
- modelの数が多くなるとそれに比例して保守性が下がる。
Solution: Divide into Confederated Applications
- 一番いいのはお互い関係ない機能を別のシステムとして分離する方法。
- 関連がある部分切り出す事も出来る。
- アプリケーションを分離する所までは同じで、APIを作る事によってアプリケーション間の連携を行う。
- APIで直接的にシステム間を繋ぐと、落ちないシステムが要求される。この場合、キューイングの仕組みを使うとベスト。
CHAPTER 6 Using Third-Party Code
- gemを使う際の注意点を記載
AntiPattern: Recutting the Gem
- アプリケーションを構築する際に2回以上繰り返すようなことがある状態
Solution: Look for a Gem First
- 繰り返すようなコードがある場合、まずはgemがないかを探してみる。
AntiPattern: Amateur Gemologist
- 十分に成熟していないgemを使ってしまっている状態
Solultion: Follow TAM
- TAM(test, activity, and maturity)の原則に従い、使うgemを評価する。
- 自動テストがついている事(test)
- コミュニティが積極的に活動している事(ソースコード、チケット等の更新が最近されている事)(activity)
- 作成されてから時間が経過していて、よく管理され、多くのユーザがいる事(maturity)
AntiPattern: Vendor Junk Drawer
- gemを利用することは一般的にいいことだが、使うgemの管理に注意を払わず、闇雲にgemを使ってしまっている状態。
Solution: Prune Irrelevant or Unused Gems
- 使用していないgemを削除する。
- ただこれは簡単なことではないため、そもそもgemの追加をするときに慎重になるのが正しい。
AntiPattern: Miscreant Modification
- 使っているgemに不具合がある状態。
Solution: Consider Vendored Code Sacrosanct
module Hoge class Piyo def print p 'piyo' end end
lib/hoge_extensions.rb
module Hoge class Piyo def print p 'gao' end end
- 修正が恒久的な解決策なら、forkして開発にプルリクエストするといい。
CHAPTER7 Testingの前まで読了。
関連
個人的メモ:Rails AntiPatterns(5)
CHAPTER 5 Services
- ここで言うサービスとはサービスクラスのそれというよりAPIやウェブサイトと言った外部が提供or外部に提供する物の総称の事。
AntiPattern: Fire and Forget
- 外部のAPIを呼ぶ際に、「例外返却される可能性があるメソッド」の例外を取得しないことによって発生する問題。
- 本来であれば無視したい例外を無視出来なくる。
Solution: Know what Exceptions to Look Out For
- とりあえず全部rescueして、何もしないというのが一番簡単
- だがその場合、本当の例外も全部消えてしまう。
- どのような例外が発生するか調べて、rescueする必要がある例外を配列の定数に持つ
- 必要な例外のみを無視出来る。万が一見落としがあったとしても配列にその例外を追加するだけで足りる。
- 例外が調べられないような場合は分かっている最低限の例外だけをrescueする。
- このような場合は、未知の例外が発生を追えるように、ロギングする事が大切。
AntiPattern: Sluggish Service
- 応答が遅い外部サービスによりアプリケーション全体が上手く動かなくなる問題
Solution: Set You Timeouts
- Net::HTTPライブラリを使っている場合、デフォルトタイムアウトが60秒なので、これを短くすることで、アプリケーション全体の問題から局所的な問題へと置き換えることが出来る。
- ようは待ち続けてコネクションプールが枯渇するというような致命的な問題をさけられるよという話だと思った。
Solution: Move the Task to the Background
- 処理を非同期にしてしまう。
- バックグランド処理をする仕組みとしてdelayed_jobを使うのがよい
AntiPattern: Pitiful Page Parsing
Solution: Use a Gem
- Nokogiriというgemを使って解析するとよい
AntiPattern: Successful Failure
- API等で外部に応答するシステムの場合、エラーの際にその応答にエラーコードを返すようなシステム(エラーが発生したかどうかは応答をパースする必要がある)
Solution: Obey the HTTP Codes
- エラーの場合は適切なHTTPレスポンスコードを返すようにする。
- RESTfulなAPIだとこれはその通りという感じ。
- 画面であってもエラーの際に正しいレスポンスコードを返すのは重要。例えば認証エラー等は401を返すべき。
AntiPattern:Kraken Code Baseの手前まで読了
関連
個人的メモ:Rails AntiPatterns(4)
Rails AntiPttrnsに関する個人的メモ。今日読んだとこまで。
CHAPTER 4 Controllers
Solution: Refactor Non-RESTful Actions into Separate Controller
- この章ではRESTfullなContollerが責務を負いすぎている例として、認証管理を上げている。
- 認証機構はUsersコントローラのようなものに直接実装するのではなく、それ用のクラスを作りログインが必要なときに呼び出せるようにroutes.rbを記載する。
- Railsで作成されるnewとeditアクションは純粋なRESTfulには必要ない。画面の場合のみ、人間のUI上必要になっている。
AntiPattern: A Lost Child Controller
- 1対多のモデルをview側で多側の情報と同時に1側の情報を取得する際に、1側のIDを指定する必要があるような状態。
- computer:display 1:多のような形だと/computer/:computer_id?display=:idのような形でアクセスするような物
Solution: Make Use of Nested Resources
- computer:display 1:多のような形だと/computer/:computer_id?display=:idのような形でアクセスするような物
- routes.rb側でresourceをネストするようにする。
- /computer/:computer_id/display/:idのような形でアクセス出来るようにする。
AntiPattern: Rat’s Nest Resources
- 同一のモデルがネストされてアクセスされる場合と、単一でアクセスされる場合の話。
- 同じViewテンプレートを使っているが、表示条件が異なる為にView内に条件分岐が生まれてしまっている状態。
Solution: Use Separate Controllers for Each Nesting
- Controller側をネストするものとそうでない物で分離し、Viewテンプレートも別物を使う。
- 例えば、controllers/display_controller.rbはcontrollers/display_controller.rbとcontrollers/computer/display_controller.rbに分離する。
- この方式にするとサブディレクトリに入るものとそうでない物が混在する可能性があるので、サブディレクトリを切るなら必要なものはみなそこに入れるようにした方がいい。
- また、この方式はDRYの原則を弱めることに繋がる可能性があるが、上手く使えば保守しやすいコードになる。
AntiPattern: Evil Twin Controllers
Solution: Use Rails 3 Responders
- Rails 3から導入されたrespond_to メソッドとrespond__withメソッドを使う事で解決出来る。
- ★このrespon_toから呼び出している実体が恐らく掲載されているコードの他にある気がするのだが、respond_to自体を調べないとなんとも。。。
CHAPTER 5 Servicesの手前まで読了
関連
個人的メモ:Rails AntiPatterns(3)
Rails AntiPttrnsに関する個人的メモ。今日読んだとこまで。
CHAPTER 4 Controllers
AntiPattern: Bloated Sessions
- Railsは仕組み上ステートレスな動きを得意としている。
- ステートフルな動きをなんらかの理由でしなければいけない場合、その内容をクッキーとしてクライアント側に保存することになる。
- このクッキーになんでもかんでもツッコミ過ぎるのはよくない。
Solution: Store References Instead of Instances
- DBではなくセッションにオブジェクトそのものを格納しないでIDを引き回すようにする。
- セッションが短い場合はviewのHiddenに値を持たせるのもあり(本当か?)
AntiPattern: Monolithic Controllers
- RESTfulの原則に従わない動作をしようとしているコントローラ。
Solution: Embrace REST
この場合はRESTfulになるように直す。直す前にインテグレーションテストを書いておくことを忘れずに。
AntiPattern: Controller of Many Faces
- アプリケーションが大きくなって非RESTfulな動作がコントローラ内に混じってしまっている状態。
Solution: Refactor Non-RESTful Actons into Separte Controlerの手前まで読了
関連
個人的メモ:Rails AntiPatterns (2)
Rails AntiPttrnsに関する個人的メモ。今日読んだとこまで。
CHAPTER 4 Controllers
AntiPattern: Fat Controller
- コールバックは条件付きにすることも出来る。
- モデル側に処理を寄せる場合はトランザクション処理をコントローラ側に持たせる必要はない。
- _idメソッドよりもアソシエーションプロキシを使う
- ★association proxyがよく分からなかったので調査。
Solution: Move to a Presenter
- コールバックに持っていけばいいという話でもない。バランスが重要
- 特に2つのモデルに対する更新処理等で、複数のモデル間にトランザクションを貼らなければならない場合、
コントローラでもモデルでもない第3の仕組みを用いてそちらに寄せるのがいい
よくない例
class HouseControleer < ApplicationController def create @house= House.new(params[:house]) @address = Address.new(params[:address] @house.address = @adress ActiveRecord::Base.transaction do @address.save! @house.save! end redirect_to(@address) end
記事の中ではMVPとMVCを合体させて、VC間にPを持ってくる手法が紹介されている。
- 具体的にはPとしてActive Presenterモジュールを使っている。
- 上の例だとhouseとaddressをpresentsに持たせたActivePresenter::Baseを継承したクラスを作成する。
- そのクラスをnewしてsaveするようにコントローラ上は実装すれば、余計なトランザクションがいらなくなる。
- ★今は理解しているが、この書き方だと忘れそうなので、もう少し具体例を考える。
- そのクラスをnewしてsaveするようにコントローラ上は実装すれば、余計なトランザクションがいらなくなる。
AntiPattern: Bloated Sessionsの前まで読了
関連
個人的メモ:Rails AntiPatterns
Rails AntiPttrnsに関する個人的メモ。今日読んだとこまで。
CHAPTER 4 Controllers
AntiPattern: Homemade Keys
AntiPattern: Fat Controller
- トランザクションの管理等は普通モデルに寄せる
- 具体的にFat Controllerになってしまっている物をいかにしてModelに寄せるかの実例
- 更新日/登録日は独自に持たない。updated_atかcreated_atを使う事。
- デフォルト値はロジックでなくテーブル側に持たせる
- モデルのコールバックに処理を寄せられないか考える
- ★後でもう一度読む。
- Simplified Calbacksの手前まで読了。
関連
JSON.parseのquirks_modeと2.4.0での変更
どんだけJSONネタで引っ張るのかという話もあるが、色々と調べて見て分かった事があったのでブログに記録しておく。
JSON.parseでは:quirks_modeというオプションがある。
通常JSONを解析する場合、そのトップレベルはオブジェクト若しくは配列になっている必要があるはずだが、 このオプションが真のときは単一のパラメータを解釈してくれる。
irb(main):001:0>require 'json' => true irb(main):002:0>JSON.parse("12",{:quirks_mode=>true}) => 12 irb(main):003:0>JSON.parse("12",{:quirks_mode=>false}) JSON::ParserError: 784: unexpected token at '12' from /Users/nekomaho/.rbenv/versions/2.3.1/lib/ruby/2.3.0/json/common.rb:156:in `parse' from /Users/nekomaho/.rbenv/versions/2.3.1/lib/ruby/2.3.0/json/common.rb:156:in `parse' from (irb):9 from /Users/nekomaho/.rbenv/versions/2.3.1/bin/irb:11:in `<main>' irb(main):004:0> JSON.load(nil,nil,{:quirks_mode=>true}) => nil irb(main):005:0> JSON.load(nil,nil,{:quirks_mode=>false}) TypeError: no implicit conversion of nil into String from /Users/nekomaho/.rbenv/versions/2.3.1/lib/ruby/2.3.0/json/common.rb:156:in `initialize' from /Users/nekomaho/.rbenv/versions/2.3.1/lib/ruby/2.3.0/json/common.rb:156:in `new' from /Users/nekomaho/.rbenv/versions/2.3.1/lib/ruby/2.3.0/json/common.rb:156:in `parse' from /Users/nekomaho/.rbenv/versions/2.3.1/lib/ruby/2.3.0/json/common.rb:335:in `load' from (irb):4 from /Users/nekomaho/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'
このオプション、ruby 2.4.0から廃止され:allow_blankというオプションになっている。
正確には、nil以外の単一のパラメータを解釈するのがデフォルトの挙動となり、 :allow_blankで"nil"を受けるかどうかのみ変更出来るようになったというのが正しい。
irb(main):001:0>require 'json' => true irb(main):002:0> JSON.load(nil,nil,{:allow_blank=>true}) => nil irb(main):003:0> JSON.load(nil,nil,{:allow_blank=>false}) TypeError: no implicit conversion of nil into String from /Users/nekomaho/.rbenv/versions/2.4.0/lib/ruby/2.4.0/json/common.rb:156:in `initialize' from /Users/nekomaho/.rbenv/versions/2.4.0/lib/ruby/2.4.0/json/common.rb:156:in `new' from /Users/nekomaho/.rbenv/versions/2.4.0/lib/ruby/2.4.0/json/common.rb:156:in `parse' from /Users/nekomaho/.rbenv/versions/2.4.0/lib/ruby/2.4.0/json/common.rb:335:in `load' from (irb):15 from /Users/nekomaho/.rbenv/versions/2.4.0/bin/irb:11:in `<main>'
という指摘も出ていて、本当にこの変更がそのまま2.4系に適用されるのかは怪しい。
関連
JSON.loadはオプションが取れる
先日の記事の中でJSON.loadのgithubの実装を紹介した。
Rubyの公式ドキュメント(以下るりま)JSON.loadの説明とgithubのコードを眺めていてあれ?るりまの説明と実装が違う。。。と言う事に気がついた。
るりま側のloadの定義は以下の通り
load(source, proc = nil) -> object
github側のloadの定義は以下の通りで、最後にオプションを取るかどうかがるりま側の物と異なる。
def load(source, proc = nil, options = {})
実際に試してみると、procの後ろにparseで指定出来るオプションをハッシュで渡すとちゃんと動く。
hoge1.json
{"color":{"red":1,"blue":2,"green":NaN}}
hoge.rb
require 'json' File.open("hoge1.json") do |f| p JSON.load(f,nil,{ :symbolize_names=>true }) end => {:color=>{:red=>1, :blue=>2, :green=>NaN}}
これ、今まで「loadはoptionが渡せないからfileをreadしてからparseを使おう」と考えていた人も結構いるだろうなと思う。
なお、もっというとJSON.loadはload_default_optionsというインスタンス変数をメソッド内でデフォルトオプションとして定義していて、 下記のようなオプションが事前に設定されている。
require 'json' p JSON.load_default_options => {:max_nesting=>false, :allow_nan=>true, :quirks_mode=>true, :create_additions=>true}
このload_default_optionsはatter_accessorとして定義されており、 外から設定を変更する事が出来る。
要するに、loadにオプションを指定する方法は、loadの第3引数に値を与える方法と、 load_default_optionsに値をセットする方法の2通りがあるという事になる。
自分は前者は一時的に指定するオプションを、 後者はアプリケーション全体で指定するオプションを設定するようなイメージかと勝手に解釈してる。
ところでこのJSON.#load、ソース上に以下の記述がある。
# BEWARE: This method is meant to serialise data from trusted user input, # like from your own database server or clients under your control, it could # be dangerous to allow untrusted users to pass JSON sources into it. The # default options for the parser can be changed via the load_default_options # method.
詰まる所、このメソッドは与えられるJSONが必ず信頼出来る物でなければいけないという制約があるようで、 実装上はparseの方を使う方が安全なようだ*1
上記の制約は恐らく :create_additionsがデフォルトでtrueな事が原因だと思うが、それなら今回の方式でfalseに出来そうな気もする。正直よく分からないので、知っている人いたら教えて下さい。。。
とりあえず、loadの記載については以下の記事参考にして、るりまに修正のプルリクエストを 投げてみた
http://qiita.com/tbpgr/items/2072a9a743918b8cfb34
※何か更新しない理由があって、直さなくていい物だったら恥ずかしいけど。。。これも勉強ですな。
※2017/2/11追記 mergeして頂きました。