Що викликає помилку ActiveRecord :: ReadOnlyRecord?


203

З цього випливає це попереднє запитання, на яке було дано відповідь. Я фактично виявив, що можу видалити з'єднання з цього запиту, тому тепер робочий запит є

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

і відповідні моделі (tableau - це картки гравців на столі)

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 в рейки api, і це не говорить набагато більше, ніж опис.

Відповіді:


283

Рейки 2.3.3 і нижче

З ActiveRecord CHANGELOG(v1.12.0, 16 жовтня 2005 р.) :

Введіть записи лише для читання. Якщо ви телефонуєте object.readonly! тоді він позначатиме об'єкт лише для читання і піднімає ReadOnlyRecord, якщо ви викликаєте object.save. object.readonly? повідомляє, чи об’єкт лише для читання. Передача: readonly => true для будь-якого методу пошуку, буде позначати повернуті записи як лише для читання. Опція: приєднується тепер означає: лише читати, тому якщо ви скористаєтесь цією опцією, збереження тієї ж записи тепер не вдасться. Використовуйте find_by_sql для обходу.

Використання find_by_sqlнасправді не є альтернативою, оскільки воно повертає необроблені дані рядків / стовпців, ні ActiveRecords. У вас є два варіанти:

  1. Примушуйте змінну екземпляра @readonlyдо помилки в записі (хак)
  2. Використовуйте :include => :cardзамість:join => :card

Рейки 2.3.4 і вище

Після 10 вересня 2012 року більшість із зазначеного більше не відповідає дійсності:

  • використання Record.find_by_sql - це життєздатний варіант
  • :readonly => trueавтоматично робиться висновок лише в тому випадку, якщо :joinsбуло вказано без явного :select або явного (або успадкованого пошуку) області :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.)

the: include корисно зменшити кількість виконаних запитів, я про це не знав; але я спробував це виправити, змінивши асоціацію Tableau / Deckcards на has_many: through, і тепер я отримую msg "не вдалося знайти асоціацію"; Можливо, мені доведеться розмістити ще одне запитання щодо цього
user26270

@codeman, так,:: include зменшить кількість запитів і приведе включену таблицю до вашої області умов (свого роду неявне об'єднання без Rails, що позначає ваші записи як лише для читання, що це робиться, як тільки вона нюхає що-небудь SQL -ish у своїй знаходженні, включаючи: join /: виберіть пункти IIRC
vladr

Для роботи 'has_many: a, through =>: b' також потрібно оголосити B асоціацію, наприклад 'has_many: b; has_many: a,: through =>: b ', я сподіваюся, що це ваш випадок?
vladr

6
Це може змінитися в останніх випусках, але ви можете просто додати: readonly => false як частину атрибутів методу пошуку.
Аарон Рустад

1
Ця відповідь також застосовна, якщо у вас є асоціація has_and_belongs_to_many зі спеціальним користувачем: join_table.
Лі

172

Або в Rails 3 ви можете скористатися методом readonly (замінити "..." своїми умовами):

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

1
Гммм ... Я подивився на обидва ці Railscasts на Asciicasts, і жодна з readonlyфункцій не згадує .
Purplejacket

45

Це могло змінитися в нещодавньому випуску Rails, але відповідний спосіб вирішити цю проблему - додати : readonly => false до параметрів пошуку.


3
Я не вірю, що це так, принаймні 2.3.4
Оллі

2
Він як і раніше працює з Rails 3.0.10, ось приклад з мого власного коду, який отримує область, яка має: приєднатися до Fundraiser.donatable.readonly (false)
Хоуен,

16

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 + '.*')
andorov

1
Це був геніальний бросон. Дякую.
Поїздка

Це може спрацювати, але складніше, ніж використовуватиreadonly(false)
Келвін

5

Замість find_by_sql ви можете вказати: виберіть на пошуку і все знову щасливе ...

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
Накладення мавп неміцне - дуже легко ламається новими версіями рейок. Однозначно недоцільно, враховуючи інші рішення.
Келвін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.