QA@IT

ActiveRecordのModelのインスタンスの値を変更するには?

6895 PV

現在Rails3.2で開発をしているのですが、ActiveRecoredで作ったモデルのインスタンスの、データを変更しようとしているのですが、やり方がわからなくてこまっています。

具体的に現在の状況を例に説明させていただきます。

class Blog < ActiveRecord::Base
  attr_accessible :content, :title
  belongs_to :user

  def set_instance
    title = "test title"
    @content = "test content"

    p title #=>"test title"
    p @content #=> "test content"

    p @title #=>nil
    p  content #=> nil
  end
end

まずこんなモデルクラスがあります。

これに対して、次のようにコントローラで呼び出します。

   def update
       blog = @user.blog.build
       blog.set_instance

       blog.title #=> nil
       blog.content #=> nil
   end

ここで疑問がいくつかあります。
まず、Blogクラス内でtitle等の値を更新するには、どうすればいいか?
次に、Blogクラス内の@titleというインスタンス変数は何か?
3つめに、Blogクラス内のtitleとは何を呼び出しているのか?(クラス変数?インスタンス変数?)

どれか一つでもかまいませんので、ご回答いただけると嬉しいです。

回答

質問をうけて、なぜか編集できなかったので、別回答として追記

まず、

(2)すべてのインスタンスメソッドは、呼び出された際にtitleというローカル変数が@titleから値をコピーされて作られる。

については、まだ勘違いがあるようです。まずここで理解すべきは、名前が重複したら、より内側のスコープの名前が優先される(変数のシャドーイング)ということです。例を出すと、

class C
  def foo
    foo = 1
    p foo
  end
end

C.new.foo
 => 1

のようになります。ここで、fooはメソッド名であると同時に、そのメソッド内で定義されているローカル変数(foo = 1)でもあることに注意してください。そうすると、このメソッド内のスコープでは、ローカル変数のほうが優先して参照されます。それが証拠に、p foop self.fooと明示的に書き換えると、今度はローカル変数ではなくメソッドのほうのfooがよばれるので、再帰呼び出しが無限に実行され、stack level too deepでエラーになります。

ローカル変数より狭いスコープとはなんでしょうか?それはブロックスコープです。

i = 5
[1,2,3].map{|i| i }
 => [1, 2, 3]

のように、先に定義したブロックの外側のi = 5は、シャドーイングされてブロック内部からはアクセスできなくなっています。まずは、ここまでをきっちり理解されると、列挙されてる疑問の大半は片付くと思います。

さて、ここまでをふまえて、ようやく本題のAR (ActiveRecord) 固有の話になるのですが、ARでは、むきだしのインスタンス変数を単純に使っているわけではなく、@attributesというハッシュにフィールドデータを保存しているのです。ためしに、Blog.first.instance_variable_get(:@attributes)とやってみると、その中身をみることができます。

つまり、今回の場合は、参照用のtitleと更新用のtitle=という、型変換など色々なことをやったうえで@attributesハッシュを操作してくれるリッチなメソッドが、テーブルのカラム情報から自動的に定義されているのです。このリッチな処理をバイパスして直接的に要素にアクセスしたければ、read_attributewrite_attributeを使う必要がある、ということです。

よくあるパターンとしては、カラムとして存在するtitleメソッドの挙動を上書きして自前の処理をしたい場合、こんな感じで書きます。

class Blog < ActiveRecord::Base
  def title
    read_attribute(:title).upcase
  end
end

いかがでしょうか。

ここまでの情報で、あとはご自身で理解を深めて疑問をひとつひとつ解消できるのではないでしょうか。

編集 履歴 (0)
  • 詳しく書いていただいてありがとうございます。自分は titleが変数だとおもっていたのですが、これは定義されたメソッドだったんですね。色々わかりました、ありがとうございます。 -

純粋にrubyレベルの話ですが、

title = "test title"

はローカル変数へのアサイン、

self.title = "test title"

ならインスタンスのtitle=メソッドが呼ばれます。(つまり@titleへのアサイン)

attr_accessorについて勉強されるのが良いとおもいます。

class C
  attr_accessor :title

  def whoami
    self.title = 'I am C'
    title
  end
end

o = C.new
o.whoami
 => "I am C"

書くほうのメソッドtitle=にはselfがいるのに読むほうのメソッドtitleにはselfがいらない、というのは慣れるまでは混乱するかもしれません。

編集 履歴 (0)

kennさん、お返事ありがとうございます。

  • 少し混乱しましたが、attr_accessorの処理を自分なりにここにまとめますので、もし理解が間違っていたらご指摘いただけると幸いです。

attr_accessor :titleと宣言した場合は以下の様な処理がされる。

(1)@titleというインスタンス変数を作成する。
(2)すべてのインスタンスメソッドは、呼び出された際にtitleというローカル変数が@titleから値をコピーされて作られる。

(2)だと考えた理由はtitle='I am B'という書き方ができ、さらにp titleで'I am B'、p self.titleで'I am C'という結果が帰ってきたからです。

  • また一点まだわからない点があります。それはself.title = "test title"@title = "test title"はどう違うかという点です。

なぜこのような疑問を持ったか説明させていただきます。

私はこの2つは同じ意味だと考えていたのですが、Blogクラスの挙動をみるとどうも違うようです。
ご指摘をいただいた後、self.content = "test content"とかくと、ちゃんとblog.content"test content"を返すようになりました。
ただそれだと@title = "test title"では値が出力されなかったので、上記の2つの処理は別の変数を書き換えている事になると思います。

  • ここで現在の質問を整理させていただきます。
  1. Blogクラス内でtitle等の値を更新するには、どうすればいいか?=>解決しました。self.titleです。
  2. Blogクラス内のtitleとは何を呼び出しているのか?=>解決しました。ローカル変数です。
  3. Blogクラス内の@titleというインスタンス変数は何か?=>解決してません。なぜなら@titleの値を変更しても、blog.titleの値が変更されていないからです。
  4. self.title = "test title"@title = "test title"はどう違うか=>新規の質問です。
  5. attr_accessorの処理あってますか?=>新規の質問です。

色々長々と書かせていただきましたが、ご回答いただけると幸いです。

編集 履歴 (0)
  • 別回答として追記しました。 -
ウォッチ

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