3

我花了一段时间才找到这个错误,但我终于找到了原因。我正在使用 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 如何将数据插入数据库有关。由于没有 cardgames 对象,我认为它只是将 card_id 拉入 id 并将其插入数据库。这工作正常,直到您有两个游戏使用同一张卡片,这违反了卡片游戏的主键约束。由于对数据库非常熟悉,我对这个问题的第一个解决方案是尝试通过删除 id 并将 cardid 和 gameid 作为主键来强制 rails 遵循这种关系的“真实”定义。它不起作用,因为迁移似乎无法处理具有两个主键的问题(尽管 Rails API 说可以这样做......很奇怪)。另一个解决方案是省略“id” INSERT INTO 语句中的列,并让数据库处理自动增量。不幸的是,我也不知道该怎么做。

那么,是否有另一种解决方法呢?有一些我不知道的漂亮的 Rails 技巧吗?或者这种结构在 Rails 中是不可能的?这真的很令人沮丧,因为我知道出了什么问题,而且我知道有几种方法可以修复它,但是由于 Rail 框架的限制,我无法做到。

4

5 回答 5

10

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的 .

于 2009-06-09T15:51:20.640 回答
4

要删除 ID 列,只需不要一开始就创建它。

  create_table :cards_rules, :id => false do ...
于 2009-06-04T22:35:07.677 回答
1

请参阅 Dr. Nis 复合主键

http://compositekeys.rubyforge.org/

于 2009-06-04T22:18:34.823 回答
0

我在破解后找到了解决方案。我发现您可以在迁移中使用“执行”功能。这非常有用,让我可以为这个问题拼凑出一个不优雅的解决方案。如果有人有更优雅、更像 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
于 2009-03-26T10:34:05.020 回答
0

你可能想看看这个foreign_key_migrations 插件

于 2009-03-26T10:38:40.237 回答