ruby-on-rails - 如何在多态连接表上强制唯一性?

标签 ruby-on-rails unique polymorphic-associations join

假设我有一个可以与 Book 或 Movie 实例关联的 Genre 模型。比如,

class Genre < ActiveRecord::Base
  belongs_to :genreable, polymorphic: true
end

class Book < ActiveRecord::Base
  has_many :genres, as: :genreable
end

class Movie < ActiveRecord::Base
  has_many :genres, as: :genreable
end

现在,假设我想标准化我的流派数据,因此每个流派在流派表中只有一个条目:

class Genre < ActiveRecord::Base
  has_many :genre_instances
end

class GenreInstance < ActiveRecord::Base
  belongs_to :genre
  belongs_to :genreable, polymorphic: true
end

class Book < ActiveRecord::Base
  has_many :genre_insances, as: :genreable
  has_many :genres, through: :genre_instances
end

class Movie < ActiveRecord::Base
  has_many :genre_insances, as: :genreable
  has_many :genres, through: :genre_instances
end

我的genre_instances 表包含genre_idgenreable_idgenreable_type 字段。

因此,如果流派 5 是“冒险”而电影 13 是“Point Break”,我可以在 genre_instances 中添加一个条目,例如 genre_id: 5、genreable_id: 13、genreable_type: '电影'

如果《Tom Sawyer》是第 13 本书,我可能会有另一个条目,例如 genre_id: 5、genreable_id: 13、genreable_type: 'Book'

如何以考虑“类型”列的方式强制在 Genre_instances 表中实现唯一性?我想确保“Point Break”只能有一个“冒险”条目,而不阻止“Tom Sawyer”也有这样的条目。

编辑:感谢 cwsault 的以下回答。它是死定了。我还在数据库级别添加了一个唯一索引,并进行了如下迁移:

class CreateGenreInstances < ActiveRecord::Migration
  def change
    create_table :genre_instances do |t|
      t.references :genre
      t.references :genreable, polymorphic: true

      t.timestamps
    end

    add_index :genre_instances, [:genre_id, :genreable_id, :genreable_type],
      unique: true, name: 'genre_and_genreable'
  end
end

...还有一个旁注:我必须指定索引的名称,因为 ActiveRecord 自动生成的索引名称超过了 64 个字符的限制。

对这一切进行了测试运行,它的工作正如预期的那样。

最佳答案

使用唯一性验证的 :scope 属性 ( documentation )。例如,在 GenreInstance 模型中,尝试:

class GenreInstance < ActiveRecord::Base

    ...

    validates :genre_id, :uniqueness => {
        :scope => [:genreable_id, :genreable_type]
    }
emd

这是我的一个项目的一个简化示例,其中的文章按不同主题分类:

class Article < ActiveRecord::Base
    has_many :topic_mappings, :as => :entity, :dependent => :destroy
    has_many :topics, :through => :topic_mappings
end

class Topic < ActiveRecord::Base
    has_many :topic_mappings, :dependent => :destroy
end

class TopicMapping < ActiveRecord::Base
    belongs_to :entity, :polymorphic => true
    belongs_to :topic

    validates :topic_id, :presence => true
    validates :entity_id, :presence => true
    validates :entity_type, :presence => true

    validates :topic_id, :uniqueness => {
        :scope => [:entity_id, :entity_type]
    }
end

请注意,您还应该通过唯一的数据库索引(在本例中是相关字段上的复合索引)来强制唯一性,否则创建对象的并发请求可能会导致竞争条件,其中 Rails 检查数据库在插入之前检查两个新项目是否存在。

此外,对于非多态关联,可以这样做:

:scope => [:model_name]

...但快速测试表明,即使具有多态关联,它也仅检查 _id 字段,因此需要指定 _id 和 _type 字段。

关于ruby-on-rails - 如何在多态连接表上强制唯一性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25214156/

相关文章:

ruby-on-rails - Rails 中强参数中的嵌套数组

arrays - 从 Matlab 元胞数组中获取唯一行

ruby-on-rails - STI 和多晶型物

ruby-on-rails-3 - 导轨 3 : Find parent of polymorphic model in controller?

javascript - 测试单击输入字段并生成文本选择

mysql - 如何在mysql控制台中知道我的用户名和密码

c# - 在两个数组中查找唯一字符串 C#

ruby-on-rails - Rails 类名称/类型不适用于多态 has_many :through

ruby-on-rails - 如何在 Rails 中以 "mm/dd/yyyy"格式显示当前日期

r - 独特的字符串组合