Skip to content

Commit 8134d99

Browse files
committed
Update README.md
The provide comprehensive usage instructions and installation details for Grape::Entity::Preloader, including activation methods, preloading associations, callbacks, and conditions.
1 parent 8cc6c0b commit 8134d99

File tree

1 file changed

+155
-10
lines changed

1 file changed

+155
-10
lines changed

README.md

Lines changed: 155 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,173 @@
11
# Grape::Entity::Preloader
22

3-
TODO: Delete this and the text below, and describe your gem
4-
5-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/grape/entity/preloader`. To experiment with that code, run `bin/console` for an interactive prompt.
3+
Grape::Entity::Preloader allows preload associations and callbacks for avoiding N+1 operations in Grape::Entity.
64

75
## Installation
86

9-
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10-
11-
Install the gem and add to the application's Gemfile by executing:
12-
137
```bash
14-
bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
8+
bundle add grape-entity-preloader
159
```
1610

1711
If bundler is not being used to manage dependencies, install the gem by executing:
1812

1913
```bash
20-
gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
14+
gem install grape-entity-preloader
2115
```
2216

2317
## Usage
2418

25-
TODO: Write usage instructions here
19+
### Activation
20+
21+
#### Global Activation
22+
23+
You can enable the preloader globally. This is useful in environments where you want preloading to be the default behavior.
24+
25+
```ruby
26+
# config/initializers/grape_entity_preloader.rb
27+
Grape::Entity::Preloader.enabled!
28+
```
29+
30+
#### Local Activation and Deactivation
31+
32+
You can control preloading for specific `represent` calls.
33+
34+
##### 1. Using options
35+
36+
Pass `grape_entity_preloader: :enabled` or `grape_entity_preloader: :disabled` to the options hash. This overrides the global setting.
37+
38+
```ruby
39+
# Locally enable
40+
MyEntity.represent(users, grape_entity_preloader: :enabled)
41+
42+
# Locally disable
43+
MyEntity.represent(users, grape_entity_preloader: :disabled)
44+
```
45+
46+
##### 2. Using a block
47+
48+
For a specific block of code, you can use `with_enable` or `with_disable`. This is useful in contexts like API endpoints or middlewares.
49+
50+
```ruby
51+
Grape::Entity::Preloader.with_enable do
52+
# Preloading is enabled for all represent calls inside this block
53+
MyAPI::Entities::User.represent(User.all)
54+
end
55+
56+
Grape::Entity::Preloader.with_disable do
57+
# Preloading is disabled for all represent calls inside this block
58+
MyAPI::Entities::User.represent(User.all)
59+
end
60+
```
61+
62+
### `preload_association`
63+
64+
Use `preload_association` to preload ActiveRecord associations. This helps to avoid N+1 queries when an exposure represents an association.
65+
66+
```ruby
67+
class UserEntity < Grape::Entity
68+
expose :id
69+
expose :name
70+
# This will preload the `books` association for all users being represented.
71+
expose :books, using: BookEntity, preload_association: :books
72+
end
73+
74+
# In your API
75+
users = User.limit(10)
76+
# When UserEntity represents users, it will execute two queries:
77+
# 1. SELECT * FROM users LIMIT 10
78+
# 2. SELECT * FROM books WHERE books.user_id IN (...)
79+
UserEntity.represent(users)
80+
```
81+
82+
For nested preloading:
83+
84+
```ruby
85+
class BookEntity < Grape::Entity
86+
expose :id
87+
expose :title
88+
# This will preload tags for each book
89+
expose :tags, using: TagEntity, preload_association: :tags
90+
end
91+
92+
class UserEntity < Grape::Entity
93+
expose :id
94+
expose :name
95+
expose :books, using: BookEntity, preload_association: :books
96+
end
97+
98+
# It will generate 3 queries instead of 1 + 10 (for books) + N (for tags)
99+
UserEntity.represent(User.limit(10))
100+
```
101+
102+
### `preload_callback`
103+
104+
For more complex scenarios that `preload_association` doesn't cover (e.g., loading data from other services, custom caching logic), you can use `preload_callback`.
105+
106+
It must be a `Proc` that accepts two arguments:
107+
1. `objects`: An array of the parent objects being represented.
108+
2. `options`: The `Grape::Entity::Options` object for the current representation context.
109+
110+
**The `Proc` should return an array of objects that will be used for the nested entity representation. These returned objects will then be passed to the preloader for that nested entity, allowing for further nested preloading.**
111+
112+
```ruby
113+
class UserStatsEntity < Grape::Entity
114+
expose :likes
115+
expose :followers
116+
end
117+
118+
class UserEntity < Grape::Entity
119+
expose :id
120+
expose :name
121+
122+
expose :stats, using: UserStatsEntity, preload_callback: ->(users, _options) do
123+
# `users` is an array of User objects.
124+
# Here you can fetch stats for all users in one batch.
125+
user_ids = users.map(&:id)
126+
stats_data = StatsService.batch_get_by_user_ids(user_ids) # returns a hash { user_id => stats_object }
127+
128+
# The preloader needs to associate the loaded data back to the original objects.
129+
# A common pattern is to attach the data to a new attribute on the object.
130+
users.each { |user| user.instance_variable_set(:@stats, stats_data[user.id]) }
131+
132+
# The block must return the objects that will be presented by the nested entity.
133+
# In this case, it's the stats objects we just loaded.
134+
users.map { |user| user.instance_variable_get(:@stats) }
135+
end
136+
end
137+
138+
# In the entity, you need to define how to access the preloaded data.
139+
class UserEntity < Grape::Entity
140+
# ...
141+
expose :stats, using: UserStatsEntity, preload_callback: ... do |user, _options|
142+
user.instance_variable_get(:@stats)
143+
end
144+
end
145+
```
146+
147+
### `preload_condition`
148+
149+
Use `preload_condition` to conditionally enable or disable preloading for an exposure. It must be a `Proc` that accepts one argument: `options`, which is the `Grape::Entity::Options` object.
150+
151+
If the `Proc` returns a falsy value, preloading for that exposure will be skipped.
152+
153+
```ruby
154+
class UserEntity < Grape::Entity
155+
expose :id
156+
expose :name
157+
158+
# The :audit_log association will only be preloaded if `include_audit_log` is true in the options.
159+
expose :audit_log,
160+
using: AuditLogEntity,
161+
preload_association: :audit_log,
162+
preload_condition: ->(options) { options[:include_audit_log] }
163+
end
164+
165+
# Preloading for :audit_log is skipped
166+
UserEntity.represent(user)
167+
168+
# Preloading for :audit_log is executed
169+
UserEntity.represent(user, include_audit_log: true)
170+
```
26171

27172
## Development
28173

0 commit comments

Comments
 (0)