【问题标题】:Foreign Key Issues in RailsRails 中的外键问题
【发布时间】:2009-03-26 09:42:59
【问题描述】:

我花了一段时间才找到这个错误,但我终于找到了为什么。我正在使用 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 说可以这样做......很奇怪)。另一个解决方案是在 INSERT INTO 语句中省略“id”列,让数据库处理自动增量。不幸的是,我也不知道该怎么做。

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

【问题讨论】:

  • 你能发布start_game代码吗?还有,为什么游戏会属于卡片?
  • 从语义上讲不是。但是,这就是 N 对 M 关系的定义方式,一张牌可以同时出现在多个游戏中(以及单个游戏中的多张牌)

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


【解决方案1】:

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 =&gt; Rule

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      查看 Dr. Nis 复合主键

      http://compositekeys.rubyforge.org/

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 执行是我在迁移中做比简单的表添加或删除更有趣的事情的方式。 IMO 迁移的用处在于它们是增量的和有序的。 (即便如此,也不是很好,但那是另一种咆哮。)
        【解决方案5】:

        你可能想看看这个foreign_key_migrations plugin

        【讨论】:

        • 看过之后,它似乎只是在数据库内部创建外键约束的一种方法。这是一个不错的功能,但并不能真正解决这个问题(除非我错过了什么)。我需要能够删除 id 列或修改 insert into 语句以忽略 id 列
        猜你喜欢
        • 2011-04-13
        • 1970-01-01
        • 1970-01-01
        • 2015-07-18
        • 1970-01-01
        • 2011-03-19
        • 2011-05-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多