How to Implement Laravels Whenloaded Function in Ror
The ORM library in Laravel provides support for a conditional loading association method known as whenLoaded
.
From the Laravel official document:
The
whenLoaded
method may be used to conditionally load a relationship. In order to avoid unnecessarily loading relationships, this method accepts the name of the relationship instead of the relationship itself.
Here’s an example from Laravel’s official document, use it with “ResourceResponse” can significantly enhance productivity.
use App\Http\Resources\PostResource;
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->whenLoaded('posts')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
How to implement it in RoR?
To achieve what “ResourceResponse” does, we need the “grape” gem.
Given two entities, PostEntity and CommentEntity
:
- PostEntity.rb
module Entities
class PostEntity < Grape::Entity
expose :id, documentation: { type: 'Integer', desc: 'id' }
expose :title, documentation: { type: 'String', desc: 'title' }
expose :comments,
using: CommentEntities,
documentation: { is_array: true, desc: 'post comments' }
end
end
- CommentEntity.rb
module Entities
class CommentEntity < Grape::Entity
expose :id, documentation: { type: 'Integer', desc: 'id' }
expose :title, documentation: { type: 'String', desc: 'title' }
end
end
When we present PostEntity, we will receive a response with comments.
present Post.all with PostEntity # {id, title, comments: [{id, title}, {id, title}]}
However, if we don’t want to retrieve comments with every single request, we can modify it as follows:
module Entities
# ...
expose :comments,
using: CommentEntities,
documentation: { is_array: true, desc: 'post comments' },
if: -> (instance, _options) { instance.association(:comments).loaded? }
end
This way, comments will only be displayed if explicitly included:
#
present Post.all with PostEntity # {id, title}
present Post.includes(:comments).all with PostEntity # {id, title, comments: [{id, title}, {id, title}]}
bi-direction association
What if we want to load PostEntity
from CommentEntity
?
- add an association to
CommentEntity
module Entities
# ...
# this will cause circular error
expose :comments,
using: CommentEntities,
documentation: { is_array: true, desc: 'post comments' },
if: -> (instance, _options) { instance.association(:post).loaded? }
end
- fix circular error
module Entities
# ...
expose :comments,
using: CommentEntities,
documentation: { is_array: true, desc: 'post comments' },
if: -> (instance, options) {
options[:parent] == instance.class.name &&
instance.association(:post).loaded?
}
end
- present
present Comment.includes(:post).all with CommentEntity, parent: 'Comment'
# {id, title, post: {id, title}}
present Post.includes(:comments).all with PostEntity, parent: 'Post'
# {id, title, comments: [{id, title}, {id, title}]}