ChatGPT解决这个技术问题 Extra ChatGPT

What is causing this ActiveRecord::ReadOnlyRecord error?

This follows this prior question, which was answered. I actually discovered I could remove a join from that query, so now the working query is

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

This appears to work. However, when I try to move these DeckCards into another association, I get the ActiveRecord::ReadOnlyRecord error.

Here's the code

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

and the relevant Models (tableau are the players cards on the table)

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

I am doing a similar action just after this code, adding DeckCards to the players hand, and that code is working fine. I wondered if I needed belongs_to :tableau in the DeckCard Model, but it works fine for the adding to player's hand. I do have a tableau_id and hand_id columns in the DeckCard table.

I looked up ReadOnlyRecord in the rails api, and it doesn't say much beyond the description.


m
morhook

Rails 2.3.3 and lower

From the ActiveRecord CHANGELOG(v1.12.0, October 16th, 2005):

Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.

Using find_by_sql is not really an alternative as it returns raw row/column data, not ActiveRecords. You have two options:

Force the instance variable @readonly to false in the record (hack) Use :include => :card instead of :join => :card

Rails 2.3.4 and above

Most of the above no longer holds true, after September 10 2012:

using Record.find_by_sql is a viable option

:readonly => true is automatically inferred only if :joins was specified without an explicit :select nor an explicit (or finder-scope-inherited) :readonly option (see the implementation of set_readonly_option! in active_record/base.rb for Rails 2.3.4, or the implementation of to_a in active_record/relation.rb and of custom_join_sql in active_record/relation/query_methods.rb for Rails 3.0.0)

however, :readonly => true is always automatically inferred in has_and_belongs_to_many if the join table has more than the two foreign keys columns and :joins was specified without an explicit :select (i.e. user-supplied :readonly values are ignored -- see finding_with_ambiguous_select? in active_record/associations/has_and_belongs_to_many_association.rb.)

in conclusion, unless dealing with a special join table and has_and_belongs_to_many, then @aaronrustad's answer applies just fine in Rails 2.3.4 and 3.0.0.

do not use :includes if you want to achieve an INNER JOIN (:includes implies a LEFT OUTER JOIN, which is less selective and less efficient than INNER JOIN.)


the :include is helpful in reducing the # of queries done, I didn't know about that; but I tried to fix it by changing the Tableau/Deckcards association to a has_many: through, and now I'm getting a 'could not find association' msg; I may have to post another question for that
@codeman, yes, the :include will reduce the number of queries and will bring the included table into your condition scope (a sort of implicit join without Rails marking your records as read-only, which it does as soon as it sniffs anything SQL-ish in your find, including :join/:select clauses IIRC
For 'has_many :a, through => :b' to work, the B association must be declared as well, e.g. 'has_many :b; has_many :a, :through => :b', I hope this is your case?
This might have changed in recent releases, but you can simply add :readonly => false as part of the find method attributes.
This answer is also applicable if you have a has_and_belongs_to_many association with a custom :join_table specified.
b
balexand

Or in Rails 3 you can use the readonly method (replace "..." with your conditions):

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

Hmmm ... I looked up both of those Railscasts on Asciicasts, and neither mentions the readonly function.
A
Aaron Rustad

This might have changed in recent release of Rails, but the appropriate way to solve this problem is to add :readonly => false to the find options.


I don't believe this is the case, with 2.3.4 at least
It still works with Rails 3.0.10, here's an example from my own code fetching a scope that has a :join Fundraiser.donatable.readonly(false)
b
bronson

select('*') seems to fix this in Rails 3.2:

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

Just to verify, omitting select('*') does produce a readonly record:

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

Can't say I understand the rationale but at least it's a quick and clean workaround.


Same thing in Rails 4. Alternatively you can do select(quoted_table_name + '.*')
That was brilliant bronson. Thank you.
This may work, but is more complicated than using readonly(false)
佚名

Instead of find_by_sql, you can specify a :select on the finder and everything's happy again...

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


g
grosser

To deactivate it...

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

Monkey-patching is fragile - very easily broken by new rails versions. Definitely inadvisable given there are other solutions.

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now