I have three ActiveRecord models: Partner, MembershipChannel (which is an STI model, inheriting from Channel) and ChannelMembership (I was not responsible for naming these models…)
When I load a ChannelMembership through the Partner association, I sometimes(!) end up with a readonly record. This is in Rails 3.0.9. The same code did not behave this way in 2.3.11.
> p = Partner.first
> p.channel_memberships.map(&:readonly?)
# => [false, false, false, false, false, false]
> p.reload.channel_memberships.limit(1).first.readonly?
# => false
> p.reload.channel_memberships.first.readonly?
# => true
Why is readonly?
true when first
is called on the association, but not on the relation from limit
?
I understand that readonly
is triggered if I use SQL fragments when finding a record, but this isn't the case here. It is just a plain has_many through association. The only complicating matter is that it joins on an STI model. What's more, looking at the generated SQL from the last two examples, they are identical!
I can get the behaviour I want by specifying :readonly => false
on the association, but I want to understand what is going on.
There are no default scopes on Channel, MembershipChannel or ChannelMembership. Here is the association declaration on Partner:
class Partner
has_many :membership_channels
has_many :channel_memberships, :through => :membership_channels
end
Here is the generated SQL from my logs:
Partner Load (0.4ms) SELECT "partners".* FROM "partners" LIMIT 1
ChannelMembership Load (0.7ms) SELECT "channel_memberships".* FROM "channel_memberships" INNER JOIN "channels" ON "channel_memberships".channel_id = "channels".id WHERE (("channels".partner_id = 2) AND (("channels"."type" = 'MembershipChannel')))
Partner Load (0.5ms) SELECT "partners".* FROM "partners" WHERE "partners"."id" = 2 LIMIT 1
ChannelMembership Load (1.0ms) SELECT "channel_memberships".* FROM "channel_memberships" INNER JOIN "channels" ON "channel_memberships".channel_id = "channels".id WHERE (("channels".partner_id = 2) AND (("channels"."type" = 'MembershipChannel'))) LIMIT 1
Partner Load (0.4ms) SELECT "partners".* FROM "partners" WHERE "partners"."id" = 2 LIMIT 1
ChannelMembership Load (0.6ms) SELECT "channel_memberships".* FROM "channel_memberships" INNER JOIN "channels" ON "channel_memberships".channel_id = "channels".id WHERE (("channels".partner_id = 2) AND (("channels"."type" = 'MembershipChannel'))) LIMIT 1
I was able to reproduce your problem through a basic has_many :through association and am also as to what's causing it.
From what I can tell, it only happens when the reload method is called on the original object. I'm not sure if this is because of anything that reload's doing specifically, or perhaps because certain attribute flags are being reset?
My second theory is that it has something to do with the fact that
p.reload.channel_memberships.limit(1)
returns an ActiveRecord::Relation through which you obtain your first ChannelMembership, and
p.reload.channel_memberships.first
loads it directly from the association. Perhaps some combination of reload resetting certain cached items (I don't know the AR source) is flagging the association as read only. When you apply the limit(1) scope on it, it may be resetting these in a new relation, and working as you'd expect.
I'd poke around ActiveRecord::Persistence / Associations a bit more for the full answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With