Не учитывающий регистр уникальный индекс в Rails / ActiveRecord? - PullRequest
22 голосов
/ 31 октября 2011

Мне нужно создать регистр без учета регистра для столбца в рельсах. Я сделал это через SQL:

execute(
   "CREATE UNIQUE INDEX index_users_on_lower_email_index 
    ON users (lower(email))"
 )

Это прекрасно работает, но в моем файле schema.rb у меня есть:

add_index "users", [nil], 
  :name => "index_users_on_lower_email_index", 
  :unique => true

Обратите внимание на «ноль». Поэтому, когда я пытаюсь клонировать базу данных для запуска теста, я получаю очевидную ошибку. Я что-то здесь не так делаю? Есть ли какое-то другое соглашение, которое я должен использовать внутри рельсов?

Спасибо за помощь.

Ответы [ 8 ]

36 голосов
/ 19 мая 2012

Поскольку индексы MySQL уже нечувствительны к регистру, я предполагаю, что вы имеете дело с PostgreSQL, который по умолчанию создает регистрозависимые индексы. Я отвечаю здесь, основываясь на Rails 3.2.3 и PostgreSQL 8.4.

Кажется, функциональные индексы - это еще один пример того, что ActiveRecord не может сгенерировать. Внешние ключи и столбцы UUID - еще две, которые приходят на ум. Таким образом, нет другого выбора (кроме исправления обезьян ActiveRecord), кроме использования execute операторов.

Это означает, что для точного дампа вашей базы данных вам нужно отказаться от DB-agnostic schema.rb в пользу DB-специфичной структуры.sql. См. Руководство по миграции на Rails, раздел 6.2 Типы дампов схемы . Это устанавливается следующим образом:

конфиг / application.rb

config.active_record.schema_format = :sql

db / structure.sql должен обновляться автоматически при запуске миграции. Вы можете создать его вручную с помощью этой команды:

rake db:structure:dump

Файл является чистым Postgres SQL. Хотя это не задокументировано, когда вы используете rake -T для вывода списка задач rake, похоже, что вы можете использовать эту команду для загрузки базы данных из дампа структуры.sql:

rake db:structure:load

Здесь нет ничего волшебного: исходный код (показанный здесь из Rails 3.2.16) просто вызывает psql для структуры.sql.

Наконец, вот моя миграция, чтобы удалить старое, чувствительное к регистру ограничение электронной почты и добавить регистрозависимый функциональный индекс:

class FixEmailUniqueIndexOnUsers < ActiveRecord::Migration
  def up
    remove_index :users, :email
    execute "CREATE UNIQUE INDEX index_users_on_lowercase_email 
             ON users USING btree (lower(email));"
  end

  def down
    execute "DROP INDEX index_users_on_lowercase_email;"
    add_index :users, :email, :unique => true
  end
end
24 голосов
/ 21 августа 2015

Если вы используете PostgreSQL, вы можете изменить тип столбца на citext - строка без учета регистра. Это также делает поиск независимым от регистра.

def change
  enable_extension :citext
  change_column :users, :email, :citext
  add_index :users, :email, unique: true
end
5 голосов
/ 31 октября 2011

Я бы упростил это ...

В вашей модели:

before_validation :downcase_email

def downcase_email
  self.email = email.downcase
end

Таким образом, индекс не зависит от базы данных, а все ваши электронные письма в базе данных строчные.

4 голосов
/ 31 января 2013

Рассматривали ли вы использование schema_plus (https://github.com/lomba/schema_plus)? Среди прочего (поддержка принудительного применения внешних ключей в базе данных и для представлений), он поддерживает установку индексов без учета регистра для баз данных PostgreSQL и обрабатывает их вывод в схема. Из файла Readme: «Если вы используете Postgresql, SchemaPlus обеспечивает поддержку условий, выражений, методов индекса и индексов без учета регистра».

2 голосов
/ 31 октября 2011

В документации неясно, как это сделать, но источник выглядит следующим образом:

def add_index(table_name, column_name, options = {})
  index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
  execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
end

Итак, если quote_column_name вашей базы данных является реализацией по умолчанию (которая ничего не делаетвообще), тогда это может сработать:

add_index "users", ['lower(email)'], :name => "index_users_on_lower_email_index", :unique => true

Вы заметили, что вы пробовали это, но это не сработало (добавление этого к вашему вопросу может быть хорошей идеей).Похоже, ActiveRecord просто не понимает индексы вычисленного значения.Я могу подумать об уродливом хаке, который сделает это, но он уродлив:

  1. Добавить столбец email_lc.
  2. Добавить хук before_validation или before_saveпоместите строчную версию email в email_lc.
  3. Поместите свой уникальный индекс в email_lc.

Это довольно уродливо, и вы можете чувствовать себя грязно, ноэто лучшее, что я могу думать прямо сейчас.

0 голосов
/ 28 февраля 2016

Я бы предложил (просто как возможность рассмотреть среди других) использовать два отдельных поля:

  • email
  • email_original

Первоеодно всегда в нижнем регистре и, таким образом, может быть уникально проиндексировано без учета базы данных, в то время как второе поле дословно сохраняет то, что пользователь вводил, и может иметь верхние символы.

Очевидно, что в пользовательской модели необходимо настроить электронную почту на основе email_original при каждом сохранении и запретить прямое искажение с полем электронной почты.

0 голосов
/ 27 мая 2015

Для Rails 4.2 создайте без учета регистра уникальный индекс в таблице пользователей в столбце имени.

Создать новый файл миграции с пустым методом изменения:

$ rails generate migration add_index_in_users_on_name

Добавить вызов метода add_index в пустой метод изменения:

add_index :users, 'lower(name)', name: 'index_users_on_lower_name', unique: true

Запустить Rake DB: миграция задачи:

$ rake db:migrate

В результате индекс будет добавлен правильно, а файл db / schema.rb содержит правильный add_index:

add_index "users", ["LOWER(\"NAME\")"], name: "index_users_on_lower_name", unique: true

Это проверено только с Oracle RDB.

0 голосов
/ 31 октября 2011

Я думаю, что вам нужны имена столбцов, как показано ниже

   add_index "users", [email], {:name => "index_users_on_lower_email_index", :unique => true }

И вы должны сделать поле электронной почты в базе данных с надлежащей сортировкой без учета регистра, чтобы ваш индекс также не учитывал регистр.

в зависимости от используемого вами механизма дБ синтаксис может отличаться, но

alter table [users] alter column [email] varchar(250) collate utf8_general_ci ...

и при добавлении индекса в этот столбец он будет нечувствителен к регистру.

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