В таблице соединений, какой лучший обходной путь для отсутствия Rails составного ключа? - PullRequest
30 голосов
/ 19 мая 2009
create_table :categories_posts, :id => false do |t|
  t.column :category_id, :integer, :null => false
  t.column :post_id, :integer, :null => false
end

У меня есть таблица соединений (как указано выше) со столбцами, которые относятся к соответствующей таблице категории и таблице posts . Я хотел применить уникальное ограничение для составного ключа category_id, post_id в таблице соединений category_posts . Но Rails не поддерживает это (я считаю).

Чтобы исключить возможность дублирования строк в моих данных, имеющих одинаковую комбинацию category_id и post_id, Какой наилучший обходной путь для отсутствия составного ключа в Rails ?

Мои предположения здесь:

  1. Столбец автоматического номера по умолчанию (id: integer) не будет делать ничего защитить мои данные в этой ситуации.
  2. ActiveScaffold может предоставить решение, но я не уверен, если излишне включать его в мой проект просто для этого сингла особенность, особенно если есть более элегантный ответ.

Ответы [ 5 ]

34 голосов
/ 19 мая 2009

Добавить уникальный индекс, который включает оба столбца. Это не позволит вам вставить запись, которая содержит дубликат пары category_id / post_id.

add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
12 голосов
/ 16 августа 2011

очень трудно рекомендовать "правильный" подход.

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) Сочетание обоих

Можно прочитать о комбинации обоих, но мне нравится только номер один.

10 голосов
/ 19 мая 2009

Я думаю, вам будет проще проверить уникальность одного из полей с другим в качестве области действия:

ИЗ 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.
5 голосов
/ 19 мая 2009

Я реализую оба следующих вопроса, когда у меня есть эта проблема в рельсах:

1) У вас должен быть уникальный составной индекс, объявленный на уровне базы данных, чтобы гарантировать, что базы данных не позволят создать дублирующую запись.

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
1 голос
/ 27 августа 2010

Решением может быть добавление как индекса, так и проверки в модель.

Итак, в миграции у вас есть: add_index: category_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]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...