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.
-
From the ActiveRecord
CHANGELOG
: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, notActiveRecords
. You have two options:- Force the instance variable
@readonly
to false in the record (hack) - Use
:include => :card
instead of:join => :card
Cheers, V.
Sep 2010 UPDATE
Most of the above no longer holds true. Thus, in Rails 2.3.4 and 3.0.0:
- 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 ofset_readonly_option!
inactive_record/base.rb
for Rails 2.3.4, or the implementation ofto_a
inactive_record/relation.rb
and ofcustom_join_sql
inactive_record/relation/query_methods.rb
for Rails 3.0.0)- however,
:readonly => true
is always automatically inferred inhas_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 -- seefinding_with_ambiguous_select?
inactive_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@BigCanOfTuna
's answer applies just fine in Rails 2.3.4 and 3.0.0. - do not use
:includes
if you want to achieve anINNER JOIN
(:includes
implies aLEFT OUTER JOIN
, which is less selective and less efficient thanINNER 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 thatvladr : @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 IIRCvladr : 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?: yes, I didn't have the 'has_many :b' association; once I got that it all worked great - Thanks!BigCanOfTuna : This might have changed in recent releases, but you can simply add :readonly => false as part of the find method attributes.Lee : This answer is also applicable if you have a has_and_belongs_to_many association with a custom :join_table specified. - Force the instance variable
-
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]
-
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.
Olly : I don't believe this is the case, with 2.3.4 at least -
Or in Rails 3 you can use the readonly method (replace "..." with your conditions):
( Deck.joins(:card) & Card.where('...') ).readonly(false)
See http://m.onkey.org/2010/1/22/active-record-query-interface for more info.
There are also some good Railscasts on this. See "Active Record Queries in Rails 3" and "Advanced Queries in Rails 3". I can't post the links here due to the spam filter.
0 comments:
Post a Comment