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

发布于 2024-07-22 07:18:53 字数 656 浏览 16 评论 0原文

create_table :categories_posts, :id => false do |t|
  t.column :category_id, :integer, :null => false
  t.column :post_id, :integer, :null => false
end

我有一个连接表(如上所述),其中的列引用相应的categories表和posts表。 我想对categories_posts连接表中的复合键category_id, post_id强制执行唯一约束。 但 Rails 不支持这一点(我相信)。

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

我的假设是:

  1. 默认的自动编号列 (id:integer) 不会做任何事情 在这种情况下保护我的数据。
  2. ActiveScaffold 可以提供 解决方案,但我不确定是否 将它包含在我的中有点过分了 项目只是为了这个单曲 功能,特别是如果有 更优雅的答案。
create_table :categories_posts, :id => false do |t|
  t.column :category_id, :integer, :null => false
  t.column :post_id, :integer, :null => false
end

I have a join table (as above) with columns that refer to a corresponding categories table and a posts table. I wanted to enforce a unique constraint on the composite key category_id, post_id in the categories_posts join table. But Rails does not support this (I believe).

To avoid the potential for duplicate rows in my data having the same combination of category_id and post_id, what's the best workaround for the absence of a composite key in Rails?

My assumptions here are:

  1. The default auto-number column
    (id:integer) would do nothing to
    protect my data in this situation.
  2. ActiveScaffold may provide a
    solution but I'm not sure if
    it's overkill to include it in my
    project simply for this single
    feature, especially if there is a
    more elegant answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

巷子口的你 2024-07-29 07:18:54

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

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'

Add a unique index that includes both columns. That will prevent you from inserting a record that contains a duplicate category_id/post_id pair.

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
我最亲爱的 2024-07-29 07:18:54

推荐“正确”的方法非常困难

1) 务实的方法

使用验证器并且不添加唯一的复合索引。 这会在用户界面中为您提供漂亮的消息,而且它确实有效。

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) 两者的组合

人们可以阅读有关两者的组合的内容,但我倾向于只喜欢第一个。

It's very hard to recommend the "right" approach.

1) The pragmatic approach

Use validator and do not add unique composite index. This gives you nice messages in the UI and it just works.

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

You can also add two separate indexes in your join tables to speed up searches:

add_index :categories_posts, :category_id
add_index :categories_posts, :post_id

Please note (according to the book Rails 3 Way) the validation is not foolproof because of a potential race condition between the SELECT and INSERT/UPDATE queries. It is recommended to use unique constraint if you must be absolutely sure there are no duplicate records.

2) The bulletproof approach

In this approach we want to put a constraint on the database level. So it means to create a composite index:

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'

Big advantage is a great database integrity, disadvantage is not much useful error reporting to the user. Please note in creating of composite index, order of columns is important.

If you put less selective columns as leading columns in index and put most selective columns at the end, other queries which have condition on non-leading index columns may also take advantage of INDEX SKIP SCAN. You may need to add one more index to get advantage of them, but this is highly database dependant.

3) Combination of both

One can read about combination of both, but I tend to like the number one only.

千紇 2024-07-29 07:18:54

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

从 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.

I think you can find easier to validate uniqueness of one of the fields with the other as a scope:

FROM THE API:

validates_uniqueness_of(*attr_names)

Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user can be named "davidhh".

  class Person < ActiveRecord::Base
    validates_uniqueness_of :user_name, :scope => :account_id
  end

It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example, making sure that a teacher can only be on the schedule once per semester for a particular class.

  class TeacherSchedule < ActiveRecord::Base
    validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
  end

When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.

Configuration options:

* 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.
唱一曲作罢 2024-07-29 07:18:54

当我在 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

I implement both of the following when I have this issue in rails:

1) You should have a unique composite index declared at the database level to ensure that the dbms won't let a duplicate record get created.

2) To provide smoother error msgs than just the above, add a validation to the Rails model:

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
花海 2024-07-29 07:18:54

解决方案可以是在模型中添加索引和验证。

所以在迁移中你有:
add_index :categories_posts, [:category_id, :post_id], :unique => true

在模型中:
validates_uniqueness_of :category_id, :scope => [:category_id,:post_id]
validates_uniqueness_of :post_id, :scope => [:category_id,:post_id]

A solution can be to add both the index and validation in the model.

So in the migration you have:
add_index :categories_posts, [:category_id, :post_id], :unique => true

And in the model:
validates_uniqueness_of :category_id, :scope => [:category_id, :post_id]
validates_uniqueness_of :post_id, :scope => [:category_id, :post_id]

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文