Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Sergei Malykh
Sergei Malykh

Posted on

Нюансы использования rails-консоли

Все мы постоянно пользуемся rails-консолью. Она незаменима при отладке в development. Но и в боевом окружении временами тоже что-то требуется посмотреть. Недавно наткнулся на один неочевидный нюанс, который заставил поразбираться.

Есть некий код, который используетActiveRecord::Associations::Preloader для кастомной подгрузки ассоциации (выборочныйselect). Отлаживаю его в консоли:

products_scope=Product.select(:id,:name)ActiveRecord::Associations::Preloader.new(records:line_items,associations: :product,scope:products_scope).call
Enter fullscreen modeExit fullscreen mode

На проде можно предварительно для удобства перенаправить в консоль SQL-запросы:

ActiveRecord::Base.logger=Logger.new(STDOUT)
Enter fullscreen modeExit fullscreen mode

Что мы видим, когда запускаем этот код?

DEBUG -- :   Product Load (3.8ms)  SELECT "products"."id", "products"."name" FROM "products" WHERE "products"."id" = $1  [["id", 1092717]]DEBUG -- :   Product Load (1326.4ms)  SELECT "products"."id", "products"."name" FROM "products"
Enter fullscreen modeExit fullscreen mode

Товары подгружаются дважды. Первый раз, как и ожидается с фильтрацией по id товара. А вот второго быть вообще не должно, к тому же запускается full scan таблицы. И это production-окружении!. Получается AR-preloader не работает? почему так происходит?

Покопался в исходниках и понял, что прелоадер все-таки ни при чем. Проблема возникает при отображении результата выполнения команды в консоли. При этом под капотом дергается некий метод, который форматирует вывод. Какой именно, не понятно. Теоретически, это может быть метод этого самого результата, либо другого объекта с аргументом в виде результата. Попробуем выяснить, как именно.

Вызываемые методы могут быть залогированы при помощи модуляTracePoint. Он позволяет отлавливать разные типы событий, в том числе событияcall, вызываемые на объекте типаMethod. То есть, таким образом мы можем залогировать вызовы любых методов.

result=ActiveRecord::Associations::Preloader.new(records:line_items,associations: :product,scope:products_scope).call;1calls=[]trace=TracePoint.new(:call,:c_call)do|tp|iftp.binding.eval('self')==result||tp.parameters.any?{tp.binding.eval(_1.last)==resultrescuefalse}calls<<"#{tp.defined_class}##{tp.method_id} got called (#{tp.path}:#{tp.lineno})"endendtrace.enableresult
Enter fullscreen modeExit fullscreen mode

Результат, вывод которого хотим залогировать, присваиваем переменной, и гасим вывод команды присваивания. Далее отлавливаем вызовы методов, в которых либо аргументом являетсяresult либо принадлежащие самомуresult. Включаем логирование, инициируем вывод результата в консоль.

Далее нужно отключить логирование и посмотреть, что накопилось вcalls:

trace.disablecalls=>["Array#pretty_print got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:366)"]
Enter fullscreen modeExit fullscreen mode

Уже теплее,result содержит массив и для вывода в консоль используется один из методов модуляPP. Очевидно, что для отображения на каждом элементе массива вызывается еще какой-то метод. Изменим немного код:

result=ActiveRecord::Associations::Preloader.new(records:line_items,associations: :product,scope:products_scope).call[0];1trace.enableresult...trace.disablecalls=>["PP::ObjectMixin#pretty_print got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:317)","Module#instance_method got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:318)","UnboundMethod#bind_call got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:320)","Kernel#method got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:320)","Method#owner got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:323)","BasicObject#!= got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:323)","Module#== got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:323)","BasicObject#! got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:325)","PP::ObjectMixin#pretty_print_instance_variables got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:345)","Kernel#instance_variables got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:346)","Array#sort got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:346)","Symbol#<=> got called (~/.rbenv/versions/3.1.2/lib/ruby/3.1.0/pp.rb:346)"]
Enter fullscreen modeExit fullscreen mode

Немного покопавшись в исходникахPP, не сложно найти, что для вывода объекта все его instance-переменные выбираются и отображаются при помощиpp:

pp(obj.instance_eval(v))
Enter fullscreen modeExit fullscreen mode

ОбъектActiveRecord::Associations::Preloader содержит instance-переменную@preload_scope, в которой сохраненproducts_scope. При отображении с помощьюpp происходит выполенение запроса к базе, что и приводит к этой неочевидной ситуации.

ppProduct.select(:id,:name)D,[2023-09-04T15:55:01.574901#28800] DEBUG -- :   Product Load (1185.7ms)  SELECT "products"."id", "products"."name" FROM "products"
Enter fullscreen modeExit fullscreen mode

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

  • Joined

More fromSergei Malykh

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp