ActiveRecord Scoped Uniqueness Validations与MySQL Compound Unique索引

时间:2015-05-21 21:04:56

标签: ruby-on-rails validation activerecord

首先,让我设定阶段:

我有一个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,其中包含特定的app_setting_option和一个特定的user_id,然后尝试再次执行完全相同的操作。验证可以阻止这种情况发生。它与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时,我得到了同样的错误。

2 个答案:

答案 0 :(得分:0)

对于布尔字段验证,您无法设置唯一性验证。 为了验证布尔字段,您可以执行类似的操作,

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

答案 1 :(得分:0)

最后,我必须按如下方式创建自定义验证:

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