今さらRails3メモ - その4: Modelのちょっと変わった基礎 -
Model は奥が深く、また Rails は Fat Model にしろ、の哲学なので長くなりそうということで小出しにする。
ActiveModelをincludeすればなんでもModelに
Rails 2 以前は ActiveRecord と密結合していたけれど、Rails 3 以降で多くの機能が ActiveModel に切り離された。ActiveRecord を使うアプリの場合はあまり関係ないけど、ActiveRecord から切り離されたということは
DBMS 関係なく Model の機能を利用できる
ことを意味する。
Model のクラスメソッドとインスタンス
Model のクラスメソッドには table_name, human_name などがあるが、Model.new したオブジェクトには存在しない。
ましてや Model.all, Model.find の検索結果に至っては Array なので .table_name, .human_name などのメソッドは存在しない。(しばらく悩んでしまった。アホすぎる。)
ということで Model の定義情報はクラスに聞け。
ただし
当たり前だけど ActiveRecord ベースでない Model には table_name などは存在しない。最終的には connection を張って取得してるらしい。
カラム定義は Model.columns に
Model.columns に必要な情報が入っている。inspect のコードを参考にすると
Model.columns.map { |c|
"#{c.name}, #{c.type}"
}
なんてことをするととりあえず欲しい情報は取れると思う。form field の自動セットとかたぶんできる。
Validation
- validates がなくて値が不正などの DBMS レベルのエラーの場合はアプリケーションの例外画面がいきなり表示される
- validates で引っ掛かったら Flash1 でエラーメッセージを出せる
Rails 3 以降は validator は ActiveModel に属すので、それっぽいプロパティと validator を用意すれば DBMS は無関係に Form を作ったりすることもできる。
validates_* なヘルパーはクラスメソッド
これ分かってなくてハマった(情けない)んだけど、validate 関係のメソッドはクラスメソッドなのでそこからインスタンスメソッドは利用できない。要するに
こういうこと。
validatorの目視チェックを破壊的メソッドで
Ruby on Rails Guides: Active Record Validations and Callbacks
validator がちゃんと動いているかどうかは
The bang versions (e.g. save!) raise an exception if the record is invalid. The non-bang versions don’t: save and update_attributes return false, create and update just return the objects.
ということで ! のついている破壊的メソッドでデータを保存しようとすれば例外が上がって目視しやすい。
- create!
- save!
- update_attributes!
non-bang version(! のついていないメソッド)は false が返るだけだし、rails console で実行していると保存に失敗しても何らかの値がセットされた状態でオブジェクトが表示されるので、
瞬間的に保存に失敗したことを認識しにくい
ので、目視チェックは破壊的メソッドで、と覚えておくと便利。
参考
- Ruby on Rails Guides: Active Record Validations and Callbacks
- Module: ActiveModel::Validations::ClassMethods
- ri ActiveModel::Validations::ClassMethods.validates
idはcreateできないのでseed dataを作る際に注意が必要
Model.create( {} )
の際には id を与えることができない。自動で振られてしまうので。ただし
m = Model.create( {} )
m[:id] = 1
m.save
とすると id を指定できる。
cf. Twitter / Dai Akatsuka: @wtnabe Model.create(:id = …
これの何が問題かというと、id で relation を作る seed data を作っている場合に困る。
fixture の場合はこれは問題にならない。なぜなら ActiveRecord の奥底で直接 INSERT INTO を呼んでいるから。したがってデータの作り方によって
db:fixtures:load はできるけど db:seed はできない
場合がある。
find_by_sql() で複雑なSQLをそのまま書く
Arel は確かにすごいんだけど、複雑な SQL を OO のメソッドチェインだけで実現するのはやはりホネ。素直に SQL を書いた方が簡単なケースはやはり少なからずある。
- Model.find_by_sql( ) で複雑な SQL をそのまま書ける
- ちゃんと sanitize というか quote してくれる
- Model.find_by_sql( [SQL, VAL, VAL] ) で変数への値の埋め込みができる2
この際、join を使った検索結果を console で確認しているとちょっとハマる。console ですぐに目視できる結果は Model 内に定義されている attibute だけなのだ。
しかし実際には join した table の attribute もそのままアクセスできる。これは
Model.find_by_sql().each { |e|
e[:attr]
}
や
Model.find_by_sql().first.attributes
で確認できる。ここで確認できる attribute にアクセスするロジックを自由に書ける。