Вопрос об ассоциации Activerecord: получение has_many: через работу - PullRequest
4 голосов
/ 14 октября 2008

Я создаю приложение в Ruby on Rails и включаю 3 мои модели (и их сценарии миграции), чтобы показать, что я пытаюсь сделать, а что нет. Вот краткое изложение: в моем приложении есть пользователи, принадлежащие к командам, и у каждой команды может быть несколько тренеров. Я хочу иметь возможность получить список тренеров, которые применимы к пользователю.

Например, пользователь A может принадлежать к командам T1 и T2. Команды T1 и T2 могут иметь по четыре разных тренера в каждой и один общий тренер. Я хотел бы получить список тренеров, просто сказав:

u = User.find(1)
coaches = u.coaches

Вот мои сценарии миграции и ассоциации в моих моделях. Я делаю что-то неправильно в моем дизайне? Правильны ли мои ассоциации?

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.column :login, :string, :default => nil
      t.column :firstname, :string, :default => nil
      t.column :lastname, :string, :default => nil
      t.column :password, :string, :default => nil
      t.column :security_token, :string, :default => nil
      t.column :token_expires, :datetime, :default => nil
      t.column :legacy_password, :string, :default => nil
    end
  end

  def self.down
    drop_table :users
  end
end

class CreateTeams < ActiveRecord::Migration
  def self.up
    create_table :teams do |t|
      t.column :name, :string
    end
  end

  def self.down
    drop_table :teams
  end
end

class TeamsUsers < ActiveRecord::Migration
  def self.up
    create_table :teams_users, :id => false do |t|
      t.column :team_id, :integer
      t.column :user_id, :integer
      t.column :joined_date, :datetime
    end
  end

  def self.down
    drop_table :teams_users
  end
end

Вот модели (не весь файл):

class User < ActiveRecord::Base

  has_and_belongs_to_many :teams
  has_many :coaches, :through => :teams

class Team < ActiveRecord::Base
  has_many :coaches
  has_and_belongs_to_many :users

class Coach < ActiveRecord::Base
  belongs_to :teams
end

Вот что происходит, когда я пытаюсь вытащить тренеров:

u = User.find(1)
=> #<User id: 1, firstname: "Dan", lastname: "Wolchonok">
>> u.coaches
ActiveRecord::StatementInvalid: Mysql::Error: #42S22Unknown column 'teams.user_id' in 'where clause': SELECT `coaches`.* FROM `coaches`    INNER JOIN teams ON coaches.team_id = teams.id    WHERE ((`teams`.user_id = 1)) 

Вот ошибка в sql:

Mysql :: Ошибка: # 42S22Неизвестный столбец 'teams.user_id' в 'предложении where': SELECT coaches. * FROM coaches INNER JOIN команды ON user_id = 1))

Я что-то упустил в своем: сквозном предложении? Мой дизайн полностью выключен? Может ли кто-нибудь указать мне правильное направление?

Ответы [ 5 ]

4 голосов
/ 14 октября 2008

Вы не можете сделать has_many: через два раза подряд. Он скажет вам, что это недопустимая ассоциация. Если вы не хотите добавлять finder_sql, как описано выше, вы можете добавить метод, который имитирует то, что вы пытаетесь сделать.

  def coaches
    self.teams.collect do |team|
      team.coaches
    end.flatten.uniq
  end
2 голосов
/ 14 октября 2008

Это скорее отношения «многие ко многим к еще большему». Я бы просто написал немного sql:

has_many :coaches, :finder_sql => 'SELECT * from coaches, teams_users WHERE 
               coaches.team_id=teams_users.team_id 
               AND teams_users.user_id=#{id}'
1 голос
/ 14 октября 2008

Вы можете удалить строку "has_many: coaches,: through =>: команды" в пользователях, а затем вручную написать метод тренеров в вашей модели User следующим образом:

def coaches
  ret = []
  teams.each do |t|
    t.coaches.each do |c|
      ret << c
    end
  end
  ret.uniq
end
1 голос
/ 14 октября 2008

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

Вместо этого вам придется использовать опцию: finder_sql и самостоятельно написать полное предложение объединения. Не самая красивая вещь в мире, но так происходит с ActiveRecord, когда вы пытаетесь сделать что-то необычное.

0 голосов
/ 14 октября 2008

Хотя я люблю писать SQL, я не думаю, что это идеальное решение в этом случае. Вот что я сделал в модели User:

  def coaches
    self.teams.collect do |team|
      team.coaches
    end.flatten.uniq
  end

  def canCoach(coachee)
    u = User.find(coachee)

    coaches = u.coaches
    c = []
    coaches.collect do |coach|
      c.push(coach.user_id)
    end

    return c.include?(self.id)
  end

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...