在连接表中,Rails缺少复合键的最佳解决方法是什么?

pez*_*ser 30 database ruby-on-rails composite-key junction-table

create_table :categories_posts, :id => false do |t|
  t.column :category_id, :integer, :null => false
  t.column :post_id, :integer, :null => false
end
Run Code Online (Sandbox Code Playgroud)

我有一个连接表(如上所述),其中的列引用了相应的类别表和posts表.我想执行的唯一约束组合键CATEGORY_ID,POST_IDcategories_posts连接表.但是Rails不支持这个(我相信).

为了避免我的数据中具有相同category_id和post_id组合的重复行的可能性,在Rails中缺少复合键的最佳解决方法是什么?

我的假设是:

  1. 在这种情况下,默认的自动编号列(id:整数)无法保护我的数据.
  2. ActiveScaffold可能会提供一个解决方案,但我不确定将它包含在我的项目中仅仅针对这个单一功能是否过度,特别是如果有更优雅的答案.

tva*_*son 37

添加包含两列的唯一索引.这将阻止您插入包含重复的category_id/post_id对的记录.

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
Run Code Online (Sandbox Code Playgroud)

  • 是的,这就是我原来的答案所说的......你应该做到这两点.请注意,索引是在迁移中添加的."add_index"仅在迁移中调用,而不在模型中调用. (2认同)

lza*_*zap 12

这是很难推荐"正确"的方法.

1)务实的方法

使用验证器,不要添加唯一的复合索引.这为您提供了很好的消息在UI中,它只是工作.

class CategoryPost < ActiveRecord::Base
  belongs_to :category
  belongs_to :post

  validates_uniqueness_of :category_id, :scope => :post_id, :message => "can only have one post assigned"
end
Run Code Online (Sandbox Code Playgroud)

您还可以在连接表中添加两个单独的索引以加快搜索速度:

add_index :categories_posts, :category_id
add_index :categories_posts, :post_id
Run Code Online (Sandbox Code Playgroud)

请注意(根据Rails 3 Way一书),验证不是万无一失的,因为SELECT和INSERT/UPDATE查询之间存在潜在的竞争条件.如果您必须绝对确定没有重复记录,建议使用唯一约束.

2)防弹方法

在这种方法中,我们希望在数据库级别上设置约束.所以它意味着创建一个复合索引:

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
Run Code Online (Sandbox Code Playgroud)

最大的优点是数据库完整性很好,缺点是没有太多有用的错误报告给用户.请注意,在创建复合索引时,列的顺序很重要.

如果您将较少选择性列作为索引中的前导列并将最多选择列放在最后,则在非前导索引列上具有条件的其他查询也可以利用INDEX SKIP SCAN.您可能需要再添加一个索引才能获得它们的优势,但这是高度依赖数据库的.

3)两者的结合

人们可以阅读关于两者的组合,但我倾向于只喜欢第一.


Oin*_*nak 10

我认为您可以更容易地验证其中一个字段的唯一性,另一个作为范围:

来自API:

validates_uniqueness_of(*attr_names)
Run Code Online (Sandbox Code Playgroud)

验证指定属性的值在整个系统中是否唯一.用于确保只有一个用户可以命名为"davidhh".

  class Person < ActiveRecord::Base
    validates_uniqueness_of :user_name, :scope => :account_id
  end
Run Code Online (Sandbox Code Playgroud)

它还可以基于多个范围参数验证指定属性的值是否唯一.例如,确保教师每学期只能按照特定班级的时间表进行一次.

  class TeacherSchedule < ActiveRecord::Base
    validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
  end
Run Code Online (Sandbox Code Playgroud)

创建记录时,将执行检查以确保数据库中不存在具有指定属性(映射到列)的给定值的记录.更新记录时,会进行相同的检查,但忽略记录本身.

配置选项:

* message - Specifies a custom error message (default is: "has already been taken")
* scope - One or more columns by which to limit the scope of the uniquness constraint.
* case_sensitive - Looks for an exact match. Ignored by non-text columns (true by default).
* allow_nil - If set to true, skips this validation if the attribute is null (default is: false)
* if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
Run Code Online (Sandbox Code Playgroud)


Lar*_*y K 5

当我在rails中遇到此问题时,我实现了以下两个方面:

1)您应该在数据库级别声明一个唯一的复合索引,以确保dbms不会创建重复记录.

2)为了提供比上述更平滑的错误消息,请在Rails模型中添加验证:

validates_each :category_id, :on => :create do |record, attr, value|
  c = value; p = record.post_id
  if c && p && # If no values, then that problem 
               # will be caught by another validator
    CategoryPost.find_by_category_id_and_post_id(c, p)
    record.errors.add :base, 'This post already has this category'
  end
end
Run Code Online (Sandbox Code Playgroud)

  • Tvanfosson的解决方案与我的#1相同 - 索引是在db级别声明的,而不是在Rails或Ruby级别.Rails只能在提交dup时报告"SQL错误".这就是为什么你想在模型中验证(在Rails级别). (2认同)