Ответ 1
Добавьте уникальный индекс, содержащий оба столбца. Это не позволит вам вставить запись, содержащую двойную пару category_id/post_id.
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
create_table :categories_posts, :id => false do |t|
t.column :category_id, :integer, :null => false
t.column :post_id, :integer, :null => false
end
У меня есть таблица соединений (как указано выше) со столбцами, которые относятся к соответствующей таблице категорий и таблице сообщений. Я хотел бы применить уникальное ограничение для составного ключа category_id, post_id в таблице соединений categories_posts. Но Rails этого не поддерживает (я считаю).
Чтобы избежать возможности дублирования строк в моих данных, имеющих одну и ту же комбинацию category_id и post_id, , что является лучшим обходным решением для отсутствия составного ключа в Rails?
Мои предположения здесь:
Добавьте уникальный индекс, содержащий оба столбца. Это не позволит вам вставить запись, содержащую двойную пару category_id/post_id.
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
Я думаю, вы можете легче проверить уникальность одного из полей с другим как область действия:
ОТ 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.
Это очень сложно, чтобы рекомендовать "правильный" подход.
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) Сочетание как
Можно прочитать о комбинации обоих, но мне нравится только номер один.
При использовании этой проблемы в рельсах я реализую оба этих параметра:
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
Решением может быть добавление индекса и валидации в модели.
Итак, в процессе миграции у вас есть: 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]