Rails中的外键问题

时间:2009-03-26 09:43:00

标签: ruby-on-rails ruby foreign-keys

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

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

5 个答案:

答案 0 :(得分: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

答案 1 :(得分:4)

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

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

答案 2 :(得分:1)

参见Dr. Nics复合主键

http://compositekeys.rubyforge.org/

答案 3 :(得分: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

答案 4 :(得分:0)

您可能需要查看此foreign_key_migrations plugin