Почему при миграции Rails внешние ключи определяются в приложении, а не в базе данных?

Если я определяю модель Customer и Order, в которой Customer "имеет много" Orders, а Order "принадлежит" Customer, в Rails мы говорим о Order, имеющем внешний ключ от Customer до customer_id, но мы не имеем в виду, что это принудительно в базе данных.

Поскольку Rails не определяет это как ограничение на уровне базы данных, существует риск нарушения целостности ваших данных, возможно, вне приложения (или внутри, если вы получаете одновременные запросы?), Если вы не принудительно принудительно не принудительно примените ограничение в базе данных.

Почему Rails не определяет внешний ключ на уровне базы данных или есть способ заставить Rails это сделать?

class Customer < ActiveRecord::Base
  has_many :orders
end

class Order < ActiveRecord::Base
    belongs_to :customer
end

ActiveRecord::Schema.define(:version => 1) do

  create_table "customers", :force => true do |t|
    t.string   "name"
  end

  create_table "orders", :force => true do |t|
    t.string   "item_name"
    t.integer  "customer_id"
  end

end

person eggdrop    schedule 29.05.2009    source источник


Ответы (5)


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

Например, Rails даже поддерживает некоторые проекты баз данных, которые нельзя использовать внешние ключи, например полиморфные ассоциации.

По сути, соглашения Rails рассматривают базу данных как статическое устройство хранения данных, а не как активную СУБД. Rails 2.0 наконец поддерживает некоторые более реалистичные возможности баз данных SQL. Неудивительно, что в результате разработка с помощью Rails станет более сложной, чем это было в версии 1.0.

person Bill Karwin    schedule 29.05.2009
comment
Какие особенности Rails 2.0 вы имели в виду? - person eggdrop; 30.05.2009
comment
Одна из функций, о которой я думаю, - это поддержка столбцов первичного ключа, которые не имеют имени id. Я не являюсь обычным разработчиком Rails, поэтому я не отслеживаю все их новые возможности. - person Bill Karwin; 30.05.2009
comment
В порядке. В целом, чтобы исправить эту конструкцию в Rails, порекомендовали бы вы определение внешних ключей уровня базы данных для усиления внешнего ключа уровня приложения? Или это суждение, которое действительно зависит от приложения? У меня сложилось впечатление, что всегда рекомендуется определять внешний ключ уровня базы данных в таких ситуациях, как в моем примере выше. - person eggdrop; 30.05.2009
comment
Я согласен, что лучше всего определять ограничения в базе данных. Только так вы можете быть уверены, что целостность данных обеспечивается на постоянной основе. Если вы сделаете это в приложении, то любой, кто обращается к базе данных, не просматривая ваше приложение, не будет подвергаться тем же ограничениям, и они испортят данные. Однако я понятия не имею, как убедить миграции Rails реализовать реальные ограничения базы данных. Это одна из причин, по которой я не использую Rails. - person Bill Karwin; 30.05.2009
comment
Конечно. Однако я имел в виду вариант определения ваших ограничений вручную в базе данных после запуска миграции Rails. К сожалению, не все ваши DDL применяются в одном месте. - person eggdrop; 30.05.2009
comment
Правильно; это может быть то, что вам нужно сделать. Вы попадаете в некоторые «уловки», подумал я, если попытаетесь обеспечить ссылочную целостность с помощью обоих ограничений базы данных и логики приложения. Например, каскадные обновления невозможны. - person Bill Karwin; 30.05.2009
comment
Невозможно использовать метод before_update для каскадных обновлений, как объясняет Майк Гандерлой по этой ссылке: workingwithrails.com/forums/4-ask-a-rails-expert/topics/ Или для этого метода потребуется отсутствие ограничений на уровне базы данных? before_update: update_employees def update_employees employee.each do {| e | e.update_attribute (: date_valid = ›self.date_valid)} конец - person eggdrop; 30.05.2009
comment
Я имею в виду каскадные обновления первичного ключа (обычно не требующиеся при использовании суррогатного ключа). Если у вас есть ограничения внешнего ключа в дочерних таблицах, ссылающиеся на этот первичный ключ, как изменить значение первичного ключа и ссылки на него в дочерних таблицах с помощью обновлений, управляемых приложением? Вы не можете сначала изменить ребенка, а также не можете сначала изменить родителя. Единственный ответ - определить внешние ключи с помощью ON UPDATE CASCADE (если это то, что вы хотите), а затем выполнить обновление только для родительского элемента. Если Rails думает, что может изменить и то, и другое, это ломается. - person Bill Karwin; 30.05.2009
comment
Вы не можете сначала изменить ребенка, а также не можете сначала изменить родителя. Вы не можете сначала изменить дочерний элемент из-за внешнего ключа уровня базы данных по отношению к родительскому первичному ключу. Но почему нельзя сначала сменить родителя? (Прошу прощения, если я исчерпал ваше терпение). - person eggdrop; 30.05.2009
comment
Попробуй. Вы получаете сообщение об ошибке при изменении значения родительского ключа, если дочерние записи ссылаются на него (если вы не объявили внешний ключ дочернего элемента с помощью ON UPDATE CASCADE). Даже если вы используете многотабличный синтаксис MySQL UPDATE и попытаетесь изменить оба в одном операторе, вы получите сообщение об ошибке. - person Bill Karwin; 30.05.2009
comment
ребята, классный вопрос, отличный ответ, отличное обсуждение. Большое спасибо! - person AJP; 29.08.2011

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

Валидации и проверки на уровне приложения предназначены для обеспечения простых, быстрых, удобочитаемых (подумайте о сообщениях об ошибках) проверок, которые работают в 99,99% случаев. Если вашему приложению требуется больше, вы должны использовать ограничения уровня базы данных.

Я думаю, что эта «философия» эволюционировала из-за использованных исходных структур тестирования: внешние ключи оказались огромной проблемой при использовании фикстур. Это похоже на то, когда «ошибка» становится «особенностью», потому что ее никто не исправляет. (Если я неправильно запоминаю историю, поправьте меня.)

Как минимум, в сообществе Rails наблюдается растущее движение за обеспечение целостности базы данных. Прочтите это сообщение в блоге за последний месяц. Она даже ссылки на некоторые плагины, которые помогают оказывать поддержку при обработке ошибок (и еще одно сообщение в блоге, которое ссылается на другие плагины). Сделайте еще несколько поисков в Google; Я видел и другие плагины, которые добавляют поддержку миграции для создания внешних ключей.

Итак, что является частью основной философии Rails: не беспокойтесь о вещах, если они вам действительно не нужны. Для многих веб-приложений, вероятно, нормально, если небольшой (возможно, крошечный) процент записей содержит недопустимые данные. Страницы, которые могут быть затронуты, могут просматриваться очень редко, или ошибка уже может быть обработана изящно. Или, может быть, дешевле (как, например, «холодные наличные») решать проблемы вручную в течение следующих 6 месяцев по мере роста приложения, чем тратить сейчас ресурсы на планирование ресурсов разработки для всех непредвиденных обстоятельств. По сути, если в ваших сценариях использования все это не кажется важным, и на самом деле это может быть вызвано только состоянием гонки, которое может произойти 1/10000000 запросов ... ну, стоит ли оно того?

Поэтому я предсказываю, что появятся инструменты, которые по умолчанию будут лучше справляться со всей ситуацией, и в конечном итоге они будут объединены в Rails 3. А пока, если вашему приложению это действительно нужно, добавьте их. Это вызовет легкую головную боль при тестировании, но ничего, что вы не сможете преодолеть с помощью заглушек и заглушек. И если вашему приложению это действительно не нужно ... что ж, у вас уже все хорошо. :)

person Ian Terrell    schedule 30.05.2009
comment
Это кажется реалистичным, разумным и практичным. Отличный пост в блоге (она, кстати, не он). Спасибо за ссылку. Я собираюсь опробовать этот плагин. - person eggdrop; 30.05.2009

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

person Dave Taylor    schedule 23.03.2011
comment
спасибо за то, что поделились своим опытом, очень признателен, сэр. - person Ahmad Al-kheat; 06.08.2017

Одна ошибка, которую делают многие люди, путает миграции с моделью. Миграции просто изменяют базу данных и не имеют ничего общего с определенными вами моделями. В результате этой путаницы многие плагины внешнего ключа пытаются объединить модель с миграциями и делают слишком много волшебных вещей.

Для миграции я бы использовал http://github.com/matthuhiggins/foreigner/tree/master. Вам не нужно менять свои модели, чтобы внешние ключи работали с Rails.

person Community    schedule 04.09.2009

Он действительно создает столбец customer_id (очевидно). Однако по большей части Rails верит в обеспечение ограничений и проверок на уровне приложения, а не на уровне базы данных; вот почему столбцы по умолчанию в Rails могут содержать NULL значения, даже если у вас есть validates_presence_of или что-то в этом роде. По мнению разработчиков Rails, такие ограничения должны обрабатываться приложением, не базой данных.

person mipadi    schedule 29.05.2009
comment
По мнению разработчиков Rails, такие ограничения должны обрабатываться приложением, а не базой данных. Но почему это их точка зрения? Разве это не то, что база данных делает лучше всего? - person eggdrop; 30.05.2009
comment
Я полагаю, что во многом это связано с переносимостью. Rails поддерживает несколько бэкэндов БД, не все из которых обязательно поддерживают один и тот же набор функций. Однако Rails пытается абстрагироваться от БД и поэтому во многих случаях выбирает поддержку только наименьшего общего знаменателя. Полагаю, могут быть и другие причины; Основные разработчики Rails - заядлые блоггеры, поэтому я полагаю, что вы можете найти более ясный ответ, если прочитаете их блоги. - person mipadi; 30.05.2009
comment
Реляционные ограничения могут быть определены для всех основных баз данных. Ваше объяснение не имеет смысла. - person eggdrop; 30.05.2009
comment
Я вырос до того, что не использую ограничения FK. В большинстве случаев они вызывают гораздо больше боли, чем облегчают (есть исключения). Кроме того, если вам нужно масштабировать (например, сегментировать или использовать что-то вроде большой таблицы), ограничения FK все равно отсутствуют. - person jshen; 30.05.2009
comment
@eggdrop: SQLite не поддерживает ограничения внешнего ключа. Механизм хранения по умолчанию MySQL (MyISAM) не поддерживает ограничения внешнего ключа. Эти две базы данных SQL довольно популярны, особенно при развертывании с открытым исходным кодом. - person Bill Karwin; 30.05.2009
comment
@ Билл Карвин - Извинения перед mipadi. Я исправился. В этом свете объяснение Мипади имеет смысл. - person eggdrop; 30.05.2009
comment
Никто не использует SQLite для производственных веб-приложений. А MyISAM используется недостаточно по сравнению с InnoDB, потому что MyISAM также не поддерживает транзакции. - person Ian Terrell; 30.05.2009
comment
Никто не использует SQLite для производственных веб-приложений. - ›justindriscoll.us/2008/03/ В вашем ответе ниже вы говорите, что для многих веб-приложений, вероятно, нормально, если небольшой (возможно, крошечный) процент записей содержит недопустимые данные. Точно так же многие веб-приложения с низким трафиком (то есть большое количество) столкнутся с очень небольшим количеством проблем с одновременной записью из-за использования sqlite в производстве. - person eggdrop; 30.05.2009
comment
Это старое обсуждение, но для записи: SQLite добавил поддержку внешних ключей в версии 3.6.19 (2009-10-14) и MySQL изменил свой механизм хранения по умолчанию на InnoDB в версии 5.5.5 (06.07.2010). - person Bill Karwin; 07.09.2013