Eager Загрузка с "имеет много сквозных" - мне нужен Арель? - PullRequest
4 голосов
/ 16 сентября 2011

У меня есть три таблицы: users, members, projects.Середина - это таблица соединения, выражающая сквозную связь между двумя другими таблицами;и у него есть некоторые атрибуты интереса, включая join_code и activated.

Более подробно:

class User < ActiveRecord::Base
  has_many :members
  has_many :projects, :through => :members
end

class Member < ActiveRecord::Base
  belongs_to :user
  belongs_to :project
  # has a column called join_code
  # has a column called activated
  # Note that this class can be thought of as "membership"
end

class Project < ActiveRecord::Base
  has_many :members
  has_many :users, :through => :members
end

Цель : для конкретного пользователя я хочузапрос, который получит все проекты и нетерпеливо загрузит только записи участника, которые связывают эти проекты с пользователем.

Пока у меня есть этот метод в user.rb, который выполняет запрос:

def live_projects
  self.projects.order("projects.name").includes(:members).where(:members => {:join_code => nil, :activated => true})
end

Но этого недостаточно.Я хотел бы тогда иметь возможность сделать это в коде представления:

<% current_user.live_projects.each do |project| %>
  <li project_id="<%= project.id %>">
    <% member = project.member %>
      (Do something with that member record here)
      <%= project.name %>
    <% end %>
  </li>
<% end %>

Здесь, как правило, у меня было бы project.members, но в моем контексте меня интересует только это одна запись участника, которая ссылается на пользователя.

Вот то, что я думаю, необработанный SQL должен выглядеть как

select projects.*, members.* 
from projects inner join members on projects.id = members.project_id
where members.user_id = X and members.join_code is null and members.activated = 't';

Как это сделать в Arel (или ActiveRecord)

Ответы [ 3 ]

2 голосов
/ 17 сентября 2011

У меня может быть что-то вроде ответа, а именно, что код ActiveRecord, который я написал, кажется довольно разумным.Опять, вот этот запрос:

def live_projects
  self.projects.order("projects.name").includes(:members).where(:members => {:join_code => nil, :activated => true})
end

При запуске через пользовательский интерфейс с образцами данных он генерирует этот вывод с сервера Rails:

Project Load (0.6ms)  SELECT "projects".* FROM "projects" INNER JOIN "members" ON "projects".id = "members".project_id WHERE "members"."join_code" IS NULL AND "members"."activated" = 't' AND (("members".user_id = 3)) ORDER BY projects.name
Member Load (2.0ms)  SELECT "members".* FROM "members" WHERE ("members".project_id IN (50,3,6,37,5,1))

Затем в коде представления я могу сделатьthis:

<% current_user.live_projects.each do |project| %>
  <li project_id="<%= project.id %>" class="<%= 'active' if project == @project %>">
    <% member = project.members.detect { |member| member.user_id == current_user.id } %>
    (Do something with that member record here)
  </li>
<% end %>

Это выражение для получения записи члена довольно уродливо в представлении, но select - это метод Array, а не запрос, и никакие дополнительные попадания в БД, кроме двух показанныхвыше появляются в выводе с сервера Rails.Таким образом, я думаю, что моя проблема n + 1 решена.

1 голос
/ 20 сентября 2011

Похоже, вы ожидаете, что будет не более одного активного участника, связывающего каждого пользователя с проектом.В этом случае должно работать следующее:

В member.rb:

scope :live, where(:join_code => nil, :activated => true)

В user.rb:

def live_projects_with_members
  members.live.includes(:project).group_by(&:project)
end

На ваш взгляд:

<% current_user.live_projects_with_members.each do |project, members| %>
  <% member = members.first %>
  <li project_id="<%= project.id %>" class="<%= 'active' if project == @project %>">
      (Do something with that member record here)
  </li>
<% end %>

Если вы хотите добавить дополнительное объединение для вашей статистики использования, вы можете сделать это:

def live_projects_with_members
  members.live.includes(:project, :stats).group_by(&:project)
end
0 голосов
/ 19 сентября 2011

Добавьте ассоциацию с именем live_members в класс Project.

class Project < ActiveRecord::Base
  has_many :live_members, :class_name => "Member", 
                :conditions => {:join_code => nil, :activated => true}
  has_many :members
  has_many :users, :through => :members
end

Добавьте ассоциацию с именем live_projects в класс User.

class User < ActiveRecord::Base
  has_many :members
  has_many :projects, :through => :members
  has_many :live_projects, :through => :members, :source => :project, 
             :include => :live_member, :order => "projects.name"
end

СейчасВы можете:

user.live_projects
...