【问题标题】:ActiveRecord Scoped Uniqueness Validations vs MySQL Compound Unique IndexesActiveRecord 范围唯一性验证与 MySQL 复合唯一索引
【发布时间】:2015-05-21 21:04:55
【问题描述】:

首先,让我来做个准备:

我有一个app_settings 表,它将app_setting_optionsuser_iduser_role_idevent_idgroup_idapp_level_setting 连接起来。对于任何给定的app_setting,应该有一个特定的app_setting_option_id,并且只有一个其他对象。对于这些次要对象中的每一个,我都在AppSetting 模型中指定了一个唯一性验证:

validates :user_id, uniqueness: { scope: :app_setting_option_id}, allow_nil: true
validates :user_role_id, uniqueness: { scope: :app_setting_option_id}, allow_nil: true
validates :group_id, uniqueness: { scope: :app_setting_option_id}, allow_nil: true
validates :event_id, uniqueness: { scope: :app_setting_option_id}, allow_nil: true
validates :app_level_setting, uniqueness: { scope: :app_setting_option_id}, allow_nil: true

除此之外,我还在数据库中的每个辅助对象上设置了一个复合唯一索引。通常我只会依靠 ActiveRecord 验证来处理任何奇怪的边缘情况,但是(长话短说)往往会发生相当多的直接数据库调整,我们希望阻止意外发生。

在我的测试中,我创建了一个带有特定 app_setting_option 和特定 user_idapp_setting,然后尝试再次执行完全相同的操作。验证阻止了这种情况的发生。它也可以完美地与user_roleeventgroup 配合使用。问题在于app_level_setting,它以某种方式使其通过验证并被数据库级别的复合唯一索引停止,得到Mysql2::Error: Duplicate entry

it 'app_level_setting uniqueness' do
  app_setting_option = AppSettingOption.find(21)
  create(:app_setting, app_setting_option: app_setting_option, app_level_setting: 1)
  create(:app_setting, app_setting_option: app_setting_option, app_level_setting: 1)
end

第二个 app_setting 没有创建是因为数据库上的索引,而不是因为验证。关于布尔值的唯一性验证有什么具体的吗?当我尝试在控制台中创建相同的 app_level_setting 时,我也遇到了同样的错误。

【问题讨论】:

    标签: ruby-on-rails validation activerecord


    【解决方案1】:

    对于布尔字段验证,您不能设置唯一性验证。 为了验证布尔字段,您可以执行以下操作,

    validates :field, :inclusion => {:in => [true, false]}
    

    【讨论】:

    • 是否可以确定范围?我想确保对于任何特定的app_setting_option_id,只有一条 app_level_setting == true 的记录。这有意义吗?
    • 您可以使用 app_setting_option_id 确定范围。
    • 好的,但它的语法是什么?范围在哪里?
    【解决方案2】:

    最后,我必须创建一个自定义验证,如下所示:

    validate :app_level_setting_validation
    
    def app_level_setting_validation
      errors.add(:app_setting, "app level setting already exists") if app_setting_option.app_setting_type_id == 1 && AppSetting.find_by(app_setting_option_id: app_setting_option.id, app_level_setting: true).present?
    end
    

    然后我的测试如下:

    it 'App level setting uniqueness' do
      app_setting_option = create(:app_setting_option)
      create(:app_setting, app_setting_option: app_setting_option, app_level_setting: true)
      setting = build(:app_setting, app_setting_option: app_setting_option, app_level_setting: true)
      setting.should_not be_valid
    end
    

    【讨论】:

      猜你喜欢
      • 2013-05-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-28
      • 2012-03-24
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      相关资源
      最近更新 更多