При выполнении сложных запросов, часто бывает необходимо выполнить предзагрузку данных об ассоциациях какой либо модели.
Обычно сложные запросы выполняються через find_by_sql
, например
Post.find_by_sql "SELECT p.name, c.author FROM posts p, comments c WHERE p.id = c.post_id"
> [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
Чтобы при вывзове ассоциаций объектов избежать проблемы с select(n+1)
запросами в Rails существует eager-loading, например:
@users = User.where(status: 'activated').includes(:posts)
Благодаря такой конструкции при вызове @users.first.posts
не будет произведен еще один дополнительный запрос для подгрузки постов, но это не работает с find_by_sql
, так как этот метод вернет массив, а не ActiveRecord::Relation
объект.
Для того чтобы избежать select(n+1)
в данном случае необзодимо использовать:
@users = User.find_by_sql(some_condition)
ActiveRecord::Associations::Preloader.new(@users, :posts).run
@users = User.find_by_sql(some_condition)
ActiveRecord::Associations::Preloader.new.preload(@users, :posts)
В качестве первого аргумента выступает массив записей, второго - желаемая ассоциация. Также существует возможность в качестве параметров передать массив ассоциаций или хещ с ассоциациями ассоциация, например:
@users = User.find_by_sql(some_condition)
#Array of associations
ActiveRecord::Associations::Preloader.new.preload(@users, %i(posts images))
#Hash to eager load associations of associations
ActiveRecord::Associations::Preloader.new.preload(@users, {posts: :comments})
#Or combination of array and hash
ActiveRecord::Associations::Preloader.new.preload(@users, [:images, { posts: :comments }])