Проблемы внешнего ключа в Rails - PullRequest
3 голосов
/ 26 марта 2009

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

cards     cards_games     games      
-----     -----------     -----
id        id              id
c_type    card_id         ...
value     game_id         other_stuff

И Rails ActiveRecord card.rb и game.rb в настоящее время выглядят так

#card.rb
class Card < ActiveRecord::Base
  has_and_belongs_to_many :player
  has_and_belongs_to_many :game
  has_and_belongs_to_many :cardsInPlay, :class_name => "Rule"
end

#game.rb
class Game < ActiveRecord::Base
  has_and_belongs_to_many :cards
  has_many :players
  has_one :rules, :class_name => Rule
end

Когда я пытаюсь запустить игру с несколькими играми (более 1), я получаю сообщение об ошибке

ActiveRecord::StatementInvalid in GameController#start_game
# example
Mysql::Error: Duplicate entry '31' for key 1: INSERT INTO `cards_games` (`card_id`, `id`, `game_id`) VALUES (31, 31, 7)

Каждый раз, когда действие заканчивается неудачей, cardid == id. Это, я полагаю, связано с тем, как Rails вставляет данные в базу данных. Так как объекта cardsgames нет, я думаю, что он просто вставляет card_id в id и вставляет его в базу данных. Это прекрасно работает, пока у вас нет двух игр с одной картой, что нарушает ограничение первичного ключа для карточных игр. Будучи богатым с базами данных, мое первое решение этой проблемы состояло в том, чтобы попытаться заставить rails следовать «реальному» определению этих отношений, отбросив id и сделав cardid и gameid первичным ключом. Это не сработало, потому что миграция не могла справиться с наличием двух первичных ключей (несмотря на то, что Rails API говорил, что это нормально ... странно). Другое решение для этого состоит в том, чтобы опустить столбец 'id' в операторе INSERT INTO и позволить базе данных обрабатывать автоинкремент. К сожалению, я тоже не знаю, как это сделать.

Так, есть ли другой обходной путь для этого? Есть какой-то изящный трюк с Rails, которого я просто не знаю? Или такая структура не возможна в Rails? Это действительно расстраивает, потому что я знаю , что не так, и я знаю несколько способов исправить это, но из-за ограничений инфраструктуры Rail я просто не могу это сделать.

Ответы [ 5 ]

10 голосов
/ 09 июня 2009

has_and_belongs_to_many подразумевает таблицу соединения, которая не должна иметь столбец первичного ключа id. Измените свою миграцию на

create_table :cards_games, :id => false do ...

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

add_index :cards_games, [ :card_id, :game_id ], :unique => true

Кроме того, ваше именование отличается от соглашения Rails и сделает ваш код немного труднее для чтения.

has_and_belongs_to_many определяет отношение 1: M при просмотре экземпляра класса. Так что в Card вы должны использовать:

has_and_belongs_to_many :players
has_and_belongs_to_many :games

Обратите внимание на множественное число «игроки» и «игры». Аналогично в Game:

has_one :rule

Это также позволит вам сбросить ненужные :class_name => Rule.

4 голосов
/ 05 июня 2009

Чтобы удалить столбец идентификатора, просто не создавайте его для начала.

  create_table :cards_rules, :id => false do ...
1 голос
/ 05 июня 2009

См. Составные первичные ключи Dr. Nics

http://compositekeys.rubyforge.org/

0 голосов
/ 26 марта 2009

Возможно, вы захотите проверить этот foreign_key_migrations плагин

0 голосов
/ 26 марта 2009

Я нашел решение после взлома. Я обнаружил, что вы можете использовать функцию «выполнить» внутри миграции. Это бесконечно полезно и позволило мне составить не элегантное решение этой проблемы. Если у кого-нибудь есть более элегантное, более похожее на Rails решение, пожалуйста, дайте мне знать. Вот решение в виде миграции:

class Make < ActiveRecord::Migration
  def self.up
    drop_table :cards_games
    create_table :cards_games do |t|
      t.column :card_id, :integer, :null => false
      t.column :game_id, :integer, :null => false
    end
    execute "ALTER TABLE cards_games DROP COLUMN id"
    execute "ALTER TABLE cards_games ADD PRIMARY KEY (card_id, game_id)"

    drop_table :cards_players
    create_table :cards_players do |t|
      t.column :card_id, :integer, :null => false
      t.column :player_id, :integer, :null => false
    end
    execute "ALTER TABLE cards_players DROP COLUMN id"
    execute "ALTER TABLE cards_players ADD PRIMARY KEY (card_id, player_id)"

    drop_table :cards_rules
    create_table :cards_rules do |t|
      t.column :card_id, :integer, :null => false
      t.column :rule_id, :integer, :null => false
    end
    execute "ALTER TABLE cards_rules DROP COLUMN id"
    execute "ALTER TABLE cards_rules ADD PRIMARY KEY (card_id, rule_id)"
  end

  def self.down
    drop_table :cards_games
    create_table :cards_games do |t|
      t.column :card_id, :integer
      t.column :game_id, :integer
    end

    drop_table :cards_players
    create_table :cards_players do |t|
      t.column :card_id, :integer
      t.column :player_id, :integer
    end

    drop_table :cards_rules
    create_table :cards_rules do |t|
      t.column :card_id, :integer
      t.column :rule_id, :integer
    end
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...