著作一覧 |
P.144で、なぜread_attributeと記述しているかと質問を受けたのだけど、即答できなかった。で、なぜそう書いたかあらためて考えてみた。
class Item < ActiveRecord::Base def foo x = 3 end def bar puts "#{x}" end end
上のItemでxが属性(itemsテーブルのフィールド)だとすると、xへ代入しようとしているメソッドfooはバグ、xの内容を読み出そうとしているbarは想定どおりに動く。
というのはfooの場合、Rubyはxをローカル変数の宣言とみなす。そのため、3はフィールドxには代入されず、ローカル変数xに代入されるだけとなる。したがってこのメソッドは実行時にエラーにはならないが想定外の動作となる。
barの場合、xという変数も定義済みメソッドも見つからないのでActiveRecord::Base#method_missingを呼び出す。それによってActiveRecordがフィールドxの内容を返す。
したがって、fooを正しくフィールドxへの設定としたければ次のようにwrite_attributeメソッドを呼び出す必要がある。
class Item < ActiveRecord::Base def foo write_attribute(:x, 3) end def bar puts "#{x}" end end
これで期待通りに動く。でも、なんかbarのすわりが悪く感じる。多分、fooとの対称性が悪いからだと思う。
class Item < ActiveRecord::Base def foo write_attribute(:x, 3) end def bar puts "#{read_attribute(:x)}" end end
これで落ち着いた。
もうひとつ安全面からの理由もある。
次のように書くと最悪の事態となるからだ。
def baz x = 3 ... puts "#{x}" end
最初のx = 3でローカル変数xが宣言され3が設定される。そのため、最後の行は、ローカル変数xの内容が読み出されて出力される。しかしフィールドにはまったく設定されない。
エラーにもならず、デバッグ用の出力も正しい値が表示されている。しかしフィールドには未設定。これは最悪だ。
def baz x = 3 #ついこう書いてしまった ... puts "#{read_attribute(:x)}" end
これだと、フィールドxがnilあるいは3の設定前の現在の値の出力となる。x = 3のバグに気づく可能性が高くなる。
write_attributeとアクセサ(実際にはmethod_missingだが)の混在で書くよりも、ActiveRecordのメソッド内ではフィールドの読み書きには必ず*_attributeを利用することで統一したほうが良いと思う。*_attributeで記述するという単一の思考方法(DRY)で済むからだ。
したがって
def baz write_attribute(:x, 3) ... puts "#{read_attribute(:x)}" end
ということを考えたようだ。
気をつけよ! ここより先へ進むものは眉に唾して臨まなければならぬ
追加すると次の書き方は堅実だがActiveRecord流儀ではないと思う。したがって勧められない。追記:不安感も拭えない。
class Item < ActiveRecord::Base def x read_attribute(:x) end def x=(v) write_attribute(:x, v) end ... end
もっとも数の問題もあるので、100回read_attributeと書くのであれば、アクセサメソッドを定義したほうが良いかも(*_attribute記述の重複を避けるか、*_attribute以外のアクセス方法の存在という方法論の重複を避けるか、どっちの重複を避けるかの判断は好みだと思うので「かも」)知れない。
追記:self.x = という書き方は嫌いなのでそれはやらない。
ジェズイットを見習え |
> *_attributeで記述するという単一の思考方法(DRY)で済むからだ<br><br>というところが、ちょっとよくわからなかったです。<br>「重複を繰り返さない」ところがどこにあるのかな、と<br>思ったので。(変なことを書いていたらすみません)
複数の方法がある場合に、「どれにしようかな」と考えないという意味で書きました。<br>上の例だと、属性を読み取るコードを書く都度、「このメソッド内ではローカル変数との重複がないから、属性名を記述しよう。でも、このメソッド内では重複しているからread_attributeじゃなければならないから、それを使おう」という、どの方法を利用するかをその都度選択することは「判断行為の重複(リピート)」です。だったら、いつでも使えるread_arributeを使うのがDRYだ、ということです。というのに加えて書き込みにはwrite_attributeを使うということも合わせて、属性操作=*_attribute というのを条件反射化(ここには深淵がありますが導入時にはその深淵は無視して)するのが良い、なぜならどうすべきかの判断の繰り返しを避けされる、というつもりで書いています。<br>どう記述するかを手に覚えさせるとか。
あ、結論の部分がよたっている部分(あとからやばいと思って区切りを入れた部分)の内側に入っているのがもしかして疑問の理由かな?<br>だとしたらすみません。
09:03のお返事で理解しました。ありがとうございます。<br>属性操作方法として*_attributeという車輪を<br>再発明させない、と理解しました。<br>ありがとうございます。