ruby-grape / ruby-grape/grape-entity
Presenting a collection with an entity fetches each table twice (2 SQL statements)
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 729
- Forks
- 154
- PR merge metrics
- No merged PRs in 30d
Description
I have a Location model that has_one :address. The data is stored in Postgres. Here's the Grape API endpoint definition for fetching all locations:
resource "locations" do
# GET /locations
desc 'Returns all locations, 30 per page by default'
params do
optional :page, type: Integer, default: 1
optional :per_page, type: Integer, default: 30
end
get do
locations = Location.includes(:address).
page(params[:page]).per(params[:per_page])
present locations, with: Entities::Location
end
end
My (simplified) Location Entities:
module Entities
class Location < Grape::Entity
expose :address, using: Address::Entity
expose :name
end
end
The Address::Entity is defined within the Address model:
include Grape::Entity::DSL
entity do
expose :id
expose :street
expose :city
expose :state
expose :zip
end
When I visit http://localhost:8080/api/locations, I get the following output in the log:
Location Load (0.6ms) SELECT "locations".* FROM "locations" ORDER BY "locations"."id" ASC LIMIT 1 OFFSET 0
Address Load (0.5ms) SELECT "addresses".* FROM "addresses" WHERE "addresses"."location_id" IN (2)
Location Load (6.0ms) SELECT "locations".* FROM "locations" LIMIT 30 OFFSET 0
Address Load (0.8ms) SELECT "addresses".* FROM "addresses" WHERE "addresses"."location_id" IN (2, 17, 41, 138, 321, 54, 61, 325, 1146, 531, 139, 326, 965, 72, 238, 140, 228, 330, 1631, 271, 241, 331, 335, 6, 1460, 336, 75, 90, 101, 818)
Note that there are two calls to each table: one to fetch the first record in the results, and then another to fetch all the results (up to the per_page amount).
If I change the endpoint definition to just return locations without any grape-entity representation, and instead, use my own as_json method in the Locations model, like this:
def as_json(options={})
{
name: name,
address: address
}
end
then, I only end up with one call to each table, as opposed to the two that grape-entity made:
Location Load (6.7ms) SELECT "locations".* FROM "locations" LIMIT 30 OFFSET 0
Address Load (4.4ms) SELECT "addresses".* FROM "addresses" WHERE "addresses"."location_id" IN (2, 17, 41, 138, 321, 54, 61, 325, 1146, 531, 139, 326, 965, 72, 238, 140, 228, 330, 1631, 271, 241, 331, 335, 6, 1460, 336, 75, 90, 101, 818)
This is reproducible every time I switch back and forth between the two methods of representing the JSON. It also happens if the JSON doesn't include data from other tables, and even when you're just calling Location.all (not that you would do that). It always makes 2 calls to the Locations table.
In the actual app, I need to include 8 other tables, so having an extra call to each one adds up!
Has anyone noticed this before? Is there a way to write a spec for this?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the GET /locations endpoint with Location.includes(:address), pagination, and Entities::Location, then compare its SQL with the as_json variant. Inspect the Location entity and Address entity definitions shown in the report. Done means identifying why the first record is loaded separately, preventing the duplicate queries, and adding a regression spec for the behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, ruby
- Domain
- api, backend, databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100