QA@IT

Railsでbefore validation時の値の取得について

3763 PV

下記コードで、create/updateすると、前者ではstripされているが、後者ではstripされない理由を教えていただけたら嬉しいです。

before_validation do
 self.description = self.description.strip if self.description.present? # work
 description = description.strip if description.present? # doesn't work
end 

p self.descriptionp descriptionのように値をみると、どちらも同じ値でした。

model内で、selfは省略でき、selfを使うときは、classメソッドを追加するときくらいかなー、という浅い認識です。
よろしくお願いいたします。

追記

すみません、コメントの再投稿、編集や削除ができなかったので、こちらに追記しました。
@flied_onion さんありがとうございます。

なるほど、ありがとうございます!modelにdef hoge; end のようなメソッドを追加して、その中で、selfの有無関係なくdescriptionにアクセスできるのは、ActiveRecordのメソッドだからで、before_validationは、ActiveModel::Validationsに定義されていて、descriptionと書いただけでは、description attributeにアクセスできず、ローカル変数として扱われるのですね。

追記2

Paperclip使って、画像を削除する際に、ユーザーが「削除する」にチェックしたら、画像を削除する処理を下記のように書いているのですが、下記はなんで動作するのでしょうか…?この場合、logo attributeにアクセスできているということですよね。

attr_accessor :delete_logo

before_validation do
 logo.clear if delete_logo == '1'
end
  • 回答に追記しました。 -

回答

そのメソッド内で

p local_variables
p self.attributes
description = "value"
p local_variables
p self.attributes
self.description = "val"
p local_variables
p self.attributes
p description.equal? self.description

とすると、その実行結果からわかるように、二つは別の場所にあるものです。
(同時ではなく一つずつ試してみてください。)

具体的にはselfなしだとメソッド内ローカルな変数です。
ですのでself.descriptionには引き継がれません。

余談ですが

p local_variables
description = "abc"

とすると、代入前ですが(おそらくビルド後なので)[:description]となると思います。


追記

descriptionにアクセスできるのは、ActiveRecordのメソッドだからで、

そうです。それについてもう少し細かく見てみます。

まず、ruby的にはクラスのインスタンス変数は @で始まるものです。
これは instance_variablesで確認できます。

では、今回のmodelのdescriptionのようなものは何かと言えばお気づきの通り ActiveRecordによって自動的に追加された メソッド です。参照してもらった attributesもそうですね。これの実体はメンバ変数の @attributesになると思います。

さらに description= もメソッドです。
self.description="abc" は selfオブジェクト(自インスタンス)のdescription= メソッドの呼び出しになります。


さて、次のコードはどの様に動作するでしょう

before_validation do
  p description
end

これは(存在するなら)descriptionメソッドを呼び出してその結果を出力するコードになります。

ところが以下のコードがメソッド呼び出しより前に来た場合はどうでしょう。

description=999

これはローカル変数 descriptionを作成して、999を代入します。

組み合わせると以下の様になります。

before_validation do
  description=999
  p description
end

このとき p descriptionの descriptionはメソッドではなくローカル変数を指してしまいます。
ローカル変数を作成したことによって、メソッドではなくてそのローカル変数を見つけて解決してしまうためで、外にあるメソッドまで評価しにいかないからです。

メソッドとして呼び出されてほしい場合は

before_validation do
  description=999
  p description()
end

と括弧を明示的に付ける必要があります。

逆に言えばローカル変数がなければ、(ActiveRecord)の作成したdescriptionメソッドは呼び出されるのです。
または、selfとオブジェクトを指定すればローカル変数ではなく descriptionメソッドが選択されます。

logo.clear の方もlogoとオブジェクトを指定しているので clearメソッドが正しく呼び出されています。

descriptionと書いただけでは、description attributeにアクセスできず、ローカル変数として扱われるのですね。

この部分は少し間違いです。
descriptionと書いただけでもdescriptionメソッドへのアクセスは可能です。

しかし、繰り返しになりますが

description="123"

とした場合には、文法的にメソッド呼び出しではなくローカル変数への代入として評価されてしまうのです。
descriptionメソッドにとっては名前が競合するものであり、description=メソッドにとっては文法的に先に評価されてしまうものになります。
そしてこの場合はattributesが更新されません(ちなみにdescription=("123")とした場合も代入として扱われます)。

一方 selfとつけた場合はメソッドが探される(というよりローカル変数ではない事がわかる)ので

self.description="123"

とすればdescription= メソッドが呼び出され attributesは更新されますし

p self.description

とすればローカル変数ではなくて、selfのdescription(メソッド)を指していることがわかるので、description属性の内容を取り出せます。

余談ですが メソッドですので、以下の様にしてもselfを付けずに値を変更することができます。

  method(:description=).call("XXX")
  p description()

ちょっとうまくまとまっていないのですが補足してみました。

編集 履歴 (5)
  • なるほど、ありがとうございます!modelにdef hoge; end のようなメソッドを追加して、その中で、selfの有無関係なくdescriptionにアクセスできるのは、ActiveRecordのメソッドだからで、before_validationは、ActiveModel::Validationsに定義されていて、descriptionと書いただけでは、description attrib -
  • 勉強になりました!本当にありがとうございいます!変数とメソッドの呼び出しを全然意識してなかったです。。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。