Rails: сбор данных из трех связанных моделей в одном представлении

Я нуб, пытающийся создать свое первое приложение для рельсов. Это «игра со ставками», в которой пользователи пытаются предсказать результаты футбола и получить очки за правильный результат или правильную «тенденцию» (выигрыш, ничья, проигрыш).

Создание и обновление ставок работает нормально, а также создание игр и вставка результатов в качестве администратора. Но у меня проблемы с отображением всех ставок от всех пользователей для каждой игры на своего рода индексной странице, которая должна выглядеть примерно так:

                       |  User1 |  User2 |  User3    ...
Game            Result |  Bet   |  Bet   |  Bet  
Team1 : Team2   1:0    |  1:1   |  3:2   |  1:0 
Team3 : Team4   1:2    |  1:2   |  -:-   |  3:0 
Team5 : Team6   -:-    |  1:2   |  -:-   |  3:0 
...

Структура моей модели выглядит следующим образом:

class User < ActiveRecord::Base
has_many :bets

class Game < ActiveRecord::Base
has_many :bets

class Bet < ActiveRecord::Base
belongs_to :user, :class_name => 'User', :foreign_key => 'user_id'
belongs_to :game, :class_name => 'Game', :foreign_key => 'game_id'

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

Я перепробовал тысячу способов собрать данные для представления индекса и получил проблемы с несколькими ошибками (нулевой объект, отсутствие метода и т. д.). Текущий (не работающий) подход получает все в контроллере:

def index
    @users = User.all
    @games = Game.all
    @bets = Bet.all
end

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

    <% @games.each do |game| %>
    <tr>
        <td><%= game.home_team %> - <%= game.away_team %></td>
        <td>
            <% if game.away_score.nil? %>
                -:-
            <% else %>
                <%= game.home_score %> : <%= game.away_score %>
            <% end -%>
        </td>
        <% @users.each do |user| %>
            <% bet = Bet.where( :user_id => user.id, :game_id => game.id) %>
            <% if bet.exists? %>
                <td><%= bet.home_bet %> : <%= bet.away_bet %></td>              
            <% else %>
                <td>-:-</td>
            <% end -%>
        <% end -%>
    </tr>
<% end -%>

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

Итак, первый вопрос: как лучше всего получить необходимые данные из разных таблиц? Соответствующий второй вопрос: лучше всего построить данные в контроллере и передать их в представление или собрать все вместе в представлении?

Я использую рельсы 3.1.1 с sqlite3. Я надеюсь, что кто-то может помочь этому глупому новичку...


person marc78    schedule 08.02.2012    source источник
comment
Не могли бы вы опубликовать некоторые из вещей, которые вы пытались сделать, чтобы собрать данные в контроллере? Если ваша коллекция пользователей не будет очень маленькой, горизонтальный отслеживание пользователей не будет очень хорошим масштабированием. Будут ли все ваши пользователи делать ставки во всех играх? Если да, то вы можете просто создать таблицу, присвоить каждой строке игру, перебрать свои ставки и поместить их в ячейки...   -  person Marc Talbot    schedule 08.02.2012
comment
только что отредактировал: нет, не у всех пользователей будет ставка на каждую игру, что, похоже, и вызывает основные проблемы. Поскольку позже пользователи будут организованы в сообщества (примерно по 10 человек), я надеюсь, что горизонтальное расположение подойдет, но на данный момент это не главная проблема. Я опубликую несколько примеров позже, так как сейчас передо мной не весь код. Спасибо за ответы!   -  person marc78    schedule 08.02.2012
comment
Просто чтобы вы знали, параметры class_name и foreign_key в вашем случае не нужны. Они автоматически выводятся из имени ассоциации (belongs_to :game) и необходимы только в том случае, если имя ассоциации отличается от имени модели или имени ключа.   -  person Andrew Marshall    schedule 08.02.2012
comment
@Andrew: Я знаю, это было результатом того, что я много читал об отношениях рельсов и пробовал разные вещи. Но спасибо, что указали на это!   -  person marc78    schedule 08.02.2012
comment
У меня сейчас нет времени, чтобы опубликовать полный ответ, но я бы предложил отношения has_many :through, как в Game has_many Users :through => :bets. Затем вы можете получить все через Game.all (и очистить свои представления, заменив операторы if вызовом модели или помощника, который возвращает правильную вещь)   -  person Mark Thomas    schedule 08.02.2012


Ответы (2)


Я бы, наверное, сделал что-то вроде этого:

контроллер:

@users = User.all
@game_bets = Game.all.map { |game| [game, game.bets.index_by(&:user)] }

Посмотреть:

<% @game_bets.each do |game, bets| %>
  ...
  <% @users.each do |user| %>
    <% if bets.has_key?(user) %>
      <td><%= bets[user].home_bet %></td>
    <% else %>
      <td>-:-</td>
...
person Victor Moroz    schedule 08.02.2012
comment
Это, вероятно, будет очень медленным, если есть много пользователей. Повторение всех пользователей не должно быть необходимым. - person Mark Thomas; 09.02.2012
comment
@MarkThomas OP предназначался для отображения всех пользователей/игр на одном экране, поэтому скорость, очевидно, не была проблемой. - person Victor Moroz; 09.02.2012
comment
@VictorMoroz Не совсем так. Он хотел отобразить всех пользователей, связанных с каждой игрой. Если есть пользователи, не связанные с играми в какой-то конкретный момент, и их количество может быть большим, то это проблема. - person Mark Thomas; 10.02.2012

Так много вопросов здесь ... давайте начнем

  1. нет необходимости перебирать ВСЕХ пользователей для КАЖДОЙ игры. Вы можете выполнить итерацию по game.bets и для каждой «ставки» получить пользователя на bet.user в соответствии с отношениями, которые вы построили.

  2. Чтобы ответить на ваш 1-й вопрос: поскольку вы пытаетесь создать вид «отчета» с намерением показать «все» игры, ваш подход к @games = Game.all правильный. Вам не нужны 2 другие коллекции .all, поскольку вы можете вывести данные из отношений

  3. 2-й вопрос: в Rails считается хорошим подходом иметь «толстые модели, тонкие контроллеры», что означает, что ваш код обработки/логики данных должен быть в модели, а контроллер должен иметь только код, используемый соответствующими представлениями.

  4. О вашем представлении: в вашем случае ваше представление не так уж плохо, так как у вас мало заполненные таблицы, и вы должны использовать ifs для случаев, когда оценка недоступна и т. д.

person Zepplock    schedule 08.02.2012
comment
Спасибо за ваши предложения, они очень помогли! Может быть, мой взгляд не такой беспорядочный... :-) - person marc78; 08.02.2012