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

Это следует за 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, и он не говорит много за описанием.

Ответ 1

Из 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.)

Ответ 2

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

( Deck.joins(:card) & Card.where('...') ).readonly(false)

Ответ 3

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

Ответ 4

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

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

Ответ 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]

Ответ 6

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

module DeactivateImplicitReadonly
  def custom_join_sql(*args)
    result = super
    @implicit_readonly = false
    result
  end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly