読者です 読者をやめる 読者になる 読者になる

モデルクラスのインスタンスメソッドで属性を書き換えるにはselfがいる?

探る過程

ARモデル内で、その属性を更新しようとして以下のようなメソッドを書くとする。

# app/models/user.rb
class User < ActiveRecord::Base
  # ...
  def toggle_roll
    status =
      status == "active" ? "inactive" : "active"
    save!
  end
end

Userモデルのインスタンスメソッドの中でのスコープなら、selfはそのインスタンス自身になってるから、例えば

@user.toggle_roll

とかやればselfは@userになるので、このメソッドで行われる手続きは、

@user.status =
  @user.status == "active" ? "inactive" : "active"
@user.save!

だろうと思う。

...

それが、正しくない。

  • When to use self in Model?

http://stackoverflow.com/questions/10805136/when-to-use-self-in-model

ここにあるように、selfを明示しないとlocal variableを定義してしまう(ほんと?)ようで、そのインスタンスの属性を上書きしてくれない。

pry>status
"active"
pry>status = "inactive"
pry>self.status
"active"

attribute methodのレシーバを明示すれば書き換わってくれる。

pry>status
"active"
pry>self.status = "inactive"
pry>self.status
"inactive"

なんでだろう?

ActiveRecord::Baseを継承したモデルのクラスのインスタンスメソッド内でのselfを追いかけてみることにする。

メソッド内でbinding.pryしてselfを見てみる。

pry>self
=> #<User:0x007fcd3a445f20
# ...
pry>self.class.superclass
=> ActiveRecord::Base

おー、普通にUserクラスだし、ActiveRecord::Baseの子クラスになってる。

じゃあなぜ。。

と、さきほどのstack over flowの説明の2番目くらいの回答で理由を説明してくれてる箇所があった。ひとまずまるまる抜粋。

Here's why:

Ruby will do what it can to avoid returning nil.

It initially asks "does active_flag exist within the scope of whats_my_active_flag? It searches and realizes the answer is "nope", so it jumps up one level, to the instance of SomeData It asks the same thing again: "does active_flag exist within this scope? The answer is "yup" and so it says "I got something for ya" and it returns that! However, if you define active_flag within the whats_my_active_flag, and then ask for it, it goes through the steps again:

It asks "does active_flag exist within the scope of whats_my_active_flag? The answer is "yup", so it returns that value In either case, it won't change the value of self.active_flag unless you explicitly tell it to.

An easy way to describe this behavior is "it doesn't want to disappoint you" and return nil -- so it does its best to find whatever it can.

At the same time, "it doesn't want to mess up data that you didn't intend to change" so it doesn't alter the instance variable itself. http://stackoverflow.com/questions/10805136/when-to-use-self-in-model

Rubynilをreturnしないようにする工夫によってこんな感じのことになるとのこと。

最初、そのメソッド定義のスコープでstatusが存在するか探す。そんで、まず「そんなもんはない」という結論になる。そしてレベルをひとつ上げてUserモデルのインスタンスにたどり着く。

そしてもういちどstatusを探す。Userモデルのインスタンスでは、statusがあるのでそれを返してあげる、って流れになる。

だけど、インスタンスメソッド定義のスコープ内でstatusってローカル変数を定義しちゃったら、すぐさまそれを返してしまうようになる。

この場合、前者でも後者でも、self.statusの値は明示しないと書き換えることにはならない。

モデルの属性って、要するにインスタンス変数だよね?

インスタンスメソッドから、インスタンス変数を書き換えるには、レシーバをselfって明示しなければいけないってこと?

これはRuby全般に言えることなのだろうか。。。

と調べてたら、こんな記述が。

小文字または`_'で始まる識別子はローカル変数また はメソッド呼び出しです。ローカル変数スコープ(クラス、モジュー ル、メソッド定義の本体)における小文字で始まる識別子への最初 の代入はそのスコープに属するローカル変数の宣言になります。宣 言されていない識別子の参照は引数の無いメソッド呼び出しとみな されます。 http://docs.ruby-lang.org/ja/1.9.3/doc/spec=2fvariables.html

多分結論的に、こう。

メソッド呼び出しよりローカル変数定義の方が優先順位が高い。

なので、「defスコープ内でfoo = "bar"とかやるのはメソッド呼び出しじゃなくてローカル変数定義と優先して解釈された」ってことなんじゃないだろうか。

うん、そういうことなら上の人の説明がしっくりきた気がする。