Skip to content

Conversation

OuYangJinTing
Copy link
Contributor

This PR introduces ActiveRecord associations preloading to reduce N+1 queries when serializing nested exposures.

Key points:

  • Add Preloader that batches association loads for collections
  • Integrate preload option into entity exposures
  • Specs covering collection and nested association cases
  • No API breaking changes; opt-in based on presence of ActiveRecord models

Why:

  • Significant performance gains for typical API responses with deep nesting
  • Keeps entity logic declarative while avoiding repeated queries

Please see the branch for details. Feedback welcome.

- Add new `:preload` exposure option
- Add `Grape::Entity.preload_and_represent` helper method
- Add `Grape::Entity::Preloader` class for preload activerecord associations
@numbata
Copy link
Collaborator

numbata commented Sep 1, 2025

Hi @OuYangJinTing,

Many thanks for diving deep into this! I really appreciate how thoroughly you addressed the N+1 query problem - great test coverage, elegant preload option, and that preload_and_represent helper are all impressive.

That said, I wonder if introducing ActiveRecord-specific logic into grape-entity core might blur its intended boundaries.

  • Purpose of the Library

    grape-entity has historically been ORM‑agnostic, acting as an “API‑focused facade that sits on top of an object model”. It’s a well-designed abstraction that works with plain Ruby objects, different ORMs, or any other object model without imposing dependencies. Adding AR-specific features like preload_and_represent, version checks, and integration with ActiveRecord::Associations::Preloader could risk shifting the gem’s scope toward ORMs and data-layer concerns.

  • Maintainability & Dependency Costs

    This PR brings in several new concerns:

    • Increases API footprint with AR-specific methods and options.
    • Ties core behavior to ActiveRecord versions (>= 7.0).
    • Adds extra test/development dependencies (e.g. sqlite3, ActiveRecord) and CI complexity.

    All of this raises the bar for maintaining and evolving the gem, and it might not bring much benefit to users outside of ActiveRecord.

  • Follow Rails’ Best Practices

    In the Rails ecosystem, the widely accepted approach is to handle eager loading (e.g. includes, preload) when building the query or in controller logic - not within the serializer/entity layer. This separation keeps layers clean: fetching concerns stay in the data-access layer, serialization stays focused on transforming data for API output.

Overall, it's a well-intentioned and technically solid proposal, but I think it could move the core gem away from its primary simplicity and flexibility.

One idea that might strike a good balance: extract this functionality into a separate adapter gem, e.g. grape-entity-activerecord, which could:

  • Provide Grape::Entity::ActiveRecordPreloader with preload_and_represent.
  • Walk the :preload flags in exposures.
  • Invoke ActiveRecord::Associations::Preloader, without the core knowing anything about AR.

That way, users needing AR-specific behavior can opt in cleanly - while the core remains lean and focused.

Let's hear what the other maintainers think as well - getting more perspectives sounds like the right next step.

Again, thank you for the hard thinking, craftsmanship, and initiative here. I’m really glad to have contributors willing to tackle real pain-points like N+1, and I’m looking forward to figuring out together what the best, most maintainable path forward could be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants