Отношения ActiveRecord has_many забывают изменения дочерних элементов?

#ruby-on-rails #activerecord

#ruby-on-rails #activerecord

Вопрос:

У меня есть экземпляр типа A, который имеет множество Bs. Когда A.foo = value вызывается метод, я на самом деле хочу написать метод, который делегирует этому foo= вызову первый из Bs A.

 class A < ActiveRecord::Base
  has_many :bs, autosave: true

  def foo
    bs.first.foo
  end
  def foo=(val)
    bs.first.foo = val
  end
end

class B < ActiveRecord::Base
  belongs_to A
end


rails generate model A 
rails generate model B a:references foo:string


2.3.0 :001 > a = A.create!
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "as" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2016-10-08 18:03:18.255107"], ["updated_at", "2016-10-08 18:03:18.255107"]]
   (7.8ms)  commit transaction
 => #<A id: 1, created_at: "2016-10-08 18:03:18", updated_at: "2016-10-08 18:03:18"> 
  

Создайте A и вызовите его a .

 2.3.0 :002 > b = B.create!(a: a, foo: "initial")
   (0.4ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "bs" ("a_id", "foo", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["a_id", 1], ["foo", "initial"], ["created_at", "2016-10-08 18:03:40.658035"], ["updated_at", "2016-10-08 18:03:40.658035"]]
   (8.3ms)  commit transaction
 => #<B id: 1, a_id: 1, foo: "initial", created_at: "2016-10-08 18:03:40", updated_at: "2016-10-08 18:03:40"> 
  

Создайте B и вызовите его b . Сделайте его дочерним элементом A. Установите для его свойства foo значение «начальный».

 2.3.0 :003 > a.reload.foo
  A Load (0.2ms)  SELECT  "as".* FROM "as" WHERE "as"."id" = ? LIMIT 1  [["id", 1]]
  B Load (0.2ms)  SELECT  "bs".* FROM "bs" WHERE "bs"."a_id" = ?  ORDER BY "bs"."id" ASC LIMIT 1  [["a_id", 1]]
 => "initial" 
  

Проверьте, что a это новый дочерний foo элемент: да. Как и ожидалось.

 2.3.0 :004 > a.foo = "set"
  B Load (0.1ms)  SELECT  "bs".* FROM "bs" WHERE "bs"."a_id" = ?  ORDER BY "bs"."id" ASC LIMIT 1  [["a_id", 1]]
 => "set" 
2.3.0 :005 > a.foo
  B Load (0.4ms)  SELECT  "bs".* FROM "bs" WHERE "bs"."a_id" = ?  ORDER BY "bs"."id" ASC LIMIT 1  [["a_id", 1]]
 => "initial" 
  

Что такое? Я только что позвонил a.foo = "set" . Теперь, когда я a.foo снова вызываю, чтобы прочитать значение обратно, я получаю "initial" ? Это не так, как это работает для has_one отношений. Почему ActiveRecord каждый раз перезагружается из базы данных, а не кэширует свои запросы?

В конечном счете, я намерен вызвать a.save! и автоматически сохранить его до b. Но это невозможно, если отношения получают амнезию о каждом ожидающем изменении. Что здесь происходит ?!

Комментарии:

1. Rails не кэширует запросы при выполнении команд в консоли.

2. Полезно знать. Но, насколько я понимаю, модель AR в любом случае должна кэшировать первый запрос: a.bs должен быть лениво вычисляемый запрос к базе данных, который извлекается при первом доступе, а затем сохраняется в виде массива до сохранения! вызывается для его родительского элемента. Или я так думал.

3. Ассоциации Has_one и belongs_to кэшируют свои результаты: guides.rubyonrails.org /… : 4.1.1.1 и 4.2.1.1 «Если связанный объект уже был извлечен из базы данных для этого объекта, будет возвращена кэшированная версия». В документации ничего не говорится об этом кэшировании для метода сбора в 4.3.1.1. Он просто этого не делает. Он запрашивает каждый раз.

Ответ №1:

Установите has_one связь между A и B и делегируйте:foo has_one ассоциации.

 class A
  has_many :bs
  has_one :first_b, -> { first },
    class_name: 'B'
  delegate :foo, to: :first_b
end
  

Чтобы избежать запроса для B, вы можете использовать .joins , includes или eager_load .

Комментарии:

1. хммм, когда я запускаю это, я получаю «NoMethodError: неопределенный метод `except’ для #<B:0x000000037915f0>» Это происходит не от вызова делегата, поэтому оно должно исходить от has_one?

2. Что произойдет, если вы запустите a.first_b ?

3. Вы не забыли настроить его для наследования от ActiveRecord::Base or ApplicationModel (rails 5)?

4. a.first_b возвращает ту же ошибку ‘undefined method: except’, что и выше. Я создал подкласс ActiveRecord::Base. (Я кратко попробовал в rails 5.0.0.1 и получил ту же ошибку, но я не думал менять суперкласс на ApplicationModel)

5. Я ничего не могу сказать без трассировки стека. Однако я предполагаю, что это, скорее всего, проблема X amp; Y. Какова бы ни была реальная цель, вероятно, есть лучший способ сделать это.