이 ActiveRecord :: ReadOnlyRecord 오류의 원인은 무엇입니까?


203

다음은 이전 질문에 대한 답변입니다. 실제로 해당 쿼리에서 조인을 제거 할 수 있음을 발견 했으므로 이제 작동중인 쿼리는

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

이것은 작동하는 것 같습니다. 그러나 이러한 DeckCard를 다른 연결로 이동하려고하면 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 :tableauDeckCard 모델에 필요한지 궁금 했지만 플레이어의 손에 추가하는 것이 좋습니다. DeckCard 테이블에 tableau_idhand_id열 이 있습니다 .

Rails API에서 ReadOnlyRecord를 찾았으며 설명을 넘어서는 말은 많지 않습니다.

답변:


283

레일 2.3.3 이하

로부터 액티브 CHANGELOG(v1.12.0 년 10 월 16 일, 2005) :

읽기 전용 레코드를 소개하십시오. object.readonly를 호출하면! 그런 다음 객체를 읽기 전용으로 표시하고 object.save를 호출하면 ReadOnlyRecord를 발생시킵니다. object.readonly? 객체가 읽기 전용인지 여부를보고합니다. 파인더 메소드에 : readonly => true를 전달하면 리턴 된 레코드가 읽기 전용으로 표시됩니다. : joins 옵션은 이제 : readonly를 의미하므로이 옵션을 사용하면 동일한 레코드를 저장할 수 없습니다. find_by_sql을 사용하여 해결하십시오.

를 사용 find_by_sql하는 것은 실제로 원시 행 / 열 데이터를 반환하므로 대안이 아닙니다 ActiveRecords. 두 가지 옵션이 있습니다.

  1. @readonly레코드에서 인스턴스 변수 를 false로 설정 (해킹)
  2. :include => :card대신에 사용:join => :card

레일 2.3.4 이상

위의 대부분은 2012 년 9 월 10 일 이후 더 이상 적용되지 않습니다.

  • 사용하는 Record.find_by_sql 것입니다 가능한 옵션
  • :readonly => true자동으로 유추되는 경우에만 경우 :joins지정 하지 않고 명시 적으로 :select (상속 범위 파인더 나) 명시 적 :readonly옵션은 (의 구현 볼 수 set_readonly_option!있는 active_record/base.rb레일 2.3.4에 대한, 또는의 구현 to_a에서 active_record/relation.rb와의 custom_join_sql에서 active_record/relation/query_methods.rb레일 3.0.0를 들어)
  • 그러나 조인 테이블에 두 개 이상의 외래 키 열이 있고 명시 적으로 지정되지 않은 경우 (예 : 사용자 제공 값은 무시 됩니다 . 참조 :readonly => true) 항상 자동으로 추론됩니다 .has_and_belongs_to_many:joins:select:readonlyfinding_with_ambiguous_select?active_record/associations/has_and_belongs_to_many_association.rb
  • 결론적으로, 특별한 취급하지 않는 테이블에 가입하고 has_and_belongs_to_many다음 @aaronrustad의 대답은 레일 2.3.4과 3.0.0에서 잘 적용됩니다.
  • 를 달성하려는 경우 사용 하지 마십시오 (을 의미하며 이는 선택보다 덜 효율적 입니다.):includesINNER JOIN:includesLEFT OUTER JOININNER JOIN

: include는 수행 된 쿼리 수를 줄이는 데 도움이됩니다. 하지만 Tableau / Deckcards 연결을 has_many로 변경하여 문제를 해결하려고했습니다. 이제 통과 할 수 없습니다. 이에 대한 또 다른 질문을 게시해야 할 수도 있습니다
user26270

네, @codeman의 : 쿼리 수를 줄일 포함 하고 암시 레일은 즉시 아무것도의 SQL을 막으로 수행 읽기 전용으로 레코드를 표시하지 않고 가입의 (당신의 조건 범위에 일종의 포함 된 테이블을 가져올 것이다 : join / : select 절을 포함하여 찾기에서
-ish

'has_many : a, through => : b'가 작동하려면 B 연관도 선언해야합니다 (예 : 'has_many : b; has_many : a, : through => : b ', 이것이 당신의 사건이기를 바랍니다.
vladr

6
최근 릴리스에서는 변경되었을 수 있지만 find 메소드 속성의 일부로 간단히 : readonly => false를 추가 할 수 있습니다.
Aaron Rustad

1
이 답변은 사용자 정의 : join_table이 지정된 has_and_belongs_to_many 연관이있는 경우에도 적용 가능합니다.

172

또는 Rails 3에서는 읽기 전용 방법을 사용할 수 있습니다 ( "..."을 조건으로 대체).

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

1
흠 ... Asciicast에서 Railscast를 모두 찾아 보았지만 그 readonly기능은 언급하지 않았습니다 .
Purplejacket

45

최근 Rails 릴리스에서 변경되었을 수 있지만이 문제를 해결하는 적절한 방법은 : readonly => false 를 찾기 옵션 에 추가 하는 것입니다.


3
나는 이것이 2.3.4 이상인 것을 믿지 않는다
Olly

2
여전히 Rails 3.0.10에서 작동합니다. 다음은 : join Fundraiser.donatable.readonly (false)
Houen

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
그것은 훌륭한 브론슨이었습니다. 감사합니다.
Trip

이것은 작동 할 수도 있지만 사용하는 것보다 더 복잡합니다.readonly(false)
Kelvin

5

find_by_sql 대신 파인더에서 : select를 지정하면 모든 것이 행복합니다 ...

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
원숭이 패치는 깨지기 쉬우 며 새로운 레일 버전으로 인해 쉽게 깨집니다. 다른 해결책이 있다면 분명히 바람직하지 않습니다.
Kelvin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.