Объединение 3 моделей в Rails для отображения в виде, повторяющемся над одной моделью - PullRequest
1 голос
/ 25 июля 2011

У меня довольно стандартные отношения has_many: x,: through =>: y с пользователем, проблема и проблема complete_problem, которая действует как связь между ними:

/ модель / user.rb

class User < ActiveRecord::Base
  has_many :completed_problems
  has_many :problems, :through => :completed_problems    
end

/ модель / problem.rb

class Problem < ActiveRecord::Base
  belongs_to :wall
  has_many :completed_problems
  has_many :users, :through => :completed_problems    
end

/ модель / completed_problem.rb

class CompletedProblem < ActiveRecord::Base
  belongs_to :user
  belongs_to :problem

  validates_presence_of :user
  validates_presence_of :problem
end

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

  • problem.id
  • problem.name
  • время с момента, когда текущий пользователь выполнил задачу в последний раз
    • если пользователь не вошел в систему, текст
    • если пользователь не выполнил эту проблему, другой текст

(очень некрасиво) первый проход в представлении выглядит следующим образом:

/ вид / стены / show.html.erb

<% @wall.problems.each do |problem| %>
    <a id=<%= "problem_#{problem.id}" %>>
      <h3><%= problem.name %></h3>
      <p><%= "#{time_ago_in_words(problem.last_complete_by_user(current_user))} ago" if current_user && problem.last_complete_by_user(current_user) %></p>
    </a>
  </li>
<% end %>

С тех пор я перезаписал его, но problem.last_complete_by_user (замеченный в приведенном выше фрагменте) был попыткой использовать проблемный объект для поиска всех связанных завершенных проблем, с пользователем в качестве аргумента, для определения 'updated_at 'значение для последней обновленной завершенной_проблемы для этой конкретной проблемы и пользователя.

Конечно, это не идеально, потому что это будет отдельный запрос для каждого элемента в списке - я предполагаю, что предпочтительным решением будет метод в контроллере стены или модели, который объединяет все 3 таблицы и возвращает новый массив для представления, чтобы перебрать. К сожалению, я слишком долго прыгал между: join,: include и: find_by_sql без решения.

Может ли кто-нибудь хотя бы привести меня в правильном направлении для того, чтобы заставить этот вид работать правильно?

Ответы [ 2 ]

1 голос
/ 25 июля 2011

Вот так я бы решил проблему. Возможно, это не самое эффективное решение, но оно чистое и его легко реорганизовать, когда придет время. Я не пробовал код, но он, вероятно, не слишком далеко. Если вы пойдете по этому пути и столкнетесь с проблемами производительности, я бы посмотрел на фрагментное кэширование, прежде чем добавлять кучу сумасшедших SQL.

Модели:

class User < ActiveRecord::Base
  has_many :completed_problems
  has_many :problems, :through => :completed_problems

  # Finds the last completed problem
  def last_completed_problem(problem)
    problems.order('created_at DESC').where(:problems => {:id => problem}).limit(1).first
  end
end

# No Changes
class Problem < ActiveRecord::Base
  belongs_to :wall
  has_many :completed_problems
  has_many :users, :through => :completed_problems
end

# No changes
class CompletedProblem < ActiveRecord::Base
  belongs_to :user
  belongs_to :problem

  validates_presence_of :user
  validates_presence_of :problem
end

приложение / контроллеры / walls_controller.rb:

class WallsController < Application::Controller
  def show
    @wall = Wall.find(params[:id]).includes(:problems)
  end
end

приложение / хелперы / wall_helper.rb:

module WallHelper
  def show_last_completed_problem_for_user(user, problem)
    return "You are not logged in" if current_user.nil?

    completed = user.last_completed_problem(problem)

    return "You have not completed this problem" if completed.nil?

    time_ago_in_words(completed.created_at)
  end
end

приложение / просмотров / стены / show.html.erb:

<%= render :partial => 'problem', :collection => @wall.problems %>

приложение / просмотров / Стенки / _problem.html.erb:

<li>
  <a id=<%= "problem_#{problem.id}" %>>
    <h3><%= problem.name %></h3>
    <p><%= show_last_completed_problem_for_user(current_user, problem) %></p>
  </a>
</li>
0 голосов
/ 25 июля 2011

Итак, вы хотите ПОСЛЕДНЮЮ завершенную задачу, которой может быть много ... или ее нет.

Мы можем сделать это, присоединившись слева к complete_problems (если его там нет), затем сгруппировавшись по пользователю / проблеме. Группировка означает, что вы получаете только одну запись на пользователя / проект. Последний трюк - вам нужно указать ваши столбцы в 'select', чтобы мы получили обычные поля проекта, а другой - для last_attempted.

@problems = Problem.join("LEFT OUTER JOIN problems ON completed_problems.problem_id = problems.id").
                    group(:user_id, :problem_id).
                    select("projects.*", "MAX(completed_problems.updated_at) as last_attempted").
                    where(:wall_id => params[:wall_id])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...