【问题标题】:HABTM duplicate recordsHABTM 重复记录
【发布时间】:2014-03-20 17:25:03
【问题描述】:

我有 2 个模型 GameTheme,它们有一个 has_and_belongs_to_many 关联。我尝试了许多解决方案来防止games_themes 表中的重复记录,但没有解决方案有效。问题是,games_themes 是一个表,但它不是一个模型,所以我想不出一种有效地对其进行验证的方法。

这是我尝试过的解决方案

class Theme < ActiveRecord::Base
  has_and_belongs_to_many :games, :uniq => true
end

class Game < ActiveRecord::Base
  has_and_belongs_to_many :themes, :uniq => true
end

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-3 associations


    【解决方案1】:

    您应该使用数据库级别的验证:

    #new_migration
    add_index :games_themes, [:game_id, :theme_id], :unique => true
    

    HABTM

    这将防止您在数据库中保存任何重复数据。减轻 Rails 的负担并确保您只有游戏或主题。问题是因为 HABTM 没有模型,在 Rails 中无法执行验证,这意味着您需要将其设为 db 级别

    正如 cmets 中所述,这意味着您必须像这样处理从数据库引发的异常:

    #app/controllers/games_controller.rb
    def create
        #creation stuff here
        if @game.save
            #successful save
        else
            #capture errors
        end
    end
    

    【讨论】:

    • 在最新版本的 Rails 中,您需要捕获 ActiveRecord::RecordNotUnique
    【解决方案2】:

    为验证目的创建新的模型游戏主题不是一个好主意。我们可以在迁移中验证自己。

    主题模型:

    class Theme < ActiveRecord::Base
      has_and_belongs_to_many :games,
        :association_foreign_key => 'theme_id',
        :class_name => 'Theme',
        :join_table => 'games_themes'
    end
    

    游戏模型:

    class Theme < ActiveRecord::Base
      has_and_belongs_to_many :games,
        :association_foreign_key => 'game_id',
        :class_name => 'Game',
        :join_table => 'games_themes'
    end
    

    games_themes 迁移: 您可以为连接表添加唯一性,请查看here 了解更多详细信息。

    class GamesThemesTable < ActiveRecord::Migration
      def self.up
        create_table :games_themes, :id => false do |t|
          t.references :game
          t.references :theme
        end
    
        add_index :games_themes, [:theme_id, :game_id], :unique => true
    
      end
    
      def self.down
        drop_table :games_themes
      end
    end
    

    【讨论】:

      【解决方案3】:

      用途:

      validates_uniqueness_of :theme_id, :scope => :game_id
      

      如下:

      class Theme < ActiveRecord::Base
        has_many :games, through: :games_themes
      end
      
      class Game < ActiveRecord::Base
        has_many :themes, through: :games_themes
      end
      
      class GamesThemes < ActiveRecord::Base
        belongs_to :game
        belongs_to :theme
      
        validates_uniqueness_of :theme_id, :scope => :game_id
      end
      

      【讨论】:

      • 我应该把这个验证放在哪里?所有模型中都没有名为theme_id 的属性。我只有一个名为games_themes 的表,其中包含两个外键。而且由于我使用的是HABTM,因此该表没有关联的模型
      • 所以正确的做法是将逻辑放在它所属的地方,也就是games_themes模型中。我确实阅读了您上面的评论,您不想创建模型,但关键是这就是它的用途。隔离使该组合唯一所需的代码,而不是在不正确的地方跳过箍。这是验证的正确位置。
      • 是的,正如 Pavel 所说,如果您需要对连接进行验证,则必须使用 has_many :through
      • 好的,谢谢,rails 应该找到实现这一点的方法。
      • has_many :through 方法,使用连接模型是 Rails 实现它的方式。一种思考方式是,您的约束与两个模型的信息有关,对,主题模型的 id 和游戏模型的 id。将解释这两条信息的代码放在游戏模型或主题模型中是错误的,因为这将是不必要的耦合,并且可能会违反某种 DRY,具体取决于您的实现方式。取而代之的是一个专门用于推理游戏和主题的 id 的模型。有意义吗?
      【解决方案4】:

      要对连接表运行验证,您应该改用has_many :through 关联。 http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

      【讨论】:

      • 我知道我可以像这样解决问题。但我想要一个不同的解决方案,它不涉及创建新模型来进行验证
      • @user2158382 我认为没有其他方法。你可以添加一个数据库约束,但是你必须处理数据库异常。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-29
      • 1970-01-01
      • 2015-04-25
      • 1970-01-01
      • 1970-01-01
      • 2011-10-23
      • 1970-01-01
      相关资源
      最近更新 更多