Что вызывает эту ошибку ActiveRecord :: ReadOnlyRecord?

205

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

start_cards = DeckCard.find :all, :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]  

Это работает. Однако, когда я пытаюсь переместить эти DeckCards в другую ассоциацию, я получаю ошибку ActiveRecord:: ReadOnlyRecord.

Здесь код

for player in @game.players 
  player.tableau = Tableau.new
  start_card = start_cards.pop 
  start_card.draw_pile = false
  player.tableau.deck_cards << start_card  # the error occurs on this line
end

и соответствующие модели (таблица - карты игроков на столе)

class Player < ActiveRecord::Base
  belongs_to :game
  belongs_to :user
  has_one :hand
  has_one :tableau
end

class Tableau < ActiveRecord::Base
  belongs_to :player
  has_many :deck_cards
end  

class DeckCard < ActiveRecord::Base
  belongs_to :card
  belongs_to :deck  
end

Я делаю подобное действие сразу после этого кода, добавляя DeckCards к руке игроков, и этот код работает нормально. Я задавался вопросом, нужен ли мне belongs_to :tableau в модели DeckCard, но он отлично подходит для добавления в руку игрока. У меня есть столбцы tableau_id и hand_id в таблице DeckCard.

Я просмотрел ReadOnlyRecord в rails api, и он не говорит много за описанием.

Теги:
join
activerecord
associations

6 ответов

271
Лучший ответ

Из ActiveRecord CHANGELOG (v1.12.0, 16 октября 2005 г.):

Введите записи только для чтения. Если вы вызываете object.readonly! то это будет отметьте объект как доступный только для чтения и поднимите ReadOnlyRecord, если вы позвоните object.save. object.readonly? отчеты является ли объект доступным только для чтения. Передача: readonly = > true для любого метод поиска будет отмечен как возвращенный записи как только для чтения. The: объединяет теперь подразумевает: readonly, так что если вы используете этот параметр, сохраняя тот же запись будет терпеть неудачу. Используйте find_by_sql для работы.

Использование find_by_sql на самом деле не является альтернативой, поскольку оно возвращает необработанные данные строки/столбца, а не ActiveRecords. У вас есть два варианта:

  • Принудите переменную экземпляра @readonly false в записи (взломать)
  • Используйте :include => :card вместо :join => :card

Sep 2010 UPDATE

Большая часть из вышеизложенного больше не выполняется. Таким образом, в Rails 2.3.4 и 3.0.0:

  • с помощью Record.find_by_sql есть жизнеспособный вариант
  • :readonly => true автоматически выводится только, если :joins был указан без явным :select и явным (или finder- scope-inherited) :readonly (см. реализацию set_readonly_option! в active_record/base.rb для Rails 2.3.4 или реализацию to_a в active_record/relation.rb и custom_join_sql в active_record/relation/query_methods.rb для Rails 3.0. 0)
  • однако :readonly => true всегда автоматически выводится в has_and_belongs_to_many, если в таблице объединений содержится больше двух столбцов внешних ключей и :joins была указана без явного :select (то есть введенные пользователем значения :readonly игнорируется - см. finding_with_ambiguous_select? в active_record/associations/has_and_belongs_to_many_association.rb.)
  • в заключение, если не иметь дело со специальной таблицей соединений и has_and_belongs_to_many, тогда ответ @aaronrustad справедливо применяется в Rails 2.3.4 и 3.0.0.
  • не используйте :includes, если вы хотите достичь INNER JOIN (:includes подразумевает LEFT OUTER JOIN, который менее избирателен и менее эффективен, чем INNER JOIN.)
  • 0
    : include помогает уменьшить количество выполненных запросов, я не знал об этом; но я попытался исправить это, изменив ассоциацию Tableau / Deckcards на has_many: through, и теперь я получаю сообщение «не удалось найти ассоциацию»; Возможно, мне придется отправить еще один вопрос для этого
  • 0
    @codeman, да,: include сократит количество запросов и перенесет включенную таблицу в область ваших условий (своего рода неявное объединение без Rails, помечающее ваши записи как доступные только для чтения, что и происходит, как только что-то прослушивает SQL). -ниш в вашей находке, в том числе: присоединиться /: выбрать пункты IIRC
Показать ещё 6 комментариев
165

Или в Rails 3 вы можете использовать метод readonly (замените "..." вашими условиями):

( Deck.joins(:card) & Card.where('...') ).readonly(false)
  • 0
    работал гладко для меня.
  • 1
    Хммм ... Я посмотрел оба этих Railscasts на Asciicasts, и ни один не упоминает readonly для readonly функции.
Показать ещё 2 комментария
45

Это может измениться в недавней версии Rails, но подходящий способ решить эту проблему - добавить : readonly = > false в параметры поиска.

  • 3
    Я не верю, что это так, по крайней мере 2.3.4
  • 2
    Он по-прежнему работает с Rails 3.0.10, вот пример из моего собственного кода, извлекающего область видимости, которая имеет: join Fundraiser.donatable.readonly (false)
Показать ещё 2 комментария
15

select ('*'), похоже, исправляет это в Rails 3.2:

> Contact.select('*').joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> false

Просто, чтобы проверить, опускание select ('*') действительно производит запись только для чтения:

> Contact.joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> true

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

  • 4
    То же самое в Rails 4. В качестве альтернативы вы можете сделать select(quoted_table_name + '.*')
  • 1
    Это был блестящий бронсон. Спасибо.
Показать ещё 1 комментарий
5

Вместо find_by_sql вы можете указать a: select на finder и все снова счастливое...

start_cards = DeckCard.find :all, :select => 'deck_cards.*', :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]

3

Чтобы отключить его...

module DeactivateImplicitReadonly
  def custom_join_sql(*args)
    result = super
    @implicit_readonly = false
    result
  end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly
  • 3
    Обезьяны-патчи хрупки - очень легко ломаются новыми версиями рельсов. Определенно не рекомендуется, если есть другие решения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню