-
-
Save UsamaAshraf/95b0c8d0d64ee193148342a931c0a423 to your computer and use it in GitHub Desktop.
# ... | |
# https://github.com/exAspArk/batch-loader | |
gem 'batch-loader' |
class PostsController < ApplicationController | |
def index | |
posts = Post.all | |
render json: posts | |
end | |
end | |
class Post | |
belongs_to :author, class_name: 'User' | |
end | |
class PostSerializer < ActiveModel::Serializer | |
attributes :id, :title, :details | |
belongs_to :author | |
end |
class Post < ApplicationRecord | |
# ... | |
def get_author_lazily | |
BatchLoader.for(self).batch do |posts, batch_loader| | |
User.where(:_id.in => posts.pluck(:author_id)).each do |user| | |
# Modify the user through a given block, say, for serialization. | |
modified_user = block_given? ? yield(user) : user | |
batch_loader.call(posts.detect { |p| p.author_id == user._id.to_s }, modified_user) | |
end | |
end | |
end | |
# ... | |
end |
class PostsController < ApplicationController | |
def index | |
# Can't do Post.includes(:author) beacuse the author (User object) | |
# is stored in an entirely different database: a MongoDB instance. | |
posts = Post.all | |
render json: posts | |
end | |
end |
class PostSerializer < ActiveModel::Serializer | |
attributes :id, :title, :details, :author | |
def author | |
object.get_author_lazily do |author| | |
# Serialize the author after it has been loaded. | |
ActiveModelSerializers::SerializableResource.new(author).as_json[:user] | |
end | |
end | |
# ... | |
end |
class User | |
include Mongoid::Document | |
include Mongoid::Timestamps | |
# ... | |
end |
class UserSerializer < ActiveModel::Serializer | |
# .... | |
end |
@luccasmaso yes, include
works only for ORM-defined database relations, not custom attributes, which is what user
has become in our case.
We can pass custom parameters and access them with @instance_options
to achieve our goal:
# PostSerializer.rb
attribute :user, if: -> { @instance_options.key?(:with_user) && @instance_options[:with_user] }
# ...
render json: posts, with_user: true
You can also stick with include
, use @instance_options[:include]
and check if user
was specified. But I'd probably not do this because it sort of goes against what the include
option is supposed to be for. Also, in a way our point was to avoid include
since it forced the n+1 queries to run. Having said that, there's nothing essentially wrong with using @instance_options[:include]
.
As far as nested associations are concerned, you can pass them to the explicit call to AMS;
ActiveModelSerializers::SerializableResource.new(user, include: [some: :nested_stuff]).as_json[:user]
Sorry for replying late. Don't know why I didn't get an email!
Hey, I digged in this topic a bit and created a plugin for ActiveModelSerializers - https://github.com/Bajena/ams_lazy_relationships
It eliminates the problem that @lucasmaso mentioned in his comment :)
Nice implementation! Also, good post around the subject. The application of this with AMS is really useful, since most graphql new approaches are heavy influenced by the data loader technic.
I was playing a little with your code and it works, except for a drawback with AMS. This batch behaviour to postpone the attribute will not take advantage of
include
directive for choosing the JSON depth/attributes and response performance.For exemple: If the post
User
has a list ofUser
as friends and I don't want to load them, I would specify:render json: posts, include: :user
And if I want it:
render json: posts, include: [user: :friends]
But with batch loader its not possible maybe because
ActiveModelSerializers::SerializableResource.new(author).as_json[:user]
will be constructed afterwards to apply theinclude
? I'm thinking here what could be a solution but can't figured out if is a limitation of AMS or the BathLoader.