Skip to content

Commit 31912a3

Browse files
authored
docs: add n1_bind_to example and update README for v2.1.0 context-sharing feature (#51)
1 parent 8023ed8 commit 31912a3

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ Are you not working with [ActiveRecord][5]? N1Loader is ready to be used as stan
5555
gem 'n1_loader'
5656
```
5757

58+
Want lazy, N+1-free loading without explicit preloading? Use `n1_bind_to` to share context across a collection of plain Ruby objects. ([full snippet](examples/n1_bind_to.rb))
59+
60+
```ruby
61+
users = [User.new, User.new, User.new]
62+
63+
# Bind users to the collection — lazy access is now automatically batched
64+
users.each { |user| user.n1_bind_to(users) }
65+
66+
users.map(&:optimized_call) # loads all in a single batch, no N+1
67+
```
68+
5869
## How to use it?
5970

6071
N1Loader provides DSL that allows you to define N+1 ready loaders that can
@@ -156,6 +167,7 @@ p User.all.includes(:payments_total).map { |user| user.payments_total(from: from
156167
- Loader support [arguments](examples/arguments_support.rb)
157168
- Has [integration](examples/active_record_integration.rb) with [ActiveRecord][5] which makes it brilliant
158169
- Has [integration](examples/ar_lazy_integration.rb) with [ArLazyPreload][6] which makes it excellent
170+
- Supports [context sharing](examples/n1_bind_to.rb) for plain Ruby objects without ActiveRecord
159171

160172
### Feature killer for [ArLazyPreload][6] integration with isolated loaders
161173

@@ -168,6 +180,16 @@ Without further ado, please have a look at the [example](examples/ar_lazy_integr
168180

169181
_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.
170182

183+
### Context sharing for plain Ruby objects with `n1_bind_to`
184+
185+
In [version 2.1.0](CHANGELOG.md#210---20260307) context sharing was added for plain Ruby objects.
186+
This allows you to get lazy, N+1-free loading without [ActiveRecord][5] or explicit preloading.
187+
188+
By calling `n1_bind_to(collection)` on each element, you bind them to their shared collection.
189+
When any loader is triggered on a bound element, it automatically batch-loads for the entire collection — and nested loaders propagate the context automatically as well.
190+
191+
Have a look at the [example](examples/n1_bind_to.rb) to see how simple it is.
192+
171193
## Funding
172194

173195
### Open Collective Backers

examples/core.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,16 @@ def unoptimized_call
3232
p users.map(&:unoptimized_call)
3333
p "Has N+1 #{Service.count == count_before + users.count}"
3434

35-
# Has no N+1
35+
# Has no N+1 via explicit preloading
3636
count_before = Service.count
3737
N1Loader::Preloader.new(users).preload(:optimized_call)
3838
p users.map(&:optimized_call)
3939
p "Has no N+1: #{Service.count == count_before + 1}"
40+
41+
users = [User.new, User.new]
42+
43+
# Has no N+1 via n1_bind_to context sharing (see examples/n1_bind_to.rb)
44+
users.each { |user| user.n1_bind_to(users) }
45+
count_before = Service.count
46+
p users.map(&:optimized_call)
47+
p "Has no N+1: #{Service.count == count_before + 1}"

examples/n1_bind_to.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
require "n1_loader"
4+
5+
require_relative 'context/service'
6+
7+
# Class that wants to request 3rd party service without N+1
8+
class User
9+
include N1Loader::Loadable
10+
11+
n1_optimized :optimized_call do |users|
12+
data = Service.receive(users)
13+
14+
users.each_with_index do |user, index|
15+
fulfill(user, data[index])
16+
end
17+
end
18+
end
19+
20+
users = [User.new, User.new, User.new]
21+
22+
# Without n1_bind_to: each user lazily loads independently causing N+1
23+
count_before = Service.count
24+
p users.map(&:optimized_call)
25+
p "Has N+1: #{Service.count == count_before + users.count}"
26+
27+
users = [User.new, User.new, User.new]
28+
29+
# With n1_bind_to: bind users to the collection so lazy loading is automatically batched
30+
users.each { |user| user.n1_bind_to(users) }
31+
32+
count_before = Service.count
33+
p users.map(&:optimized_call)
34+
p "Has no N+1: #{Service.count == count_before + 1}"

0 commit comments

Comments
 (0)