TodoList: find() で :include を使用2006年12月28日 09時06分51秒

Associations の中の「include オプションによる実行」によると、:include を使うと join を自動的に構築してくれる様だ。実験してみよう。

app/controlllers/todo_controller.rb の中の list 関数を変更する。参考の為に以前に動作を確認したコマンドをコメントとして残して実験する。


  def list 
    @todo_pages, @todos = paginate(:todos, 
      #:select => 
      #  Todo.columns.map{ |column| "todos." + column.name.to_s() }.join(", "), 
      #:joins => "LEFT JOIN todo_organization_permissions" + 
      #  " ON todo_organization_permissions.todo_id = todos.id WHERE " + 
      #  "published = 1 AND organization_id = " + 
      #  session[:user][:organization_id].to_s(), 
      :include => :todo_organization_permissions, 
      :per_page => 10) 
  end

まず、:select:joain を取り除き、:include に変えた。app/models/todo.rb の has_many に綴りの間違いがあったので、それを修正するまでは動かなかった。

class Todo < ActiveRecord::Base
  has_many :todo_organization_permissions
  has_many :organizations, :through=>:todo_organization_permissions
end

なお、今のままでは、where 句の中の条件が合わなくなるので、今まで通りの動作は期待できない。http://localhost:3000/todo/list にアクセスすると案の定、「Hide from uyota」が見えてしまった。大切なのはここからである。log/development.log を覗くと

Todo Load Including Associations (0.001375) SELECT todos.`id` AS t0_r0, todos.`description` AS t0_r1, todos.`done` AS t0_r2, todo_organization_permissions.`id` AS t1_r0, todo_organization_permissions.`todo_id` AS t1_r1, todo_organization_permissions.`organization_id` AS t1_r2, todo_organization_permissions.`published` AS t1_r3 FROM todos LEFT OUTER JOIN todo_organization_permissions ON todo_organization_permissions.todo_id = todos.id WHERE todos.id IN ('1', '2', '3')
とあった。

動きそうな気はしないが、とりあえず :condition を試してみる。


  def list
    @todo_pages, @todos = paginate(:todos,
      :include => :todo_organization_permissions,
      :conditions => ["published = 1 AND organization_id = " +
        session[:user][:organization_id].to_s()],
      :per_page => 10)
  end

結果は、エラーだった。

Mysql::Error: Unknown column 'published' in 'where clause': SELECT id FROM todos WHERE (published = 1 AND organization_id = 1) LIMIT 0, 10
log/development.log を見ると、
SELECT COUNT(DISTINCT todos.id) FROM todos LEFT OUTER JOIN todo_organization_permissions ON todo_organization _permissions.todo_id = todos.id WHERE (published = 1 AND organization_id = 1)
が実行されていた。published と organization_id は todos テーブルではなく、todo_organization_permissions テーブルの項目だ。動くはずがない。

しかし、前回発行された SQL と見比べると join 句の中の where 句が入れ替わっているの確認出来たのは大きな収穫だ。さっきは IN 句が生成されていたので、COUNT などの SQL が発行されていたのは、暗に気が付いていたが、実際に COUNT が発行されているのも明らかになった。

さて、三度目の正直である。この様子だと、:join を使って、where 句を指定すれば良さそうだ。


  def list
    @todo_pages, @todos = paginate(:todos,
      :include => :todo_organization_permissions,
        :joins => ["WHERE published = 1 AND organization_id = " +
          session[:user][:organization_id].to_s()],
      :per_page => 10)
  end

さて、今回は期待通りの動作をするようになった。以下の SQL 文が発行された。
SELECT todos.`id` AS t0_r0, todos.`description` AS t0_r1, todos.`done` AS t0_r2, todo_o rganization_permissions.`id` AS t1_r0, todo_organization_permissions.`todo_id` A S t1_r1, todo_organization_permissions.`organization_id` AS t1_r2, todo_organiza tion_permissions.`published` AS t1_r3 FROM todos LEFT OUTER JOIN todo_organizati on_permissions ON todo_organization_permissions.todo_id = todos.id WHERE publish ed = 1 AND organization_id = 1 AND todos.id IN ('1', '2', '3')

:include:join を組み合わせることにより、ある程度の JOIN 句を生成することが出来そうだ。これにより、六行の:joinが三行の :include:join になった。しかし、この文を始めて見たら、少々混乱を招きそうな気もする。

また、この様な方法での動作を見ながら変更していくと、仕様でなかった場合に後々困るのであまり気が進まない。。残念ながら、Associations API にも find API にも、簡単すぎる例がいくつかあるだけで、細かい動作が記述されていない。そうは言ったものの、全ての動作を逐一解説されても読む気が失せるのも目に見えている。悩ましいところだ。

前回次回