如何在多态连接表上强制实现唯一性?

时间:2014-08-09 00:44:48

标签: ruby-on-rails unique polymorphic-associations jointable

假设我有一个可以与书籍或电影的实例相关联的流派模型。像,

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

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

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是&#34;冒险&#34;和电影13是#34; Point Break&#34;,我可以在genre_instances genre_id: 5, genreable_id: 13, genreable_type: 'Movie'中输入一个条目。

如果&#34; Tom Sawyer&#34;是第13册,我可能会有另一个条目,如genre_id: 5, genreable_id: 13, genreable_type: 'Book'

如何以一种考虑&#39;类型&#39;的方式强制genre_instances表中的唯一性?柱?我想确保&#34; Point Break&#34;只能进行一次冒险&#34;进入,没有阻止&#34; Tom Sawyer&#34;也有这样的条目。

编辑 :感谢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个字符的限制。

全部用于测试运行并且它正如预期的那样工作。

1 个答案:

答案 0 :(得分:3)

使用唯一性验证的{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字段。