添加包含两列的唯一索引.这将阻止您插入包含重复的category_id/post_id对的记录.
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
是的,这就是我原来的答案所说的......你应该做到这两点.请注意,索引是在迁移中添加的."add_index"仅在迁移中调用,而不在模型中调用. (2认同)
lzap.. 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
您还可以在连接表中添加两个单独的索引以加快搜索速度:
add_index :categories_posts, :category_id add_index :categories_posts, :post_id
请注意(根据Rails 3 Way一书),验证不是万无一失的,因为SELECT和INSERT/UPDATE查询之间存在潜在的竞争条件.如果您必须绝对确定没有重复记录,建议使用唯一约束.
2)防弹方法
在这种方法中,我们希望在数据库级别上设置约束.所以它意味着创建一个复合索引:
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
最大的优点是数据库完整性很好,缺点是没有太多有用的错误报告给用户.请注意,在创建复合索引时,列的顺序很重要.
如果您将较少选择性列作为索引中的前导列并将最多选择列放在最后,则在非前导索引列上具有条件的其他查询也可以利用INDEX SKIP SCAN.您可能需要再添加一个索引才能获得它们的优势,但这是高度依赖数据库的.
3)两者的结合
人们可以阅读关于两者的组合,但我倾向于只喜欢第一.
添加包含两列的唯一索引.这将阻止您插入包含重复的category_id/post_id对的记录.
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
这是很难推荐"正确"的方法.
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
您还可以在连接表中添加两个单独的索引以加快搜索速度:
add_index :categories_posts, :category_id add_index :categories_posts, :post_id
请注意(根据Rails 3 Way一书),验证不是万无一失的,因为SELECT和INSERT/UPDATE查询之间存在潜在的竞争条件.如果您必须绝对确定没有重复记录,建议使用唯一约束.
2)防弹方法
在这种方法中,我们希望在数据库级别上设置约束.所以它意味着创建一个复合索引:
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
最大的优点是数据库完整性很好,缺点是没有太多有用的错误报告给用户.请注意,在创建复合索引时,列的顺序很重要.
如果您将较少选择性列作为索引中的前导列并将最多选择列放在最后,则在非前导索引列上具有条件的其他查询也可以利用INDEX SKIP SCAN.您可能需要再添加一个索引才能获得它们的优势,但这是高度依赖数据库的.
3)两者的结合
人们可以阅读关于两者的组合,但我倾向于只喜欢第一.
我认为您可以更容易地验证其中一个字段的唯一性,另一个作为范围:
来自API:
validates_uniqueness_of(*attr_names)
验证指定属性的值在整个系统中是否唯一.用于确保只有一个用户可以命名为"davidhh".
class Person < ActiveRecord::Base validates_uniqueness_of :user_name, :scope => :account_id end
它还可以基于多个范围参数验证指定属性的值是否唯一.例如,确保教师每学期只能按照特定班级的时间表进行一次.
class TeacherSchedule < ActiveRecord::Base validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id] end
创建记录时,将执行检查以确保数据库中不存在具有指定属性(映射到列)的给定值的记录.更新记录时,会进行相同的检查,但忽略记录本身.
配置选项:
* 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.
当我在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