#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
orApplicationModel
(rails 5)?4. a.first_b возвращает ту же ошибку ‘undefined method: except’, что и выше. Я создал подкласс ActiveRecord::Base. (Я кратко попробовал в rails 5.0.0.1 и получил ту же ошибку, но я не думал менять суперкласс на ApplicationModel)
5. Я ничего не могу сказать без трассировки стека. Однако я предполагаю, что это, скорее всего, проблема X amp; Y. Какова бы ни была реальная цель, вероятно, есть лучший способ сделать это.