Skip to content

Commit 7a38697

Browse files
committed
feat: improve N+1 queries resolution
1 parent 3adf948 commit 7a38697

6 files changed

Lines changed: 30 additions & 16 deletions

File tree

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ group :development do
103103
# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
104104
# gem "spring"
105105
gem 'annotate'
106+
gem 'bullet'
106107
gem 'rubocop'
107108
gem 'rubocop-rails'
108109
gem 'rubocop-rspec'

Gemfile.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ GEM
8787
bootsnap (1.18.6)
8888
msgpack (~> 1.2)
8989
builder (3.3.0)
90+
bullet (8.1.0)
91+
activesupport (>= 3.0.0)
92+
uniform_notifier (~> 1.11)
9093
concurrent-ruby (1.3.5)
9194
connection_pool (2.5.4)
9295
crack (1.0.0)
@@ -409,6 +412,7 @@ GEM
409412
unicode-display_width (3.2.0)
410413
unicode-emoji (~> 4.1)
411414
unicode-emoji (4.1.0)
415+
uniform_notifier (1.18.0)
412416
uri (1.1.1)
413417
useragent (0.16.11)
414418
vcr (6.3.1)
@@ -431,6 +435,7 @@ DEPENDENCIES
431435
bcrypt (~> 3.1.7)
432436
blueprinter
433437
bootsnap
438+
bullet
434439
database_cleaner-active_record
435440
debug
436441
dotenv-rails

app/controllers/api/v1/rosters_controller.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,14 @@ def free_agents
9393

9494
result = paginate(players)
9595

96+
# Preload organizations in a single query to avoid N+1 on previous_organization_id
97+
org_ids = result[:data].map(&:previous_organization_id).compact.uniq
98+
orgs_by_id = org_ids.any? ? Organization.where(id: org_ids).index_by(&:id) : {}
99+
96100
free_agents_data = result[:data].map do |player|
97101
{
98102
player: PlayerSerializer.render_as_hash(player),
99-
previous_organization: player.previous_organization_id ?
100-
Organization.find(player.previous_organization_id).name : nil,
103+
previous_organization: orgs_by_id[player.previous_organization_id]&.name,
101104
removed_at: player.deleted_at,
102105
removed_reason: player.removed_reason
103106
}

app/modules/dashboard/controllers/dashboard_controller.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ def roster_status_data
156156
end
157157

158158
def fetch_recent_activities
159-
# Fetch recent audit logs and format them
159+
# Fetch recent audit logs and format them.
160+
# includes(:user) preloads the user in one query — avoids N+1 on log.user&.email
160161
activities = AuditLog
162+
.includes(:user)
161163
.where(organization: current_organization)
162164
.order(created_at: :desc)
163165
.limit(20)

app/serializers/vod_review_serializer.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ class VodReviewSerializer < Blueprinter::Base
2727
end
2828

2929
field :timestamps_count do |vod_review, options|
30-
options[:include_timestamps_count] ? vod_review.vod_timestamps.count : nil
30+
# Use .size (not .count) so that when vod_timestamps is eager-loaded (via includes)
31+
# the count comes from the in-memory collection — avoids 1 COUNT query per review.
32+
options[:include_timestamps_count] ? vod_review.vod_timestamps.size : nil
3133
end
3234

3335
association :organization, blueprint: OrganizationSerializer

config/environments/development.rb

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212

1313
config.eager_load = false
1414

15-
# nosemgrep: ruby.rails.security.audit.detailed-exceptions.detailed-exceptions
16-
# We want detailed exceptions in development environment
17-
config.consider_all_requests_local = true
15+
# Intentional: development needs full error reports for debugging
16+
config.consider_all_requests_local = true # nosemgrep: ruby.rails.security.audit.detailed-exceptions.detailed-exceptions
1817

1918
config.server_timing = true
2019

@@ -80,13 +79,15 @@
8079
# ActiveJob configuration - use Sidekiq in development
8180
config.active_job.queue_adapter = :sidekiq
8281

83-
# Bullet for N+1 query detection
84-
# Uncomment if using Bullet gem
85-
# config.after_initialize do
86-
# Bullet.enable = true
87-
# Bullet.alert = true
88-
# Bullet.bullet_logger = true
89-
# Bullet.console = true
90-
# Bullet.rails_logger = true
91-
# end
82+
# Bullet — N+1 query detection
83+
config.after_initialize do
84+
Bullet.enable = true
85+
Bullet.bullet_logger = true # log/bullet.log
86+
Bullet.rails_logger = true # log/development.log
87+
Bullet.add_footer = false # API-only, sem HTML footer
88+
Bullet.raise = false # não levantar exceção em dev
89+
90+
# Ignore associations que são consultadas via SQL puro (não associação AR)
91+
# Bullet.add_safelist type: :n_plus_one_query, class_name: 'Foo', association: :bar
92+
end
9293
end

0 commit comments

Comments
 (0)