個人的メモ: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して頂きました。
JSON.parseとJSON.loadの違い
JSON.parseとJSON.loadの差が何なのか分からなかったので調べてみた。
※下記の参考のStackOverflowの内容がその答えだが、裏を取りたかった。
require 'json' File.open("hoge.json") do |f| JSON.parse(f.read) == File.open("hoge.json"){ |f| JSON.load(f) } ? p("same") : p("diffrent") end => "same"
※File.openを2回呼んでいるのはどげんかせんといかん。
Rubyの公式ドキュメント:parse、:loadを見ると、parseはparamのsourceがJSONの文字列しか受け付けないのに対して、 loadはto_str,to_io, readを持つオブジェクトもparamのsourceに指定可能と記載されている。
githubのJSON.loadの実装を見ると、loadがparseをラップしているのが分かる。
今回のようにファイルから読み込む場合、parseはreadから返された文字列が、loadはFile.to_io.readで返された文字列が使われる為、両方供同じようにfileからJSONファイルを読み込める形になるようだ。
loadはドキュメントの通り、loadに渡されたオブジェクトがto_str,to_io,readのどれかを返せばいいので、 String以外のオブジェクトからparseを呼び出す際は、loadを使用する形になるのではないかと思う。
参考
StackOverflow:What’s the difference between JSON.load and JSON.parse methods of Ruby lib?
classとそれが読み込んでいるmoduleの一覧を表示する方法
クラスの継承関係と各クラスで読み込んでいるモジュールの一覧を表示したかったので作ってみた。
def display_class_modules(class_name) print "class:#{class_name},modules:#{class_name.included_modules}","\n" if class_name.respond_to?(:included_modules) display_class_modules(class_name.superclass) if class_name.respond_to?(:superclass) end display_class_modules(1.class) #=> class:Fixnum,modules:[Comparable, Kernel] #=> class:Integer,modules:[Comparable, Kernel] #=> class:Numeric,modules:[Comparable, Kernel] #=> class:Object,modules:[Kernel] #=> class:BasicObject,modules:[]
1のクラスはFixnumでComparableとKernelモジュールをインクルードしている事と、 継承チェーンがFixnum->Integer->Numeric->Object->BasicObjectの順に上がっている事が分かる。
疑問点
- Moduleは何処に行ったのだろう?(included_modulesにclass_nameが反応しているのにModule自体がイングルードしているモジュール一覧に出てこない)
改善ポイント
- moduleの中のinclude関係も見れるとよい
- もっと簡潔に書きたい(respond_to?とかまとめて書けないか)