Правильный способ предотвратить ActiveRecord :: ReadOnlyRecord? - PullRequest
30 голосов
/ 06 октября 2011

Я сейчас использую Rails 2.3.9. Я понимаю, что указание параметра :joins в запросе без явного :select автоматически делает любые записи, которые возвращаются только для чтения. У меня есть ситуация, когда я хотел бы обновить записи, и, хотя я читал о различных способах приближения к ней, мне было интересно, какой путь является предпочтительным или «правильным».

В частности, моя ситуация такова, что у меня есть следующая модель User с именованной областью действия active, которая выполняет JOIN с таблицей subscriptions:

class User < ActiveRecord::Base
  has_one :subscription

  named_scope :active, :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end

Когда я вызываю User.active.all, все возвращаемые пользовательские записи доступны только для чтения, поэтому, если, например, я вызову update_attributes! для пользователя, ActiveRecord::ReadOnlyRecord будет вызвано.

Благодаря чтению различных источников, кажется, что популярный способ обойти это, добавив :readonly => false к запросу. Однако мне было интересно следующее:

  • Это безопасно? Я понимаю причину, по которой Rails в первую очередь устанавливает его только для чтения, потому что, согласно документации Rails , "они будут иметь атрибуты, которые не соответствуют столбцам таблицы. " Однако в SQL-запросе, сгенерированном по этому вызову, в любом случае используется SELECT `users`.*, который выглядит безопасным, , так что же в первую очередь пытается защитить Rails? Может показаться, что Rails должен защищать против случая, когда :select фактически явно указан, что является обратным фактическому поведению, , поэтому я неправильно понимаю цель автоматической установки флага только для чтения на :joins?
  • Похоже ли это на хак? Неправильно, что определение именованной области должно явно касаться установки :readonly => false. Я также боюсь побочных эффектов, если именованная область связана с другими именованными областями. Если я попытаюсь указать его вне области действия (например, с помощью User.active.scoped(:readonly => false) или User.scoped(:readonly => false).active), он не будет работать.

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

Наконец, я также мог бы снова получить запрос, используя идентификаторы записей, вызвав User.find_all_by_id(User.active.map(&:id)), но я считаю, что это скорее обходной путь, нежели возможное решение, поскольку он генерирует дополнительный запрос SQL.

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

Заранее спасибо!

Ответы [ 4 ]

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

Я считаю, что в этом случае было бы принято и приемлемо использовать :include вместо :join.Я думаю, что :join используется только в редких специализированных случаях, тогда как :include довольно распространено.

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

User.active.some_further_limiting_scope(:with_an_argument)
  #or
User.active.find(:all, :conditions => {:etc => 'etc'})

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

readonly_users = User.active
# insert some other code that picks out a particular user to update
User.find(readonly_users[@index].id).update_attributes(:etc => 'etc')

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

#no need to do find_all_by_id in this case. A simple find() is sufficient.
writable_users_without_subscriptions = User.find(Users.active.map(&:id))

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

3 голосов
/ 19 июня 2012

Я думаю, что лучшим решением является использование .join, как вы уже сделали, и отдельное выполнение find ()

Одно из важных отличий использования: include заключается в том, что он использует внешнее соединение, тогда как: join использует внутреннее соединение!Поэтому использование: include может решить проблему только для чтения, но результат может быть неправильным!

2 голосов
/ 07 сентября 2013

Я столкнулся с этой же проблемой, и мне было неудобно использовать :readonly => false

. В результате я сделал явный выбор, а именно :select => 'users.*', и почувствовал, что это похоже на взлом.

Вы можете сделать следующее:

class User < ActiveRecord::Base
  has_one :subscription

  named_scope :active, :select => 'users.*', :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
0 голосов
/ 25 июля 2012

Относительно вашего подвопроса: , поэтому я неправильно понимаю цель автоматической установки флага только для чтения на: joins?

Я полагаю, что ответ: С объединениямизапрос, вы получаете одну запись с атрибутами таблицы User + Subscription.Если вы попытаетесь обновить один из атрибутов (скажем, «subscription_num») в таблице Subscription вместо таблицы User, оператор обновления таблицы User не сможет найти subscription_num и будет аварийно завершать работу.Таким образом, области объединения по умолчанию доступны только для чтения, чтобы предотвратить это.

Ссылка: 1) http://blog.ethanvizitei.com/2009/05/joins-and-namedscopes-in-activerecord.html

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