Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/djezzzl/n1_loader
Loader to solve N+1 issues for good. Highly recommended for GraphQL API.
https://github.com/djezzzl/n1_loader
activerecord graphql nplus1 rails ruby
Last synced: 8 days ago
JSON representation
Loader to solve N+1 issues for good. Highly recommended for GraphQL API.
- Host: GitHub
- URL: https://github.com/djezzzl/n1_loader
- Owner: djezzzl
- License: mit
- Created: 2021-12-18T13:18:31.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2023-12-18T06:51:33.000Z (11 months ago)
- Last Synced: 2024-05-17T16:03:07.169Z (6 months ago)
- Topics: activerecord, graphql, nplus1, rails, ruby
- Language: Ruby
- Homepage:
- Size: 126 KB
- Stars: 220
- Watchers: 6
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# N1Loader
[![Gem Version][3]][4]
[![][11]][12]
[![][13]][14]
[![][9]][10]N1Loader is designed to provide a simple way for avoiding [N+1 issues][7] of any kind.
For example, it can help with resolving N+1 for:
- database querying (most common case)
- 3rd party service calls
- complex calculations
- and many more> If the project helps you or your organization, I would be very grateful if you [contribute][15] or [donate][10].
> Your support is an incredible motivation and the biggest reward for my hard work.___Support:___ ActiveRecord 5, 6, and 7.
Follow me and stay tuned for the updates:
- [LinkedIn](https://www.linkedin.com/in/evgeniydemin/)
- [Medium](https://evgeniydemin.medium.com/)
- [Twitter](https://twitter.com/EvgeniyDemin/)
- [GitHub](https://github.com/djezzzl)## Killer feature for GraphQL API
N1Loader in combination with [ArLazyPreload][6] is a killer feature for your GraphQL API.
Give it a try now and see incredible results instantly! Check out the [example](examples/graphql.rb) and start benefiting from it in your projects!```ruby
gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
```## Enhance [ActiveRecord][5]
Are you working with well-known Rails application? Try it out and see how well N1Loader fulfills missing gaps when you can't define ActiveRecord associations!
Check out the detailed [guide](guides/enhanced-activerecord.md) with examples or its [short version](examples/active_record_integration.rb).```ruby
gem 'n1_loader', require: 'n1_loader/active_record'
```Are you ready to forget about N+1 once and for all? Install [ArLazyPreload][6] and see dreams come true!
```ruby
gem 'n1_loader', require: 'n1_loader/ar_lazy_preload'
```## Standalone mode
Are you not working with [ActiveRecord][5]? N1Loader is ready to be used as standalone solution! ([full snippet](examples/core.rb))
```ruby
gem 'n1_loader'
```## How to use it?
N1Loader provides DSL that allows you to define N+1 ready loaders that can
be injected into your objects in a way that you can avoid N+1 issues.> _Disclaimer_: examples below are working but designed to show N1Loader potentials only.
In real live applications, N1Loader can be applied anywhere and in more [elegant way](examples/isolated_loader.rb).Let's look at simple example below ([full snippet](examples/active_record_integration.rb)):
```ruby
class User < ActiveRecord::Base
has_many :paymentsn1_optimized :payments_total do |users|
total_per_user =
Payment.group(:user_id)
.where(user: users)
.sum(:amount)
.tap { |h| h.default = 0 }users.each do |user|
total = total_per_user[user.id]
fulfill(user, total)
end
end
endclass Payment < ActiveRecord::Base
belongs_to :uservalidates :amount, presence: true
end# A user has many payments.
# Assuming, we want to know for group of users, what is a total of their payments, we can do the following:# Has N+1 issue
p User.all.map { |user| user.payments.sum(&:amount) }# Has no N+1 but we load too many data that we don't actually need
p User.all.includes(:payments).map { |user| user.payments.sum(&:amount) }# Has no N+1 and we load only what we need
p User.all.includes(:payments_total).map { |user| user.payments_total }
```Let's assume now, that we want to calculate the total of payments for the given period for a group of users.
N1Loader can do that as well! ([full snippet](examples/arguments_support.rb))```ruby
class User < ActiveRecord::Base
has_many :paymentsn1_optimized :payments_total do
argument :from
argument :todef perform(users)
total_per_user =
Payment
.group(:user_id)
.where(created_at: from..to)
.where(user: users)
.sum(:amount)
.tap { |h| h.default = 0 }users.each do |user|
total = total_per_user[user.id]
fulfill(user, total)
end
end
end
endclass Payment < ActiveRecord::Base
belongs_to :uservalidates :amount, presence: true
end# Has N+1
p User.all.map { |user| user.payments.select { |payment| payment.created_at >= from && payment.created_at <= to }.sum(&:amount) }# Has no N+1 but we load too many data that we don't need
p User.all.includes(:payments).map { |user| user.payments.select { |payment| payment.created_at >= from && payment.created_at <= to }.sum(&:amount) }# Has no N+1 and calculation is the most efficient
p User.all.includes(:payments_total).map { |user| user.payments_total(from: from, to: to) }
```## Features and benefits
- N1Loader doesn't use Promises which means it's easy to debug
- Doesn't require injection to objects, can be used in [isolation](examples/isolated_loader.rb)
- Loads data [lazily](examples/lazy_loading.rb)
- Loaders can be [shared](examples/shared_loader.rb) between multiple classes
- Loaded data can be [re-fetched](examples/reloading.rb)
- Loader can be optimized for [single cases](examples/single_case.rb)
- Loader support [arguments](examples/arguments_support.rb)
- Has [integration](examples/active_record_integration.rb) with [ActiveRecord][5] which makes it brilliant
- Has [integration](examples/ar_lazy_integration.rb) with [ArLazyPreload][6] which makes it excellent### Feature killer for [ArLazyPreload][6] integration with isolated loaders
In [version 1.6.0](CHANGELOG.md#160---20221019) isolated loaders were integrated with [ArLazyPreload][6] context.
This means, it isn't required to inject `N1Loader` into your [ActiveRecord][5] models to avoid N+1 issues out of the box.
It is especially great as many engineers are trying to avoid extra coupling between their models/services when it's possible.
And this feature was designed exactly for this without losing an out of a box solution for N+1.Without further ado, please have a look at the [example](examples/ar_lazy_integration_with_isolated_loader.rb).
_Spoiler:_ as soon as you have your loader defined, it will be as simple as `Loader.for(element)` to get your data efficiently and without N+1.
## Funding
### Open Collective Backers
You're an individual who wants to support the project with a monthly donation. Your logo will be available on the Github page. [[Become a backer](https://opencollective.com/n1_loader#backer)]
### Open Collective Sponsors
You're an organization that wants to support the project with a monthly donation. Your logo will be available on the Github page. [[Become a sponsor](https://opencollective.com/n1_loader#sponsor)]
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/djezzzl/n1_loader.
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
## Code of Conduct
Everyone interacting in the N1Loader project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
## Changelog
*N1Loader*'s changelog is available [here](CHANGELOG.md).
## Copyright
Copyright (c) Evgeniy Demin. See [LICENSE.txt](LICENSE.txt) for further details.
[3]: https://badge.fury.io/rb/n1_loader.svg
[4]: https://badge.fury.io/rb/n1_loader
[5]: https://github.com/rails/rails/tree/main/activerecord
[6]: https://github.com/DmitryTsepelev/ar_lazy_preload
[7]: https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping
[8]: https://github.com/djezzzl/n1_loader
[9]: https://opencollective.com/n1_loader/tiers/badge.svg
[10]: https://opencollective.com/n1_loader#support
[11]: https://github.com/djezzzl/n1_loader/actions/workflows/tests.yml/badge.svg?branch=master
[12]: https://github.com/djezzzl/n1_loader/actions/workflows/tests.yml?query=event%3Aschedule
[13]: https://github.com/djezzzl/n1_loader/actions/workflows/rubocop.yml/badge.svg?branch=master
[14]: https://github.com/djezzzl/n1_loader/actions/workflows/rubocop.yml?query=event%3Aschedule
[15]: https://github.com/djezzzl/n1_loader#contributing