トップ «前の日記(2006-08-18) 最新 次の日記(2006-08-20)» 編集

日々の破片

著作一覧

2006-08-19

_ ActiveRecord内部での属性の読み書き

かんたんRuby on RailsでWebアプリケーション開発(arton)

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 = という書き方は嫌いなのでそれはやらない。

本日のツッコミ(全4件) [ツッコミを入れる]
_ hyuki (2006-08-19 20:45)

> *_attributeで記述するという単一の思考方法(DRY)で済むからだ<br><br>というところが、ちょっとよくわからなかったです。<br>「重複を繰り返さない」ところがどこにあるのかな、と<br>思ったので。(変なことを書いていたらすみません)

_ arton (2006-08-20 09:03)

複数の方法がある場合に、「どれにしようかな」と考えないという意味で書きました。<br>上の例だと、属性を読み取るコードを書く都度、「このメソッド内ではローカル変数との重複がないから、属性名を記述しよう。でも、このメソッド内では重複しているからread_attributeじゃなければならないから、それを使おう」という、どの方法を利用するかをその都度選択することは「判断行為の重複(リピート)」です。だったら、いつでも使えるread_arributeを使うのがDRYだ、ということです。というのに加えて書き込みにはwrite_attributeを使うということも合わせて、属性操作=*_attribute というのを条件反射化(ここには深淵がありますが導入時にはその深淵は無視して)するのが良い、なぜならどうすべきかの判断の繰り返しを避けされる、というつもりで書いています。<br>どう記述するかを手に覚えさせるとか。

_ arton (2006-08-20 09:07)

あ、結論の部分がよたっている部分(あとからやばいと思って区切りを入れた部分)の内側に入っているのがもしかして疑問の理由かな?<br>だとしたらすみません。

_ hyuki (2006-08-20 21:33)

09:03のお返事で理解しました。ありがとうございます。<br>属性操作方法として*_attributeという車輪を<br>再発明させない、と理解しました。<br>ありがとうございます。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|

ジェズイットを見習え