解説しよう

ごめんなさい。最初、Rails2.0.2環境で検証してしまってました。
1.2系だと一手間必要になります。


http://d.hatena.ne.jp/actrace/20080314/1205473456

self.schedules << scheduleがなぁ、いまいちわからない。

との事なので、執筆した手前、解説を試みてみます。
といっても、ruby-debugでここを追っかけてみるだけです。
実際に動かしてみると感じをつかみやすいと思います。
確かにここは勉強会の時もわかりづらいという意見が多かったです。

要gem install ruby-debugです。
データの前提条件です。Time.now.beginning_of_monthの条件に当てはまるscheduleレコードが3/15,17,18,22と4件。
nameが'hoge',emailが'fuga'というユーザを作成します。

まずはdebugしたい位置にdebuggerと記述しましょう。

class User < ActiveRecord::Base
  has_many :user_schedules, :dependent =>:destroy
  has_many :schedules, :through =>:user_schedules
  validates_presence_of :name,:email
  after_create :create_user_schedule
  def create_user_schedule
    Schedule.find(:all,:conditions =>["schedule_date >= ?",Time.now.beginning_of_month]).each do |schedule|
    self.schedules << schedule
    debugger #<=ここ
    end
  end
end

続いてサーバーをデバッグモードで立ち上げます(2.0.2)。

% script/server -u

1.2系の場合、config/environment.rbの最後あたりにrequire 'ruby-debug'と追加して、普通にサーバーを起動します。

script/server

で、該当行のプログラムが走る所までブラウザを操作します。この場合はユーザー作成フォームに入力してcreateを押す所までです。すると、createのボタンを押したところで止まります。
この状態で、サーバーのコンソールをみてみましょう。

self.schedules << schedule
(rdb:5) 

こんな感じの表示になってるはずです。今いる行を確認してみましょう。l=と入力します。

(rdb:5) l=
[13, 22] in /Users/yuichi_katahira/svn-work/adjuster/app/models/user.rb
   13    has_many :schedules, :through =>:user_schedules
   14    validates_presence_of :name,:email
   15    after_create :create_user_schedule
   16    def create_user_schedule
   17      Schedule.find(:all,:conditions =>["schedule_date >= ?",Time.now.beginning_of_month]).each do |schedule|
=> 18      self.schedules << schedule
   19      debugger
   20      end
   21    end
   22  end

18行目にいますね。さて、この時点のselfっていうのはチュートリアルインスタンス変数だよと説明しています。確認してみましょう。pp selfと入力します。

(rdb:5) pp self
#<User id: 23, name: "hoge", email: "fuga">

user_controller.rbのcreateメソッドでcreate,saveされた@userがこの場合のselfのようです。
ではselfにeachで渡しているScheduleのfind(:all)を確認してみましょう。irbと打って、irbの中でSchedule.find(...)と入力し、確認したらexitします。

(rdb:5) irb
irb(#<User:0x22b2814>):001:0> Schedule.find(:all,:conditions =>["schedule_date >='2008-03-01'"])
=> [#<Schedule id: 1, event_id: 1, schedule_date: "2008-03-15 00:00:00">, #<Schedule id: 2, event_id: 1, schedule_date: "2008-03-22 00:00:00">, #<Schedule id: 3, event_id: 16, schedule_date: "2008-03-17 00:00:00">, #<Schedule id: 4, event_id: 16, schedule_date: "2008-03-18 00:00:00">]
irb(#<User:0x22b2814>):002:0>exit

3/15,3/22,3/17,3/18の4レコードですね。
では今eachで渡されているローカル変数scheduleを確認してみましょう。

(rdb:5) pp schedule
#<Schedule id: 2, event_id: 1, schedule_date: "2008-03-22 00:00:00">

debuggerを19行目に仕込んでいたので、18行目が処理されすでに2件目の3/22のようです。

(rdb:5) pp self.schedules
[#<Schedule id: 1, event_id: 1, schedule_date: "2008-03-15 00:00:00">]

1件目はschedules<<メソッドでselfに関連づけられていますね。
ステップ実行しましょう。

(rdb:5) n

Processing UserController#create (for 127.0.0.1 at 2008-03-17 23:06:12) [POST]
  Session ID: hogehogehogehoge
  Parameters: {"user"=>{"name"=>"hoge", "email"=>"fuga"}, "commit"=>"Create", "authenticity_token"=>"nantara_hogehoge_fugafugafugafugafuga", "action"=>"create", "controller"=>"user"}
  User Columns (0.001895)   SHOW FIELDS FROM `users`
  SQL (0.000176)   BEGIN
  User Create (0.000376)   INSERT INTO `users` (`name`, `email`) VALUES('hoge', 'fuga')
  SQL (0.000490)   SHOW TABLES
  Schedule Load (0.000273)   SELECT * FROM `schedules` WHERE (schedule_date >= '2008-03-01 00:00:00') 
  Schedule Columns (0.002189)   SHOW FIELDS FROM `schedules`
  UserSchedule Columns (0.002201)   SHOW FIELDS FROM `user_schedules`
  UserSchedule Create (0.000343)   INSERT INTO `user_schedules` (`attend`, `schedule_id`, `user_id`) VALUES ('-', 1, 23)
  Schedule Load (0.000498)   SELECT schedules.* FROM schedules INNER JOIN user_schedules ON schedules.id = user_schedules.schedule_id WHERE ((user_schedules.user_id = 23)) 
  Schedule Load (0.011225)   SELECT * FROM `schedules` WHERE (schedule_date >='2008-03-01') 
  CACHE (0.000000)   SELECT * FROM `schedules` WHERE (schedule_date >='2008-03-01') 

SQLが発行されましたね。
a.UserをINSERT
b.Scheduleを条件に従ってSELECT
c.user_scheduleをINSERT
ちなみに余計なSQLは、変数を参照したりした為に発行されたものです。

ブレイクポイントまで飛んでみましょう。変数を確認。

(rdb:5) c
(rdb:5) pp self
#<User id: 23, name: "hoge", email: "fuga">
(rdb:5) pp self.schedules
[#<Schedule id: 1, event_id: 1, schedule_date: "2008-03-15 00:00:00">,
 #<Schedule id: 2, event_id: 1, schedule_date: "2008-03-22 00:00:00">]
(rdb:5) pp schedule
#<Schedule id: 3, event_id: 16, schedule_date: "2008-03-17 00:00:00">

後は終わるまで繰り返しです。どうでしょう。なんとなく感じはつかめたでしょうか?
あ、schedules<<を説明していませんでした。
これはRailsAPIActiveRecord::Associations::ClassMethodsのhas_manyを見ると良いでしょう。has_manyを定義すると、自動で定義されるメソッド中のひとつです。
関連名(この場合schedules)<< までで一つのメソッドです。関連オブジェクトを自分の所有物に追加します。(自動的にDBへ保存されます)。
なお、Associationsについてはこちらが詳しいです。
http://wota.jp/ac/?date=20060120