Почему мой has_many через связанную запись (иногда) только для чтения? - PullRequest
5 голосов
/ 19 августа 2011

У меня есть три модели ActiveRecord: Partner, MembershipChannel (который является моделью STI, унаследованной от Channel) и ChannelMembership (я не отвечал за наименование этих моделей…)

Когда я загружаю ChannelMembership через партнерскую ассоциацию, у меня иногда (!) Получается запись только для чтения. Это в Rails 3.0.9. Такой же код не вел себя таким образом в 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

Почему readonly? истинно, когда first вызывается для ассоциации, но не для отношения из limit?

Я понимаю, что readonly срабатывает, если я использую фрагменты SQL при поиске записи, но здесь это не так. Это просто обычный has_many через ассоциацию. Единственное осложняющее обстоятельство - это то, что он включается в модель ИППП. Более того, если посмотреть на сгенерированный SQL из двух последних примеров, они идентичны!

Я могу получить желаемое поведение, указав :readonly => false в ассоциации, но я хочу понять, что происходит.

Нет никаких областей по умолчанию для Channel, MembershipChannel или ChannelMembership. Вот заявление ассоциации на Партнера:

class Partner
  has_many :membership_channels
  has_many :channel_memberships, :through => :membership_channels
end

Вот сгенерированный SQL из моих журналов:

  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

1 Ответ

2 голосов
/ 26 августа 2011

Мне удалось воспроизвести вашу проблему с помощью базового has_many: через ассоциацию, а также относительно того, что ее вызывает.

Из того, что я могу сказать, это происходит только тогда, когда метод reload вызывается для исходного объекта. Я не уверен, что это из-за того, что перезагрузка делает что-то определенное, или, может быть, из-за сброса флагов некоторых атрибутов?

Моя вторая теория заключается в том, что это как-то связано с тем, что

p.reload.channel_memberships.limit(1)

возвращает ActiveRecord :: Relation, с помощью которого вы получаете ваш первый ChannelMembership, и

p.reload.channel_memberships.first

загружает его напрямую из ассоциации. Возможно, некоторая комбинация перезагрузки, сброса определенных кэшированных элементов (я не знаю источника AR) помечает ассоциацию как доступную только для чтения. Когда вы применяете к нему предел limit (1), он может сбрасывать их в новом отношении и работать так, как вы ожидаете.

Я бы побольше покопался в ActiveRecord :: Персистентность / Ассоциации для полного ответа.

...