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