Showing preview only (395K chars total). Download the full file or copy to clipboard to get everything.
Repository: flyerhzm/bullet
Branch: main
Commit: ec551da5b09e
Files: 143
Total size: 363.1 KB
Directory structure:
gitextract_njyn2xoi/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── close_inactive_issues.yml
│ └── main.yml
├── .gitignore
├── .rspec
├── CHANGELOG.md
├── Gemfile
├── Gemfile.mongoid
├── Gemfile.mongoid-4.0
├── Gemfile.mongoid-5.0
├── Gemfile.mongoid-6.0
├── Gemfile.mongoid-7.0
├── Gemfile.mongoid-8.0
├── Gemfile.mongoid-9.0
├── Gemfile.rails-4.2
├── Gemfile.rails-5.0
├── Gemfile.rails-5.1
├── Gemfile.rails-5.2
├── Gemfile.rails-6.0
├── Gemfile.rails-6.1
├── Gemfile.rails-7.0
├── Gemfile.rails-7.1
├── Gemfile.rails-7.2
├── Gemfile.rails-8.0
├── Gemfile.rails-8.1
├── Guardfile
├── Hacking.md
├── MIT-LICENSE
├── README.md
├── Rakefile
├── bullet.gemspec
├── lib/
│ ├── bullet/
│ │ ├── active_job.rb
│ │ ├── active_record4.rb
│ │ ├── active_record41.rb
│ │ ├── active_record42.rb
│ │ ├── active_record5.rb
│ │ ├── active_record52.rb
│ │ ├── active_record60.rb
│ │ ├── active_record61.rb
│ │ ├── active_record70.rb
│ │ ├── active_record71.rb
│ │ ├── active_record72.rb
│ │ ├── active_record80.rb
│ │ ├── active_record81.rb
│ │ ├── bullet_xhr.js
│ │ ├── dependency.rb
│ │ ├── detector/
│ │ │ ├── association.rb
│ │ │ ├── base.rb
│ │ │ ├── counter_cache.rb
│ │ │ ├── n_plus_one_query.rb
│ │ │ └── unused_eager_loading.rb
│ │ ├── detector.rb
│ │ ├── ext/
│ │ │ ├── object.rb
│ │ │ └── string.rb
│ │ ├── mongoid4x.rb
│ │ ├── mongoid5x.rb
│ │ ├── mongoid6x.rb
│ │ ├── mongoid7x.rb
│ │ ├── mongoid8x.rb
│ │ ├── mongoid9x.rb
│ │ ├── notification/
│ │ │ ├── base.rb
│ │ │ ├── counter_cache.rb
│ │ │ ├── n_plus_one_query.rb
│ │ │ └── unused_eager_loading.rb
│ │ ├── notification.rb
│ │ ├── notification_collector.rb
│ │ ├── rack.rb
│ │ ├── registry/
│ │ │ ├── association.rb
│ │ │ ├── base.rb
│ │ │ ├── call_stack.rb
│ │ │ └── object.rb
│ │ ├── registry.rb
│ │ ├── stack_trace_filter.rb
│ │ └── version.rb
│ ├── bullet.rb
│ └── generators/
│ └── bullet/
│ └── install_generator.rb
├── perf/
│ └── benchmark.rb
├── rails/
│ └── init.rb
├── spec/
│ ├── bullet/
│ │ ├── detector/
│ │ │ ├── association_spec.rb
│ │ │ ├── base_spec.rb
│ │ │ ├── counter_cache_spec.rb
│ │ │ ├── n_plus_one_query_spec.rb
│ │ │ └── unused_eager_loading_spec.rb
│ │ ├── ext/
│ │ │ ├── object_spec.rb
│ │ │ └── string_spec.rb
│ │ ├── notification/
│ │ │ ├── base_spec.rb
│ │ │ ├── counter_cache_spec.rb
│ │ │ ├── n_plus_one_query_spec.rb
│ │ │ └── unused_eager_loading_spec.rb
│ │ ├── notification_collector_spec.rb
│ │ ├── rack_spec.rb
│ │ ├── registry/
│ │ │ ├── association_spec.rb
│ │ │ ├── base_spec.rb
│ │ │ └── object_spec.rb
│ │ └── stack_trace_filter_spec.rb
│ ├── bullet_spec.rb
│ ├── integration/
│ │ ├── active_record/
│ │ │ └── association_spec.rb
│ │ ├── counter_cache_spec.rb
│ │ └── mongoid/
│ │ └── association_spec.rb
│ ├── models/
│ │ ├── address.rb
│ │ ├── attachment.rb
│ │ ├── author.rb
│ │ ├── base_user.rb
│ │ ├── category.rb
│ │ ├── city.rb
│ │ ├── client.rb
│ │ ├── comment.rb
│ │ ├── company.rb
│ │ ├── country.rb
│ │ ├── deal.rb
│ │ ├── document.rb
│ │ ├── entry.rb
│ │ ├── firm.rb
│ │ ├── folder.rb
│ │ ├── group.rb
│ │ ├── mongoid/
│ │ │ ├── address.rb
│ │ │ ├── category.rb
│ │ │ ├── comment.rb
│ │ │ ├── company.rb
│ │ │ ├── entry.rb
│ │ │ ├── post.rb
│ │ │ └── user.rb
│ │ ├── newspaper.rb
│ │ ├── page.rb
│ │ ├── person.rb
│ │ ├── pet.rb
│ │ ├── post.rb
│ │ ├── relationship.rb
│ │ ├── reply.rb
│ │ ├── role.rb
│ │ ├── student.rb
│ │ ├── submission.rb
│ │ ├── teacher.rb
│ │ ├── user.rb
│ │ └── writer.rb
│ ├── spec_helper.rb
│ └── support/
│ ├── bullet_ext.rb
│ ├── mongo_seed.rb
│ ├── rack_double.rb
│ └── sqlite_seed.rb
├── tasks/
│ └── bullet_tasks.rake
├── test.sh
└── update.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
================================================
FILE: .github/workflows/close_inactive_issues.yml
================================================
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v10
with:
days-before-issue-stale: 30
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/main.yml
================================================
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test_rails_4:
runs-on: ubuntu-latest
strategy:
matrix:
gemfile: ['Gemfile.rails-4.2']
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler: 1
bundler-cache: true
- name: Run tests
run: bundle exec rake
test_rails_5:
runs-on: ubuntu-latest
strategy:
matrix:
gemfile: ['Gemfile.rails-5.0', 'Gemfile.rails-5.1', 'Gemfile.rails-5.2']
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler: 1
bundler-cache: true
- name: Run tests
run: bundle exec rake
test_rails_6:
runs-on: ubuntu-latest
strategy:
matrix:
gemfile: ['Gemfile.rails-6.0', 'Gemfile.rails-6.1']
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
- name: Run tests
run: bundle exec rake
test_rails_7:
runs-on: ubuntu-latest
strategy:
matrix:
gemfile: ['Gemfile.rails-7.0', 'Gemfile.rails-7.1', 'Gemfile.rails-7.2']
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1
bundler-cache: true
- name: Run tests
run: bundle exec rake
test_rails_8:
runs-on: ubuntu-latest
strategy:
matrix:
gemfile: [ 'Gemfile.rails-8.0', 'Gemfile.rails-8.1' ]
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Run tests
run: bundle exec rake
================================================
FILE: .gitignore
================================================
log/**
pkg/**
.DS_Store
lib/.DS_Store
.*.swp
coverage.data
tags
.bundle
*.gem
benchmark_profile*
/nbproject/private/
coverage/
.coveralls.yml
Gemfile*.lock
.idea/
.vscode/
================================================
FILE: .rspec
================================================
--colour
--format progress
================================================
FILE: CHANGELOG.md
================================================
## Next Release
## 8.1.0 (10/23/2025)
* Make `get_relation` private
* Support Rails 8.1
## 8.0.8 (05/30/2025)
* Add middleware after initializers
* Fix bullet composite primary key retrieval
## 8.0.7 (05/15/2025)
* Try to insert `Bullet::Rack` properly
## 8.0.6 (05/07/2025)
* Add CSP nonce for footer styles as well
* Add support for OpenTelemetry reporting
## 8.0.5 (04/21/2025)
* Properly insert ContentSecurityPolicy middleware
* Properly parse query string
## 8.0.4 (04/18/2025)
* Insert bullet middleware before `ContentSecurityPolicy`
* Support url query `skip_html_injection=true`
* Mark object as impossible after updating inversed
## 8.0.3 (04/04/2025)
* Update non persisted `inversed_objects`
## 8.0.2 (04/02/2025)
* Do not cache `bullet_key` if object is not persisted
## 8.0.1 (02/10/2025)
* Update benchmark to use sqlite
* Reduce mem allocations
* Require active_support's inflections module before requiring the delegation module
## 8.0.0 (11/10/2024)
* Support Rails 8
* Drop Rails 4.0 and 4.1 support
* Require Ruby at least 2.7.0
* Store global objects into thread-local variables
* Avoid globally polluting `::String` and `::Object` classes
## 7.2.0 (07/12/2024)
* Support Rails 7.2
* Fix count method signature for active_record5 and active_record60
## 7.1.6 (01/16/2024)
* Allow apps to not include the user in a notification
## 7.1.5 (01/05/2024)
* Fix mongoid8
## 7.1.4 (11/17/2023)
* Call association also on through reflection
## 7.1.3 (11/05/2023)
* Call NPlusOneQuery's call_association when calling count on collection association
## 7.1.2 (10/13/2023)
* Handle Rails 7.1 composite primary keys
## 7.1.1 (10/07/2023)
* Add support for `Content-Security-Policy-Report-Only` nonces
* Fix count method signature
## 7.1.0 (10/06/2023)
* Support rails 7.1
* Alias `Bullet.enable?` to `enabled?`, and `Bullet.enable=` to `enabled=`
* Added `always_append_html_body` option, so the html snippet is always included even if there are no notifications
* Added detection of n+1 count queries from `count` method
* Changed the counter cache notification title to recommend using `size`
## 7.0.7 (03/01/2023)
* Check `Rails.application.config.content_security_policy` before insert `Bullet::Rack`
## 7.0.6 (03/01/2023)
* Better way to check if `ActionDispatch::ContentSecurityPolicy::Middleware` exists
## 7.0.5 (01/01/2023)
* Fix n+1 false positives in AR 7.0
* Fix eager_load nested has_many :through false positives
* Respect Content-Security-Policy nonces
* Added CallStacks support for avoid eager loading
* Iterate fewer times over objects
## 7.0.4 (11/28/2022)
* Fix `eager_load` `has_many :through` false positives
* mongoid7x: add dynamic methods
## 7.0.3 (08/13/2022)
* Replace `Array()` with `Array.wrap()`
## 7.0.2 (05/31/2022)
* Drop growl support
* Do not check html tag in Bullet::Rack anymore
## 7.0.1 (01/15/2022)
* Get rid of *_whitelist methods
* Hack ActiveRecord::Associations::Preloader::Batch in rails 7
## 7.0.0 (12/18/2021)
* Support rails 7
* Fix Mongoid 7 view iteration
* Move CI from Travis to Github Actions
## 6.1.5 (08/16/2021)
* Rename whitelist to safelist
* Fix onload called twice
* Support Rack::Files::Iterator responses
* Ensure HABTM associations are not incorrectly labeled n+1
## 6.1.4 (02/26/2021)
* Added an option to stop adding HTTP headers to API requests
## 6.1.3 (01/21/2021)
* Consider ThroughAssociation at SingularAssociation like CollectionAssociation
* Add xhr_script only when add_footer is enabled
## 6.1.2 (12/12/2020)
* Revert "Make whitelist thread safe"
## 6.1.1 (12/12/2020)
* Add support Rails 6.1
* Make whitelist thread safe
## 6.1.0 (12/28/2019)
* Add skip_html_injection flag
* Remove writer hack in active_record6
* Use modern includes syntax in warnings
* Fix warning: The last argument is used as the keyword parameter
## 6.0.2 (08/20/2019)
* Fully support Rails 6.0
## 6.0.1 (06/26/2019)
* Add Bullet::ActiveJob
* Prevent "Maximum call stack exceeded" errors when used with Turbolinks
## 6.0.0 (04/25/2019)
* Add XHR support to Bullet
* Support Rails 6.0
* Handle case where ID is manually set on unpersisted record
## 5.9.0 (11/11/2018)
* Require Ruby 2.3+
* Support Mongo 7.x
## 5.8.0 (10/29/2018)
* Fix through reflection for rails 5.x
* Fix false positive in after_save/after_create callbacks
* Don't trigger a preload error on "manual" preloads
* Avoid Bullet from making extra queries in mongoid6
* Support option for #first and #last on mongoid6.x
* Fix duplicate logs in mongoid 4.x and 5.x version
* Use caller for ruby 1.9 while caller_locations for 2.0+
* Extend stacktrace matching for sub-file precision
* Exclude configured bundler path in addition to '/vendor'
* Fix `caller_path` in `excluded_stacktrace_path`
* Update `uniform_notifier` dependency to add Sentry support
* Integrate awesomecode.io and refactor code
## 5.7.0 (12/03/2017)
* Support rails 5.2
* Implement Bullet.delete_whitelist to delete a specific whitelist definition
* Fix caller_path in the case of nil
## 5.6.0 (07/16/2017)
* Migrate alias_method to Module#prepend
* Add install generator
* Stack trace filter
* Fix rails 5.1 compatibility
* Fix inverse_of for rails 5
* Fix detect file attachment for rack #319
## 5.5.0 (12/30/2016)
* Display http request method #311
* Add close button to footer
* Raise an error if bullet does not support AR or Mongoid
* Avoid double backtrace
* Fix false alert on counter cache when associations are already loaded #288
* Fix "false alert" in rails 5 #239
* Do not support ActiveRecord 3.x and Mongoid 3.x / 4.x anymore
## 5.4.0 (10/09/2016)
* Support rails 5.1
* Extract stack trace filtering into module
## 5.3.0 (15/08/2016)
* Fix false alert on through association with join sql #301
* Fix association.target in `through_association` can be singular #302
* Support `find_by_sql` #303
* Fix env `REQUEST_URI`
## 5.2.0 (07/26/2016)
* Fix `has_cached_counter?` is not defined in HABTM #297
* Fix false alert if preloaded association has no records #260
* Support Rails 5.0.0
## 5.1.0 (05/21/2016)
* Fix false alert when `empty?` used with `counter_cache`
* Fix `alias_method_chain` deprecation for rails 5
* Add response handling for non-Rails Rack responses
* Fix false alert when querying immediately after creation
* Fix UnusedEagerLoading bug when multiple eager loading query include same objects
## 5.0.0 (01/06/2016)
* Support Rails 5.0.0.beta1
* Fix `has_many :through` infinite loop issue
* Support mongoid 5.0.0
* Do not report association queries immediately after object creation to
require a preload
* Detect `counter_cache` for `has_many :through` association
* Compatible with `composite_primary_keys` gem
* Fix AR 4.2 SingularAssociation#reader result can be nil
* `perform_out_of_channel_notifications` should always be triggered
* Fix false positive with `belongs_to` -> `belongs_to` for active\_record 4.2
* Activate active\_record hacks only when Bullet already start
* Don't execute query when running `to_sql`
* Send backtrace to `uniform_notifier`
* Fix sse response check
* Dynamically delegate available notifiers to UniformNotifier
* Hotfix nil object when `add_impossible_object`
* Fix `has_one` then `has_many` associations in rails 4.2
* Append js and dom to html body in proper position
## 4.14.0 (10/03/2014)
* Support rails 4.2
* Polish notification output
* Fix warning: `*' interpreted as argument prefix
## 4.13.0 (07/19/2014)
* Support include? call on ar associations
## 4.12.0 (07/13/2014)
* Fix false n+1 queries caused by inversed objects.
* Replace .id with .primary_key_value
* Rename bullet_ar_key to bullet_key
* Fix rails sse detect
* Fix bullet using in test environment
* Memoize whoami
## 4.11.0 (06/24/2014)
* Support empty? call on ar associations
* Skip detecting if object is a new record
## 4.10.0 (06/06/2014)
* Handle join query smarter
* Support mongoid 4.0
* Thread safe
* Add debug mode
## 4.9.0 (04/30/2014)
* Add Bullet.stacktrace_includes option
* Applied keyword argument fixes on Ruby 2.2.0
* Add bugsnag notifier
* Support rails 4.1.0
## 4.8.0 (02/16/2014)
* Support rails 4.1.0.beta1
* Update specs to be RSpec 3.0 compatible
* Update latest minor version activerecord and mongoid on travis
## 4.7.0 (11/03/2013)
* Add coverall support
* Add helper to profile code outside a request
* Add activesupport dependency
* Add Bullet.raise notification
* Add Bullet.add_footer notification
* Fix activerecord4 warnings in test code
## 4.6.0 (04/18/2013)
* Fix Bullet::Rack to support sinatra
## 4.5.0 (03/24/2013)
* Add api way to access captured association
* Allow disable n_plus_one_query, unused_eager_loading and counter_cache respectively
* Add whitelist
## 4.4.0 (03/15/2013)
* Remove disable_browser_cache option
* Compatible with Rails 4.0.0.beta1
## 4.3.0 (12/28/2012)
* Fix content-length for non ascii html
* Add mongoid 2.5.x support
## 4.2.0 (09/29/2012)
* Add Bullet::Dependency to check AR and mongoid version
* Add Rails 4 support
* Add airbrake notifier support
## 4.1.0 (05/30/2012)
* Add mongoid 3 support
## 4.0.0 (05/09/2012)
* Add mongoid support
================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'
git_source(:github) do |repo_name|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/')
"https://github.com/#{repo_name}.git"
end
gemspec
gem 'rails', github: 'rails'
gem 'sqlite3', platforms: [:ruby]
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem 'rspec'
gem 'guard'
gem 'guard-rspec'
gem 'coveralls', require: false
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.mongoid
================================================
source "https://rubygems.org"
gemspec
gem 'rails'
gem 'sqlite3', platforms: [:ruby]
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'mongoid', github: 'mongoid/mongoid'
gem "rspec"
gem 'coveralls', require: false
================================================
FILE: Gemfile.mongoid-4.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 4.0.0'
gem 'sqlite3', platforms: [:ruby]
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'mongoid', '~> 4.0.0'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.mongoid-5.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 4.0.0'
gem 'sqlite3', platforms: [:ruby]
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'mongoid', '~> 5.1.0'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.mongoid-6.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 5.0.0'
gem 'sqlite3', platforms: [:ruby]
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'mongoid', '~> 6.0.0'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.mongoid-7.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 5.0'
gem 'sqlite3', platforms: [:ruby]
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'mongoid', '~> 7.0.0'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.mongoid-8.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 6.1'
gem 'sqlite3', platforms: [:ruby]
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'mongoid', '~> 8.0'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.mongoid-9.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 8.0'
gem 'sqlite3'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'mongoid', '~> 9.0'
gem 'rspec'
================================================
FILE: Gemfile.rails-4.2
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 4.2.0'
gem 'sqlite3', '~> 1.3.6'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem 'tins', '~> 1.6.0', platforms: [:ruby_19]
gem 'bigdecimal', '~> 1.4'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.rails-5.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 5.0.0'
gem 'sqlite3', '~> 1.3.6'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.rails-5.1
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 5.1.0'
gem 'sqlite3', '~> 1.3.6'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.rails-5.2
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 5.2.0'
gem 'sqlite3', '~> 1.3.6'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.rails-6.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 6.0.0'
gem 'sqlite3'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.rails-6.1
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 6.1.0'
gem 'sqlite3'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
platforms :rbx do
gem 'rubysl', '~> 2.0'
gem 'rubinius-developer_tools'
end
================================================
FILE: Gemfile.rails-7.0
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 7.0.0'
gem 'sqlite3', '~> 1.4'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
================================================
FILE: Gemfile.rails-7.1
================================================
source "https://rubygems.org"
gemspec
gem 'rails', '~> 7.1.0'
gem 'sqlite3', '~> 1.4'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
================================================
FILE: Gemfile.rails-7.2
================================================
source "https://rubygems.org"
gemspec
gem "rails", "~> 7.2.0"
gem 'sqlite3'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
================================================
FILE: Gemfile.rails-8.0
================================================
source "https://rubygems.org"
gemspec
gem "rails", "~> 8.0.0"
gem 'sqlite3'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
================================================
FILE: Gemfile.rails-8.1
================================================
source "https://rubygems.org"
gemspec
gem "rails", "~> 8.1.0"
gem 'sqlite3'
gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
gem 'activerecord-import'
gem "rspec"
================================================
FILE: Guardfile
================================================
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'rspec', version: 2, all_after_pass: false, all_on_start: false, cli: '--color --format nested --fail-fast' do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { 'spec' }
end
================================================
FILE: Hacking.md
================================================
# Bullet Overview for Developers
This file aims to give developers a quick tour of the bullet internals, making
it (hopefully) easier to extend or enhance the Bullet gem.
## General Control Flow aka. 10000 Meter View
When Rails is initialized, Bullet will extend ActiveRecord (and if you're using
Rails 2.x ActiveController too) with the relevant modules and methods found
in lib/bullet/active_recordX.rb and lib/bullet/action_controller2.rb. If you're
running Rails 3, Bullet will integrate itself as a middleware into the Rack
stack, so ActionController does not need to be extended.
The ActiveRecord extensions will call methods in a given detector class, when
certain methods are called.
Detector classes contain all the logic to recognize
a noteworthy event. If such an event is detected, an instance of the
corresponding Notification class is created and stored in a Set instance in the
main Bullet module (the 'notification collector').
Notification instances contain the message that will be displayed, and will
use a Presenter class to display their message to the user.
So the flow of a request goes like this:
1. Bullet.start_request is called, which resets all the detectors and empties
the notification collector
2. The request is handled by Rails, and the installed ActiveRecord extensions
trigger Detector callbacks
3. Detectors once called, will determine whether something noteworthy happened.
If yes, then a Notification is created and stored in the notification collector.
4. Rails finishes handling the request
5. For each notification in the collector, Bullet will iterate over each
Presenter and will try to generate an inline message that will be appended to
the generated response body.
6. The response is returned to the client.
7. Bullet will try to generate an out-of-channel message for each notification.
8. Bullet calls end_request for each detector.
9. Goto 1.
## Adding Notification Types
If you want to add more kinds of things that Bullet can detect, a little more
work is needed than if you were just adding a Presenter, but the concepts are
similar.
* Add the class to the DETECTORS constant in the main Bullet module
* Add (if needed) Rails monkey patches to Bullet.enable
* Add an autoload directive to lib/bullet/detector.rb
* Create a corresponding notification class in the Bullet::Notification namespace
* Add an autoload directive to lib/bullet/notification.rb
As a rule of thumb, you can assume that each Detector will have its own
Notification class. If you follow the principle of Separation of Concerns I
can't really think of an example where one would deviate from this rule.
Since the detection of pathological associations is a bit hairy, I'd recommend
having a look at the counter cache detector and associated notification to get
a feel for what is needed to get off the ground.
### Detectors
The only things you'll need to consider when building your Detector class is
that it will need to supply the .start_request, .end_request and .clear class
methods.
Simple implementations are provided by Bullet::Detector::Base for start_request
and end_request, you will have to supply your own clear method.
### Notifications
For notifications you will want to supply a #title and #body instance method,
and check to see if the #initialize and #full_notice methods in the
Bullet::Notification::Base class fit your needs.
================================================
FILE: MIT-LICENSE
================================================
Copyright (c) 2009 - 2024 Richard Huang (flyerhzm@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Bullet

[](http://badge.fury.io/rb/bullet)
[](https://awesomecode.io/repos/flyerhzm/bullet)
[](https://coderwall.com/flyerhzm)
The Bullet gem is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries), when you're using eager loading that isn't necessary and when you should use counter cache.
Best practice is to use Bullet in development mode or custom mode (staging, profile, etc.). The last thing you want is your clients getting alerts about how lazy you are.
Bullet gem now supports **activerecord** >= 4.0 and **mongoid** >= 4.0.
If you use activerecord 2.x, please use bullet <= 4.5.0
If you use activerecord 3.x, please use bullet < 5.5.0
## External Introduction
* [https://rubyonrails.org/2009/10/22/community-highlights](https://rubyonrails.org/2009/10/22/community-highlights)
* [https://www.shakacode.com/blog/eliminate-N-1-queries-with-bullet/](https://www.shakacode.com/blog/eliminate-N-1-queries-with-bullet/)
* [https://reinteractive.com/articles/ruby-on-rails-community/elevating-your-code-quality-bullet-gem-and-query-prevention-in-testing](https://reinteractive.com/articles/ruby-on-rails-community/elevating-your-code-quality-bullet-gem-and-query-prevention-in-testing)
## Install
You can install it as a gem:
```
gem install bullet
```
or add it into a Gemfile (Bundler):
```ruby
gem 'bullet', group: 'development'
```
enable the Bullet gem with generate command
```ruby
bundle exec rails g bullet:install
```
The generate command will auto generate the default configuration and may ask to include in the test environment as well. See below for custom configuration.
**Note**: make sure `bullet` gem is added after activerecord (rails) and
mongoid.
## Configuration
Bullet won't enable any notification systems unless you tell it to explicitly. Append to
`config/environments/development.rb` initializer with the following code:
```ruby
config.after_initialize do
Bullet.enable = true
Bullet.sentry = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
:password => 'bullets_password_for_jabber',
:receiver => 'your_account@jabber.org',
:show_online_status => true }
Bullet.rails_logger = true
Bullet.honeybadger = true
Bullet.bugsnag = true
Bullet.appsignal = true
Bullet.airbrake = true
Bullet.rollbar = true
Bullet.add_footer = true
Bullet.skip_html_injection = false
Bullet.skip_http_headers = false
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
Bullet.opentelemetry = true
Bullet.raise = false
Bullet.always_append_html_body = false
Bullet.skip_user_in_notification = false
end
```
The notifier of Bullet is a wrap of [uniform_notifier](https://github.com/flyerhzm/uniform_notifier)
The code above will enable all of the Bullet notification systems:
* `Bullet.enable`: enable Bullet gem, otherwise do nothing
* `Bullet.sentry`: add notifications to sentry
* `Bullet.alert`: pop up a JavaScript alert in the browser
* `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)
* `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
* `Bullet.xmpp`: send XMPP/Jabber notifications to the receiver indicated. Note that the code will currently not handle the adding of contacts, so you will need to make both accounts indicated know each other manually before you will receive any notifications. If you restart the development server frequently, the 'coming online' sound for the Bullet account may start to annoy - in this case set :show_online_status to false; you will still get notifications, but the Bullet account won't announce it's online status anymore.
* `Bullet.rails_logger`: add warnings directly to the Rails log
* `Bullet.honeybadger`: add notifications to Honeybadger
* `Bullet.bugsnag`: add notifications to bugsnag
* `Bullet.appsignal`: add notifications to AppSignal
* `Bullet.airbrake`: add notifications to airbrake
* `Bullet.rollbar`: add notifications to rollbar
* `Bullet.add_footer`: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
* `Bullet.skip_html_injection`: prevents Bullet from injecting code into the returned HTML. This must be false for receiving alerts, showing the footer or console logging.
* `Bullet.skip_http_headers`: don't add headers to API requests, and remove the javascript that relies on them. Note that this prevents bullet from logging warnings to the browser console or updating the footer.
* `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
* `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.
Each item can be a string (match substring), a regex, or an array where the first item is a path to match, and the second
item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file.
* `Bullet.slack`: add notifications to slack
* `Bullet.opentelemetry`: add notifications to OpenTelemetry
* `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries
* `Bullet.always_append_html_body`: always append the html body even if no notifications are present. Note: `console` or `add_footer` must also be true. Useful for Single Page Applications where the initial page load might not have any notifications present.
* `Bullet.skip_user_in_notification`: exclude the OS user (`whoami`) from notifications.
Bullet also allows you to disable any of its detectors.
```ruby
# Each of these settings defaults to true
# Detect N+1 queries
Bullet.n_plus_one_query_enable = false
# Detect eager-loaded associations which are not used
Bullet.unused_eager_loading_enable = false
# Detect unnecessary COUNT queries which could be avoided
# with a counter_cache
Bullet.counter_cache_enable = false
```
## Safe list
Sometimes Bullet may notify you of query problems you don't care to fix, or
which come from outside your code. You can add them to a safe list to ignore them:
```ruby
Bullet.add_safelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
Bullet.add_safelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
Bullet.add_safelist :type => :counter_cache, :class_name => "Country", :association => :cities
```
If you want to skip bullet in some specific controller actions, you can
do like
```ruby
class ApplicationController < ActionController::Base
around_action :skip_bullet, if: -> { defined?(Bullet) }
def skip_bullet
previous_value = Bullet.enable?
Bullet.enable = false
yield
ensure
Bullet.enable = previous_value
end
end
```
## Log
The Bullet log `log/bullet.log` will look something like this:
* N+1 Query:
```
2009-08-25 20:40:17[INFO] USE eager loading detected:
Post => [:comments]·
Add to your query: .includes([:comments])
2009-08-25 20:40:17[INFO] Call stack
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
```
The first log entry is a notification that N+1 queries have been encountered. The remaining entry is a stack trace so you can find exactly where the queries were invoked in your code, and fix them.
* Unused eager loading:
```
2009-08-25 20:53:56[INFO] AVOID eager loading detected
Post => [:comments]·
Remove from your query: .includes([:comments])
2009-08-25 20:53:56[INFO] Call stack
```
These lines are notifications that unused eager loadings have been encountered.
* Need counter cache:
```
2009-09-11 09:46:50[INFO] Need Counter Cache
Post => [:comments]
```
## XMPP/Jabber and Airbrake Support
see [https://github.com/flyerhzm/uniform_notifier](https://github.com/flyerhzm/uniform_notifier)
## Growl Support
Growl support is dropped from uniform_notifier 1.16.0, if you still want it, please use uniform_notifier 1.15.0.
## URL query control
You can add the URL query parameter `skip_html_injection` to make the current HTML request behave as if `Bullet.skip_html_injection` is enabled,
e.g. `http://localhost:3000/posts?skip_html_injection=true`
## Important
If you find Bullet does not work for you, *please disable your browser's cache*.
## Advanced
### Work with ActiveJob
Include `Bullet::ActiveJob` in your `ApplicationJob`.
```ruby
class ApplicationJob < ActiveJob::Base
include Bullet::ActiveJob if Rails.env.development?
end
```
### Work with other background job solution
Use the Bullet.profile method.
```ruby
class ApplicationJob < ActiveJob::Base
around_perform do |_job, block|
Bullet.profile do
block.call
end
end
end
```
### Work with sinatra
Configure and use `Bullet::Rack`.
```ruby
configure :development do
Bullet.enable = true
Bullet.bullet_logger = true
use Bullet::Rack
end
```
If your application generates a Content-Security-Policy via a separate middleware, ensure that `Bullet::Rack` is loaded _before_ that middleware.
### Run in tests
First you need to enable Bullet in the test environment.
```ruby
# config/environments/test.rb
config.after_initialize do
Bullet.enable = true
Bullet.bullet_logger = true
Bullet.raise = true # raise an error if n+1 query occurs
end
```
Then wrap each test in the Bullet api.
With RSpec:
```ruby
# spec/rails_helper.rb
RSpec.configure do |config|
config.before(:each) do
Bullet.start_request
end
config.after(:each) do
Bullet.perform_out_of_channel_notifications if Bullet.notification?
Bullet.end_request
end
end
```
With Minitest:
```ruby
# test/test_helper.rb
module ActiveSupport
class TestCase
def before_setup
Bullet.start_request
super
end
def after_teardown
super
Bullet.perform_out_of_channel_notifications if Bullet.notification?
Bullet.end_request
end
end
end
```
## Debug Mode
Bullet outputs some details info, to enable debug mode, set
`BULLET_DEBUG=true` env.
## Contributors
[https://github.com/flyerhzm/bullet/contributors](https://github.com/flyerhzm/bullet/contributors)
## Demo
Bullet is designed to function as you browse through your application in development. To see it in action,
you can follow these steps to create, detect, and fix example query problems.
1\. Create an example application
```
$ rails new test_bullet
$ cd test_bullet
$ rails g scaffold post name:string
$ rails g scaffold comment name:string post_id:integer
$ bundle exec rails db:migrate
```
2\. Change `app/models/post.rb` and `app/models/comment.rb`
```ruby
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
end
```
3\. Go to `rails c` and execute
```ruby
post1 = Post.create(:name => 'first')
post2 = Post.create(:name => 'second')
post1.comments.create(:name => 'first')
post1.comments.create(:name => 'second')
post2.comments.create(:name => 'third')
post2.comments.create(:name => 'fourth')
```
4\. Change the `app/views/posts/index.html.erb` to produce a N+1 query
```
<% @posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.comments.map(&:name) %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
```
5\. Add the `bullet` gem to the `Gemfile`
```ruby
gem "bullet"
```
And run
```
bundle install
```
6\. enable the Bullet gem with generate command
```
bundle exec rails g bullet:install
```
7\. Start the server
```
$ rails s
```
8\. Visit `http://localhost:3000/posts` in browser, and you will see a popup alert box that says
```
The request has unused preload associations as follows:
None
The request has N+1 queries as follows:
model: Post => associations: [comment]
```
which means there is a N+1 query from the Post object to its Comment association.
In the meantime, there's a log appended into `log/bullet.log` file
```
2010-03-07 14:12:18[INFO] N+1 Query in /posts
Post => [:comments]
Add to your finder: :include => [:comments]
2010-03-07 14:12:18[INFO] N+1 Query method call stack
/home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:14:in `_render_template__600522146_80203160_0'
/home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `each'
/home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `_render_template__600522146_80203160_0'
/home/flyerhzm/Downloads/test_bullet/app/controllers/posts_controller.rb:7:in `index'
```
The generated SQL is:
```
Post Load (1.0ms) SELECT * FROM "posts"
Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments".post_id = 1)
Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments".post_id = 2)
```
9\. To fix the N+1 query, change `app/controllers/posts_controller.rb` file
```ruby
def index
@posts = Post.includes(:comments)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
end
```
10\. Refresh `http://localhost:3000/posts`. Now there's no alert box and nothing new in the log.
The generated SQL is:
```
Post Load (0.5ms) SELECT * FROM "posts"
Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id IN (1,2))
```
N+1 query fixed. Cool!
11\. Now simulate unused eager loading. Change
`app/controllers/posts_controller.rb` and
`app/views/posts/index.html.erb`
```ruby
def index
@posts = Post.includes(:comments)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
end
```
```
<% @posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
```
12\. Refresh `http://localhost:3000/posts`, and you will see a popup alert box that says
```
The request has unused preload associations as follows:
model: Post => associations: [comment]
The request has N+1 queries as follows:
None
```
Meanwhile, there's a line appended to `log/bullet.log`
```
2009-08-25 21:13:22[INFO] Unused preload associations: PATH_INFO: /posts; model: Post => associations: [comments]·
Remove from your finder: :include => [:comments]
```
13\. Simulate counter_cache. Change `app/controllers/posts_controller.rb`
and `app/views/posts/index.html.erb`
```ruby
def index
@posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
end
```
```
<% @posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.comments.size %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
```
14\. Refresh `http://localhost:3000/posts`, then you will see a popup alert box that says
```
Need counter cache
Post => [:comments]
```
Meanwhile, there's a line appended to `log/bullet.log`
```
2009-09-11 10:07:10[INFO] Need Counter Cache
Post => [:comments]
```
Copyright (c) 2009 - 2022 Richard Huang (flyerhzm@gmail.com), released under the MIT license
================================================
FILE: Rakefile
================================================
$LOAD_PATH.unshift File.expand_path('lib', __dir__)
require 'bundler'
Bundler.setup
require 'rake'
require 'rspec'
require 'rspec/core/rake_task'
require 'bullet/version'
task :build do
system 'gem build bullet.gemspec'
end
task install: :build do
system "sudo gem install bullet-#{Bullet::VERSION}.gem"
end
task release: :build do
puts "Tagging #{Bullet::VERSION}..."
system "git tag -a #{Bullet::VERSION} -m 'Tagging #{Bullet::VERSION}'"
puts 'Pushing to Github...'
system 'git push --tags'
puts 'Pushing to rubygems.org...'
system "gem push bullet-#{Bullet::VERSION}.gem"
end
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
end
RSpec::Core::RakeTask.new('spec:progress') do |spec|
spec.rspec_opts = %w[--format progress]
spec.pattern = 'spec/**/*_spec.rb'
end
begin
require 'rdoc/task'
desc 'Generate documentation for the plugin.'
Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "bullet #{Bullet::VERSION}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
rescue LoadError
puts 'RDocTask is not supported for this platform'
end
task default: :spec
================================================
FILE: bullet.gemspec
================================================
# frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
require 'bullet/version'
Gem::Specification.new do |s|
s.name = 'bullet'
s.version = Bullet::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ['Richard Huang']
s.email = ['flyerhzm@gmail.com']
s.homepage = 'https://github.com/flyerhzm/bullet'
s.summary = 'help to kill N+1 queries and unused eager loading.'
s.description = 'help to kill N+1 queries and unused eager loading.'
s.metadata = {
'changelog_uri' => 'https://github.com/flyerhzm/bullet/blob/main/CHANGELOG.md',
'source_code_uri' => 'https://github.com/flyerhzm/bullet'
}
s.license = 'MIT'
s.required_ruby_version = '>= 2.7.0'
s.required_rubygems_version = '>= 1.3.6'
s.add_runtime_dependency 'activesupport', '>= 3.0.0'
s.add_runtime_dependency 'uniform_notifier', '~> 1.11'
s.files = Dir.chdir(__dir__) do
`git ls-files -z`.split("\x0").reject do |file|
file.start_with?(*%w[.git .rspec Gemfile Guardfile Hacking Rakefile
bullet.gemspec perf rails spec test.sh update.sh])
end
end
s.require_paths = ['lib']
end
================================================
FILE: lib/bullet/active_job.rb
================================================
# frozen_string_literal: true
module Bullet
module ActiveJob
def self.included(base)
base.class_eval do
around_perform do |_job, block|
Bullet.profile { block.call }
end
end
end
end
end
================================================
FILE: lib/bullet/active_record4.rb
================================================
# frozen_string_literal: true
module Bullet
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.class_eval do
class << self
alias_method :origin_find_by_sql, :find_by_sql
def find_by_sql(sql, binds = [])
result = origin_find_by_sql(sql, binds)
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
end
::ActiveRecord::Relation.class_eval do
alias_method :origin_to_a, :to_a
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def to_a
records = origin_to_a
if Bullet.start?
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
records
end
end
::ActiveRecord::Persistence.class_eval do
def _create_record_with_bullet(*args)
_create_record_without_bullet(*args).tap do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
end
end
alias_method_chain :_create_record, :bullet
end
::ActiveRecord::Associations::Preloader.class_eval do
# include query for one to many associations.
# keep this eager loadings.
alias_method :origin_initialize, :initialize
def initialize(records, associations, preload_scope = nil)
origin_initialize(records, associations, preload_scope)
if Bullet.start?
records = [records].flatten.compact.uniq
return if records.empty?
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
end
end
end
::ActiveRecord::FinderMethods.class_eval do
# add includes in scope
alias_method :origin_find_with_associations, :find_with_associations
def find_with_associations
records = origin_find_with_associations
if Bullet.start?
associations = (eager_load_values + includes_values).uniq
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
end
records
end
end
::ActiveRecord::Associations::JoinDependency.class_eval do
alias_method :origin_instantiate, :instantiate
alias_method :origin_construct_association, :construct_association
def instantiate(rows)
@bullet_eager_loadings = {}
records = origin_instantiate(rows)
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
end
end
records
end
# call join associations
def construct_association(record, join, row)
result = origin_construct_association(record, join, row)
if Bullet.start?
associations = [join.reflection.name]
if join.reflection.nested?
associations << join.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
::ActiveRecord::Associations::CollectionAssociation.class_eval do
# call one to many associations
alias_method :origin_load_target, :load_target
def load_target
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
origin_load_target
end
alias_method :origin_include?, :include?
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
origin_include?(object)
end
end
::ActiveRecord::Associations::HasManyAssociation.class_eval do
alias_method :origin_empty?, :empty?
def empty?
if Bullet.start? && !loaded? && !has_cached_counter?(@reflection)
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
end
origin_empty?
end
end
::ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
alias_method :origin_empty?, :empty?
def empty?
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start? && !loaded?
origin_empty?
end
end
::ActiveRecord::Associations::SingularAssociation.class_eval do
# call has_one and belongs_to associations
alias_method :origin_reader, :reader
def reader(force_reload = false)
result = origin_reader(force_reload)
if Bullet.start?
unless @inversed
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
end
end
result
end
end
::ActiveRecord::Associations::HasManyAssociation.class_eval do
alias_method :origin_has_cached_counter?, :has_cached_counter?
def has_cached_counter?(reflection = reflection())
result = origin_has_cached_counter?(reflection)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) if Bullet.start? && !result
result
end
end
::ActiveRecord::Associations::CollectionProxy.class_eval do
def count(column_name = nil, options = {})
if Bullet.start?
Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)
Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)
end
super(column_name, options)
end
end
end
end
end
================================================
FILE: lib/bullet/active_record41.rb
================================================
# frozen_string_literal: true
module Bullet
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.class_eval do
class << self
alias_method :origin_find_by_sql, :find_by_sql
def find_by_sql(sql, binds = [])
result = origin_find_by_sql(sql, binds)
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
end
::ActiveRecord::Relation.class_eval do
alias_method :origin_to_a, :to_a
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def to_a
records = origin_to_a
if Bullet.start?
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
end
::ActiveRecord::Persistence.class_eval do
def _create_record_with_bullet(*args)
_create_record_without_bullet(*args).tap do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
end
end
alias_method_chain :_create_record, :bullet
end
::ActiveRecord::Associations::Preloader.class_eval do
alias_method :origin_preloaders_on, :preloaders_on
def preloaders_on(association, records, scope)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
end
end
origin_preloaders_on(association, records, scope)
end
end
::ActiveRecord::FinderMethods.class_eval do
# add includes in scope
alias_method :origin_find_with_associations, :find_with_associations
def find_with_associations
return origin_find_with_associations { |r| yield r } if block_given?
records = origin_find_with_associations
if Bullet.start?
associations = (eager_load_values + includes_values).uniq
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
end
records
end
end
::ActiveRecord::Associations::JoinDependency.class_eval do
alias_method :origin_instantiate, :instantiate
alias_method :origin_construct_model, :construct_model
def instantiate(result_set, aliases)
@bullet_eager_loadings = {}
records = origin_instantiate(result_set, aliases)
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
end
end
records
end
# call join associations
def construct_model(record, node, row, model_cache, id, aliases)
result = origin_construct_model(record, node, row, model_cache, id, aliases)
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.nested?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
::ActiveRecord::Associations::CollectionAssociation.class_eval do
# call one to many associations
alias_method :origin_load_target, :load_target
def load_target
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start? && !@inversed
origin_load_target
end
alias_method :origin_empty?, :empty?
def empty?
if Bullet.start? && !has_cached_counter?(@reflection)
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
end
origin_empty?
end
alias_method :origin_include?, :include?
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
origin_include?(object)
end
end
::ActiveRecord::Associations::SingularAssociation.class_eval do
# call has_one and belongs_to associations
alias_method :origin_reader, :reader
def reader(force_reload = false)
result = origin_reader(force_reload)
if Bullet.start?
if @owner.class.name !~ /^HABTM_/ && !@inversed
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
end
end
result
end
end
::ActiveRecord::Associations::HasManyAssociation.class_eval do
alias_method :origin_count_records, :count_records
def count_records
result = has_cached_counter?
Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) if Bullet.start? && !result
origin_count_records
end
end
::ActiveRecord::Associations::CollectionProxy.class_eval do
def count(column_name = nil, options = {})
if Bullet.start?
Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)
Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)
end
super(column_name, options)
end
end
end
end
end
================================================
FILE: lib/bullet/active_record42.rb
================================================
# frozen_string_literal: true
module Bullet
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.class_eval do
class << self
alias_method :origin_find, :find
def find(*args)
result = origin_find(*args)
if Bullet.start?
if result.is_a? Array
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
alias_method :origin_find_by_sql, :find_by_sql
def find_by_sql(sql, binds = [])
result = origin_find_by_sql(sql, binds)
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
end
::ActiveRecord::Persistence.class_eval do
def _create_record_with_bullet(*args)
_create_record_without_bullet(*args).tap do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
end
end
alias_method_chain :_create_record, :bullet
end
::ActiveRecord::Relation.class_eval do
alias_method :origin_to_a, :to_a
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def to_a
records = origin_to_a
if Bullet.start?
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
end
::ActiveRecord::Associations::Preloader.class_eval do
alias_method :origin_preloaders_on, :preloaders_on
def preloaders_on(association, records, scope)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
end
end
origin_preloaders_on(association, records, scope)
end
end
::ActiveRecord::FinderMethods.class_eval do
# add includes in scope
alias_method :origin_find_with_associations, :find_with_associations
def find_with_associations
return origin_find_with_associations { |r| yield r } if block_given?
records = origin_find_with_associations
if Bullet.start?
associations = (eager_load_values + includes_values).uniq
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
end
records
end
end
::ActiveRecord::Associations::JoinDependency.class_eval do
alias_method :origin_instantiate, :instantiate
alias_method :origin_construct, :construct
alias_method :origin_construct_model, :construct_model
def instantiate(result_set, aliases)
@bullet_eager_loadings = {}
records = origin_instantiate(result_set, aliases)
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
end
end
records
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.nested?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
origin_construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
end
# call join associations
def construct_model(record, node, row, model_cache, id, aliases)
result = origin_construct_model(record, node, row, model_cache, id, aliases)
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.nested?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
::ActiveRecord::Associations::CollectionAssociation.class_eval do
# call one to many associations
alias_method :origin_load_target, :load_target
def load_target
records = origin_load_target
if Bullet.start?
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
alias_method :origin_empty?, :empty?
def empty?
if Bullet.start? && !has_cached_counter?(@reflection)
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
end
origin_empty?
end
alias_method :origin_include?, :include?
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
origin_include?(object)
end
end
::ActiveRecord::Associations::SingularAssociation.class_eval do
# call has_one and belongs_to associations
alias_method :origin_reader, :reader
def reader(force_reload = false)
result = origin_reader(force_reload)
if Bullet.start?
if @owner.class.name !~ /^HABTM_/ && !@inversed
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
::ActiveRecord::Associations::HasManyAssociation.class_eval do
alias_method :origin_many_empty?, :empty?
def empty?
result = origin_many_empty?
if Bullet.start? && !has_cached_counter?(@reflection)
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
end
result
end
alias_method :origin_count_records, :count_records
def count_records
result = has_cached_counter?
Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) if Bullet.start? && !result
origin_count_records
end
end
::ActiveRecord::Associations::CollectionProxy.class_eval do
def count(column_name = nil, options = {})
if Bullet.start?
Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)
Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)
end
super(column_name, options)
end
end
end
end
end
================================================
FILE: lib/bullet/active_record5.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader.prepend(
Module.new do
def preloaders_for_one(association, records, scope)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
end
end
super
end
end
)
::ActiveRecord::FinderMethods.prepend(
Module.new do
# add includes in scope
def find_with_associations
return super { |r| yield r } if block_given?
records = super
if Bullet.start?
associations = (eager_load_values + includes_values).uniq
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
end
records
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
if ::ActiveRecord::Associations::JoinDependency.instance_method(:instantiate).parameters.last[0] == :block
# ActiveRecord >= 5.1.5
def instantiate(result_set, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
else
# ActiveRecord <= 5.1.4
def instantiate(result_set, aliases)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, aliases)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
refl = reflection.through_reflection
association = owner.association(refl.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if refl.through_reflection?
refl = refl.through_reflection while refl.through_reflection?
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def target
result = super()
if Bullet.start?
if owner.class.name !~ /^HABTM_/ && !@inversed
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record52.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader.prepend(
Module.new do
def preloaders_for_one(association, records, scope)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
end
end
super
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, aliases)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def target
result = super()
if Bullet.start?
if owner.class.name !~ /^HABTM_/ && !@inversed
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association reflection.through_reflection.name
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record60.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader.prepend(
Module.new do
def preloaders_for_one(association, records, scope, polymorphic_parent)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
end
end
super
end
def preloaders_for_reflection(reflection, records, scope)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def preloaded_records
if Bullet.start? && !defined?(@preloaded_records)
source_preloaders.each do |source_preloader|
reflection_name = source_preloader.send(:reflection).name
source_preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
end
super
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, seen, model_cache)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def target
result = super()
if Bullet.start?
if owner.class.name !~ /^HABTM_/ && !@inversed
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record61.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader.prepend(
Module.new do
def preloaders_for_one(association, records, scope, polymorphic_parent)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
end
end
super
end
def preloaders_for_reflection(reflection, records, scope)
if Bullet.start?
records.compact!
if records.first.class.name !~ /^HABTM_/
records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def preloaded_records
if Bullet.start? && !defined?(@preloaded_records)
source_preloaders.each do |source_preloader|
reflection_name = source_preloader.send(:reflection).name
source_preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
end
super
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, strict_loading_value, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, strict_loading_value)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def target
result = super()
if Bullet.start?
if owner.class.name !~ /^HABTM_/ && !@inversed
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record70.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader::Batch.prepend(
Module.new do
def call
if Bullet.start?
@preloaders.each do |preloader|
preloader.records.each { |record|
Bullet::Detector::Association.add_object_associations(record, preloader.associations)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::Branch.prepend(
Module.new do
def preloaders_for_reflection(reflection, reflection_records)
if Bullet.start?
reflection_records.compact!
if reflection_records.first.class.name !~ /^HABTM_/
reflection_records.each { |record|
Bullet::Detector::Association.add_object_associations(record, reflection.name)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def source_preloaders
if Bullet.start? && !defined?(@source_preloaders)
preloaders = super
preloaders.each do |preloader|
reflection_name = preloader.send(:reflection).name
preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
else
super
end
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, strict_loading_value, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, strict_loading_value)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
def inversed_from_queries(record)
if Bullet.start? && inversable?(record)
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def reader
result = super
if Bullet.start?
if owner.class.name !~ /^HABTM_/
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record71.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader::Batch.prepend(
Module.new do
def call
if Bullet.start?
@preloaders.each do |preloader|
preloader.records.each { |record|
Bullet::Detector::Association.add_object_associations(record, preloader.associations)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::Branch.prepend(
Module.new do
def preloaders_for_reflection(reflection, reflection_records)
if Bullet.start?
reflection_records.compact!
if reflection_records.first.class.name !~ /^HABTM_/
reflection_records.each { |record|
Bullet::Detector::Association.add_object_associations(record, reflection.name)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def source_preloaders
if Bullet.start? && !defined?(@source_preloaders)
preloaders = super
preloaders.each do |preloader|
reflection_name = preloader.send(:reflection).name
preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
else
super
end
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, strict_loading_value, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, strict_loading_value)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
def inversed_from_queries(record)
if Bullet.start? && inversable?(record)
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def reader
result = super
if Bullet.start?
if owner.class.name !~ /^HABTM_/
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record72.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader::Batch.prepend(
Module.new do
def call
if Bullet.start?
@preloaders.each do |preloader|
preloader.records.each { |record|
Bullet::Detector::Association.add_object_associations(record, preloader.associations)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::Branch.prepend(
Module.new do
def preloaders_for_reflection(reflection, reflection_records)
if Bullet.start?
reflection_records.compact!
if reflection_records.first.class.name !~ /^HABTM_/
reflection_records.each { |record|
Bullet::Detector::Association.add_object_associations(record, reflection.name)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def source_preloaders
if Bullet.start? && !defined?(@source_preloaders)
preloaders = super
preloaders.each do |preloader|
reflection_name = preloader.send(:reflection).name
preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
else
super
end
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, strict_loading_value, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, strict_loading_value)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
def inversed_from_queries(record)
if Bullet.start? && inversable?(record)
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def reader
result = super
if Bullet.start?
if owner.class.name !~ /^HABTM_/
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record80.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader::Batch.prepend(
Module.new do
def call
if Bullet.start?
@preloaders.each do |preloader|
preloader.records.each { |record|
Bullet::Detector::Association.add_object_associations(record, preloader.associations)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::Branch.prepend(
Module.new do
def preloaders_for_reflection(reflection, reflection_records)
if Bullet.start?
reflection_records.compact!
if reflection_records.first.class.name !~ /^HABTM_/
reflection_records.each { |record|
Bullet::Detector::Association.add_object_associations(record, reflection.name)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def source_preloaders
if Bullet.start? && !defined?(@source_preloaders)
preloaders = super
preloaders.each do |preloader|
reflection_name = preloader.send(:reflection).name
preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
else
super
end
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, strict_loading_value, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, strict_loading_value)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
def inversed_from_queries(record)
if Bullet.start? && inversable?(record)
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def reader
result = super
if Bullet.start?
if owner.class.name !~ /^HABTM_/
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/active_record81.rb
================================================
# frozen_string_literal: true
module Bullet
module SaveWithBulletSupport
def _create_record(*)
super do
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
yield(self) if block_given?
end
end
end
module ActiveRecord
def self.enable
require 'active_record'
::ActiveRecord::Base.extend(
Module.new do
def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
result = super
if Bullet.start?
if result.is_a? Array
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
elsif result.is_a? ::ActiveRecord::Base
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
Bullet::Detector::CounterCache.add_impossible_object(result)
end
end
result
end
end
)
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
::ActiveRecord::Relation.prepend(
Module.new do
# if select a collection of objects, then these objects have possible to cause N+1 query.
# if select only one object, then the only one object has impossible to cause N+1 query.
def records
result = super
if Bullet.start?
if result.first.class.name !~ /^HABTM_/
if result.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
Bullet::Detector::CounterCache.add_possible_objects(result)
elsif result.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
Bullet::Detector::CounterCache.add_impossible_object(result.first)
end
end
end
result
end
end
)
::ActiveRecord::Associations::Preloader::Batch.prepend(
Module.new do
def call
if Bullet.start?
@preloaders.each do |preloader|
preloader.records.each { |record|
Bullet::Detector::Association.add_object_associations(record, preloader.associations)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::Branch.prepend(
Module.new do
def preloaders_for_reflection(reflection, reflection_records)
if Bullet.start?
reflection_records.compact!
if reflection_records.first.class.name !~ /^HABTM_/
reflection_records.each { |record|
Bullet::Detector::Association.add_object_associations(record, reflection.name)
}
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
end
end
super
end
end
)
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
Module.new do
def source_preloaders
if Bullet.start? && !defined?(@source_preloaders)
preloaders = super
preloaders.each do |preloader|
reflection_name = preloader.send(:reflection).name
preloader.send(:owners).each do |owner|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
end
end
else
super
end
end
end
)
::ActiveRecord::Associations::JoinDependency.prepend(
Module.new do
def instantiate(result_set, strict_loading_value, &block)
@bullet_eager_loadings = {}
records = super
if Bullet.start?
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
objects = eager_loadings_hash.keys
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
objects,
eager_loadings_hash[objects.first].to_a
)
end
end
records
end
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
if Bullet.start?
unless ar_parent.nil?
parent.children.each do |node|
key = aliases.column_alias(node, node.primary_key)
id = row[key]
next unless id.nil?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(ar_parent, association)
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
@bullet_eager_loadings[ar_parent.class] ||= {}
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
end
end
end
end
super
end
# call join associations
def construct_model(record, node, row, model_cache, id, strict_loading_value)
result = super
if Bullet.start?
associations = [node.reflection.name]
if node.reflection.through_reflection?
associations << node.reflection.through_reflection.name
end
associations.each do |association|
Bullet::Detector::Association.add_object_associations(record, association)
Bullet::Detector::NPlusOneQuery.call_association(record, association)
@bullet_eager_loadings[record.class] ||= {}
@bullet_eager_loadings[record.class][record] ||= Set.new
@bullet_eager_loadings[record.class][record] << association
end
end
result
end
end
)
::ActiveRecord::Associations::Association.prepend(
Module.new do
def inversed_from(record)
if Bullet.start?
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
def inversed_from_queries(record)
if Bullet.start? && inversable?(record)
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionAssociation.prepend(
Module.new do
def load_target
records = super
if Bullet.start?
if is_a? ::ActiveRecord::Associations::ThroughAssociation
association = owner.association(reflection.through_reflection.name)
if association.loaded?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if records.first.class.name !~ /^HABTM_/
if records.size > 1
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
Bullet::Detector::CounterCache.add_possible_objects(records)
elsif records.size == 1
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
Bullet::Detector::CounterCache.add_impossible_object(records.first)
end
end
end
records
end
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def include?(object)
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
super
end
end
)
::ActiveRecord::Associations::SingularAssociation.prepend(
Module.new do
# call has_one and belongs_to associations
def reader
result = super
if Bullet.start?
if owner.class.name !~ /^HABTM_/
if is_a? ::ActiveRecord::Associations::ThroughAssociation
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
association = owner.association(reflection.through_reflection.name)
Array.wrap(association.target).each do |through_record|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
end
if reflection.through_reflection != through_reflection
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
end
end
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
else
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
end
end
end
result
end
end
)
::ActiveRecord::Associations::HasManyAssociation.prepend(
Module.new do
def empty?
if Bullet.start? && !reflection.has_cached_counter?
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
end
super
end
def count_records
result = reflection.has_cached_counter?
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
end
super
end
end
)
::ActiveRecord::Associations::CollectionProxy.prepend(
Module.new do
def count(column_name = nil)
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
Bullet::Detector::CounterCache.add_counter_cache(
proxy_association.owner,
proxy_association.reflection.name
)
Bullet::Detector::NPlusOneQuery.call_association(
proxy_association.owner,
proxy_association.reflection.name,
caller_locations
)
end
super(column_name)
end
end
)
end
end
end
================================================
FILE: lib/bullet/bullet_xhr.js
================================================
(function () {
var oldOpen = window.XMLHttpRequest.prototype.open;
var oldSend = window.XMLHttpRequest.prototype.send;
/**
* Return early if we've already extended prototype. This prevents
* "maximum call stack exceeded" errors when used with Turbolinks.
* See https://github.com/flyerhzm/bullet/issues/454
*/
if (isBulletInitiated()) return;
function isBulletInitiated() {
return oldOpen.name == "bulletXHROpen" && oldSend.name == "bulletXHRSend";
}
function bulletXHROpen(_, url) {
this._storedUrl = url;
return Reflect.apply(oldOpen, this, arguments);
}
function bulletXHRSend() {
if (this.onload) {
this._storedOnload = this.onload;
}
this.onload = null;
this.addEventListener("load", bulletXHROnload);
return Reflect.apply(oldSend, this, arguments);
}
function bulletXHROnload() {
if (
this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
!this._storedUrl.startsWith("http") // For relative paths
) {
var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
if (bulletFooterText) {
setTimeout(function () {
var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
var header = oldHtml[0];
oldHtml = oldHtml.slice(1, oldHtml.length);
var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
document.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
}, 0);
}
var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
if (bulletConsoleText && typeof console !== "undefined" && console.log) {
setTimeout(function () {
JSON.parse(bulletConsoleText).forEach((message) => {
if (console.groupCollapsed && console.groupEnd) {
console.groupCollapsed("Uniform Notifier");
console.log(message);
console.groupEnd();
} else {
console.log(message);
}
});
}, 0);
}
}
if (this._storedOnload) {
return Reflect.apply(this._storedOnload, this, arguments);
}
}
window.XMLHttpRequest.prototype.open = bulletXHROpen;
window.XMLHttpRequest.prototype.send = bulletXHRSend;
})();
================================================
FILE: lib/bullet/dependency.rb
================================================
# frozen_string_literal: true
module Bullet
module Dependency
def mongoid?
@mongoid ||= defined?(::Mongoid)
end
def active_record?
@active_record ||= defined?(::ActiveRecord)
end
def active_record_version
@active_record_version ||=
begin
if active_record40?
'active_record4'
elsif active_record41?
'active_record41'
elsif active_record42?
'active_record42'
elsif active_record50?
'active_record5'
elsif active_record51?
'active_record5'
elsif active_record52?
'active_record52'
elsif active_record60?
'active_record60'
elsif active_record61?
'active_record61'
elsif active_record70?
'active_record70'
elsif active_record71?
'active_record71'
elsif active_record72?
'active_record72'
elsif active_record80?
'active_record80'
elsif active_record81?
'active_record81'
else
raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
end
end
end
def mongoid_version
@mongoid_version ||=
begin
if mongoid4x?
'mongoid4x'
elsif mongoid5x?
'mongoid5x'
elsif mongoid6x?
'mongoid6x'
elsif mongoid7x?
'mongoid7x'
elsif mongoid8x?
'mongoid8x'
elsif mongoid9x?
'mongoid9x'
else
raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
end
end
end
def active_record4?
active_record? && ::ActiveRecord::VERSION::MAJOR == 4
end
def active_record5?
active_record? && ::ActiveRecord::VERSION::MAJOR == 5
end
def active_record6?
active_record? && ::ActiveRecord::VERSION::MAJOR == 6
end
def active_record7?
active_record? && ::ActiveRecord::VERSION::MAJOR == 7
end
def active_record8?
active_record? && ::ActiveRecord::VERSION::MAJOR == 8
end
def active_record40?
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
end
def active_record41?
active_record4? && ::ActiveRecord::VERSION::MINOR == 1
end
def active_record42?
active_record4? && ::ActiveRecord::VERSION::MINOR == 2
end
def active_record50?
active_record5? && ::ActiveRecord::VERSION::MINOR == 0
end
def active_record51?
active_record5? && ::ActiveRecord::VERSION::MINOR == 1
end
def active_record52?
active_record5? && ::ActiveRecord::VERSION::MINOR == 2
end
def active_record60?
active_record6? && ::ActiveRecord::VERSION::MINOR == 0
end
def active_record61?
active_record6? && ::ActiveRecord::VERSION::MINOR == 1
end
def active_record70?
active_record7? && ::ActiveRecord::VERSION::MINOR == 0
end
def active_record71?
active_record7? && ::ActiveRecord::VERSION::MINOR == 1
end
def active_record72?
active_record7? && ::ActiveRecord::VERSION::MINOR == 2
end
def active_record80?
active_record8? && ::ActiveRecord::VERSION::MINOR == 0
end
def active_record81?
active_record8? && ::ActiveRecord::VERSION::MINOR == 1
end
def mongoid4x?
mongoid? && ::Mongoid::VERSION =~ /\A4/
end
def mongoid5x?
mongoid? && ::Mongoid::VERSION =~ /\A5/
end
def mongoid6x?
mongoid? && ::Mongoid::VERSION =~ /\A6/
end
def mongoid7x?
mongoid? && ::Mongoid::VERSION =~ /\A7/
end
def mongoid8x?
mongoid? && ::Mongoid::VERSION =~ /\A8/
end
def mongoid9x?
mongoid? && ::Mongoid::VERSION =~ /\A9/
end
end
end
================================================
FILE: lib/bullet/detector/association.rb
================================================
# frozen_string_literal: true
using Bullet::Ext::Object
module Bullet
module Detector
class Association < Base
class << self
def add_object_associations(object, associations)
return unless Bullet.start?
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
return unless object.bullet_primary_key_value
Bullet.debug(
'Detector::Association#add_object_associations',
"object: #{object.bullet_key}, associations: #{associations}"
)
call_stacks.add(object.bullet_key)
object_associations.add(object.bullet_key, associations)
end
def add_call_object_associations(object, associations)
return unless Bullet.start?
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
return unless object.bullet_primary_key_value
Bullet.debug(
'Detector::Association#add_call_object_associations',
"object: #{object.bullet_key}, associations: #{associations}"
)
call_stacks.add(object.bullet_key)
call_object_associations.add(object.bullet_key, associations)
end
# possible_objects keep the class to object relationships
# that the objects may cause N+1 query.
# e.g. { Post => ["Post:1", "Post:2"] }
def possible_objects
Thread.current.thread_variable_get(:bullet_possible_objects)
end
# impossible_objects keep the class to objects relationships
# that the objects may not cause N+1 query.
# e.g. { Post => ["Post:1", "Post:2"] }
# if find collection returns only one object, then the object is impossible object,
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
def impossible_objects
Thread.current.thread_variable_get(:bullet_impossible_objects)
end
private
# object_associations keep the object relationships
# that the object has many associations.
# e.g. { "Post:1" => [:comments] }
# the object_associations keep all associations that may be or may no be
# unpreload associations or unused preload associations.
def object_associations
Thread.current.thread_variable_get(:bullet_object_associations)
end
# call_object_associations keep the object relationships
# that object.associations is called.
# e.g. { "Post:1" => [:comments] }
# they are used to detect unused preload associations.
def call_object_associations
Thread.current.thread_variable_get(:bullet_call_object_associations)
end
# inversed_objects keeps object relationships
# that association is inversed.
# e.g. { "Comment:1" => ["post"] }
def inversed_objects
Thread.current.thread_variable_get(:bullet_inversed_objects)
end
# eager_loadings keep the object relationships
# that the associations are preloaded by find :include.
# e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
def eager_loadings
Thread.current.thread_variable_get(:bullet_eager_loadings)
end
# call_stacks keeps stacktraces where querie-objects were called from.
# e.g. { 'Object:111' => [SomeProject/app/controllers/...] }
def call_stacks
Thread.current.thread_variable_get(:bullet_call_stacks)
end
end
end
end
end
================================================
FILE: lib/bullet/detector/base.rb
================================================
# frozen_string_literal: true
module Bullet
module Detector
class Base
end
end
end
================================================
FILE: lib/bullet/detector/counter_cache.rb
================================================
# frozen_string_literal: true
using Bullet::Ext::Object
module Bullet
module Detector
class CounterCache < Base
class << self
def add_counter_cache(object, associations)
return unless Bullet.start?
return unless Bullet.counter_cache_enable?
return unless object.bullet_primary_key_value
Bullet.debug(
'Detector::CounterCache#add_counter_cache',
"object: #{object.bullet_key}, associations: #{associations}"
)
create_notification object.class.to_s, associations if conditions_met?(object, associations)
end
def add_possible_objects(object_or_objects)
return unless Bullet.start?
return unless Bullet.counter_cache_enable?
objects = Array.wrap(object_or_objects)
return if objects.map(&:bullet_primary_key_value).compact.empty?
Bullet.debug(
'Detector::CounterCache#add_possible_objects',
"objects: #{objects.map(&:bullet_key).join(', ')}"
)
objects.each { |object| possible_objects.add object.bullet_key }
end
def add_impossible_object(object)
return unless Bullet.start?
return unless Bullet.counter_cache_enable?
return unless object.bullet_primary_key_value
Bullet.debug('Detector::CounterCache#add_impossible_object', "object: #{object.bullet_key}")
impossible_objects.add object.bullet_key
end
def conditions_met?(object, _associations)
possible_objects.include?(object.bullet_key) && !impossible_objects.include?(object.bullet_key)
end
def possible_objects
Thread.current.thread_variable_get(:bullet_counter_possible_objects)
end
def impossible_objects
Thread.current.thread_variable_get(:bullet_counter_impossible_objects)
end
private
def create_notification(klazz, associations)
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
if notify_associations.present?
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
Bullet.notification_collector.add notice
end
end
end
end
end
end
================================================
FILE: lib/bullet/detector/n_plus_one_query.rb
================================================
# frozen_string_literal: true
using Bullet::Ext::Object
module Bullet
module Detector
class NPlusOneQuery < Association
extend Dependency
extend StackTraceFilter
class << self
# executed when object.associations is called.
# first, it keeps this method call for object.association.
# then, it checks if this associations call is unpreload.
# if it is, keeps this unpreload associations and caller.
def call_association(object, associations, caller_stack = nil)
return unless Bullet.start?
return unless Bullet.n_plus_one_query_enable?
return unless object.bullet_primary_key_value
return if inversed_objects.include?(object.bullet_key, associations)
add_call_object_associations(object, associations)
call_stacks.add(object.bullet_key, caller_stack) if caller_stack
Bullet.debug(
'Detector::NPlusOneQuery#call_association',
"object: #{object.bullet_key}, associations: #{associations}"
)
if !excluded_stacktrace_path? && conditions_met?(object, associations)
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
create_notification(caller_in_project(object.bullet_key), object.class.to_s, associations)
end
end
def add_possible_objects(object_or_objects)
return unless Bullet.start?
return unless Bullet.n_plus_one_query_enable?
objects = Array.wrap(object_or_objects)
class_names_match_regex = true
primary_key_values_are_empty = true
keys_joined = objects.map do |obj|
unless obj.class.name =~ /^HABTM_/
class_names_match_regex = false
end
unless obj.bullet_primary_key_value.nil?
primary_key_values_are_empty = false
end
obj.bullet_key
end.join(", ")
unless class_names_match_regex || primary_key_values_are_empty
Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{keys_joined}")
objects.each { |object| possible_objects.add object.bullet_key }
end
end
def add_impossible_object(object)
return unless Bullet.start?
return unless Bullet.n_plus_one_query_enable?
return unless object.bullet_primary_key_value
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
impossible_objects.add object.bullet_key
end
def add_inversed_object(object, association)
return unless Bullet.start?
return unless Bullet.n_plus_one_query_enable?
object_key = object.bullet_primary_key_value ? object.bullet_key : object.object_id
Bullet.debug(
'Detector::NPlusOneQuery#add_inversed_object',
"object: #{object_key}, association: #{association}"
)
inversed_objects.add object_key, association
end
def update_inversed_object(object)
if inversed_objects&.key?(object.object_id)
Bullet.debug(
'Detector::NPlusOneQuery#update_inversed_object',
"object from #{object.object_id} to #{object.bullet_key}"
)
inversed_objects.add(object.bullet_key, inversed_objects[object.object_id].to_a)
end
end
# decide whether the object.associations is unpreloaded or not.
def conditions_met?(object, associations)
possible?(object) && !impossible?(object) && !association?(object, associations)
end
def possible?(object)
possible_objects.include? object.bullet_key
end
def impossible?(object)
impossible_objects.include? object.bullet_key
end
# check if object => associations already exists in object_associations.
def association?(object, associations)
value = object_associations[object.bullet_key]
value&.each do |v|
# associations == v comparison order is important here because
# v variable might be a squeel node where :== method is redefined,
# so it does not compare values at all and return unexpected results
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
return true if result
end
false
end
private
def create_notification(callers, klazz, associations)
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
if notify_associations.present?
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
Bullet.notification_collector.add(notice)
end
end
end
end
end
end
================================================
FILE: lib/bullet/detector/unused_eager_loading.rb
================================================
# frozen_string_literal: true
using Bullet::Ext::Object
using Bullet::Ext::String
module Bullet
module Detector
class UnusedEagerLoading < Association
extend Dependency
extend StackTraceFilter
class << self
# check if there are unused preload associations.
# get related_objects from eager_loadings associated with object and associations
# get call_object_association from associations of call_object_associations whose object is in related_objects
# if association not in call_object_association, then the object => association - call_object_association is unused preload associations
def check_unused_preload_associations
return unless Bullet.start?
return unless Bullet.unused_eager_loading_enable?
object_associations.each do |bullet_key, associations|
object_association_diff = diff_object_associations bullet_key, associations
next if object_association_diff.empty?
Bullet.debug('detect unused preload', "object: #{bullet_key}, associations: #{object_association_diff}")
create_notification(caller_in_project(bullet_key), bullet_key.bullet_class_name, object_association_diff)
end
end
def add_eager_loadings(objects, associations)
return unless Bullet.start?
return unless Bullet.unused_eager_loading_enable?
return if objects.map(&:bullet_primary_key_value).compact.empty?
Bullet.debug(
'Detector::UnusedEagerLoading#add_eager_loadings',
"objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}"
)
bullet_keys = objects.map(&:bullet_key)
to_add = []
to_merge = []
to_delete = []
eager_loadings.each do |k, _v|
key_objects_overlap = k & bullet_keys
next if key_objects_overlap.empty?
bullet_keys -= k
if key_objects_overlap == k
to_add << [k, associations]
else
to_merge << [key_objects_overlap, (eager_loadings[k].dup << associations)]
keys_without_objects = k - key_objects_overlap
to_merge << [keys_without_objects, eager_loadings[k]]
to_delete << k
end
end
to_add.each { |k, val| eager_loadings.add k, val }
to_merge.each { |k, val| eager_loadings.merge k, val }
to_delete.each { |k| eager_loadings.delete k }
eager_loadings.add bullet_keys, associations unless bullet_keys.empty?
end
private
def create_notification(callers, klazz, associations)
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(
:unused_eager_loading,
klazz
)
if notify_associations.present?
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
Bullet.notification_collector.add(notice)
end
end
def call_associations(bullet_key, associations)
all = Set.new
eager_loadings.similarly_associated(bullet_key, associations).each do |related_bullet_key|
coa = call_object_associations[related_bullet_key]
gitextract_njyn2xoi/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── close_inactive_issues.yml │ └── main.yml ├── .gitignore ├── .rspec ├── CHANGELOG.md ├── Gemfile ├── Gemfile.mongoid ├── Gemfile.mongoid-4.0 ├── Gemfile.mongoid-5.0 ├── Gemfile.mongoid-6.0 ├── Gemfile.mongoid-7.0 ├── Gemfile.mongoid-8.0 ├── Gemfile.mongoid-9.0 ├── Gemfile.rails-4.2 ├── Gemfile.rails-5.0 ├── Gemfile.rails-5.1 ├── Gemfile.rails-5.2 ├── Gemfile.rails-6.0 ├── Gemfile.rails-6.1 ├── Gemfile.rails-7.0 ├── Gemfile.rails-7.1 ├── Gemfile.rails-7.2 ├── Gemfile.rails-8.0 ├── Gemfile.rails-8.1 ├── Guardfile ├── Hacking.md ├── MIT-LICENSE ├── README.md ├── Rakefile ├── bullet.gemspec ├── lib/ │ ├── bullet/ │ │ ├── active_job.rb │ │ ├── active_record4.rb │ │ ├── active_record41.rb │ │ ├── active_record42.rb │ │ ├── active_record5.rb │ │ ├── active_record52.rb │ │ ├── active_record60.rb │ │ ├── active_record61.rb │ │ ├── active_record70.rb │ │ ├── active_record71.rb │ │ ├── active_record72.rb │ │ ├── active_record80.rb │ │ ├── active_record81.rb │ │ ├── bullet_xhr.js │ │ ├── dependency.rb │ │ ├── detector/ │ │ │ ├── association.rb │ │ │ ├── base.rb │ │ │ ├── counter_cache.rb │ │ │ ├── n_plus_one_query.rb │ │ │ └── unused_eager_loading.rb │ │ ├── detector.rb │ │ ├── ext/ │ │ │ ├── object.rb │ │ │ └── string.rb │ │ ├── mongoid4x.rb │ │ ├── mongoid5x.rb │ │ ├── mongoid6x.rb │ │ ├── mongoid7x.rb │ │ ├── mongoid8x.rb │ │ ├── mongoid9x.rb │ │ ├── notification/ │ │ │ ├── base.rb │ │ │ ├── counter_cache.rb │ │ │ ├── n_plus_one_query.rb │ │ │ └── unused_eager_loading.rb │ │ ├── notification.rb │ │ ├── notification_collector.rb │ │ ├── rack.rb │ │ ├── registry/ │ │ │ ├── association.rb │ │ │ ├── base.rb │ │ │ ├── call_stack.rb │ │ │ └── object.rb │ │ ├── registry.rb │ │ ├── stack_trace_filter.rb │ │ └── version.rb │ ├── bullet.rb │ └── generators/ │ └── bullet/ │ └── install_generator.rb ├── perf/ │ └── benchmark.rb ├── rails/ │ └── init.rb ├── spec/ │ ├── bullet/ │ │ ├── detector/ │ │ │ ├── association_spec.rb │ │ │ ├── base_spec.rb │ │ │ ├── counter_cache_spec.rb │ │ │ ├── n_plus_one_query_spec.rb │ │ │ └── unused_eager_loading_spec.rb │ │ ├── ext/ │ │ │ ├── object_spec.rb │ │ │ └── string_spec.rb │ │ ├── notification/ │ │ │ ├── base_spec.rb │ │ │ ├── counter_cache_spec.rb │ │ │ ├── n_plus_one_query_spec.rb │ │ │ └── unused_eager_loading_spec.rb │ │ ├── notification_collector_spec.rb │ │ ├── rack_spec.rb │ │ ├── registry/ │ │ │ ├── association_spec.rb │ │ │ ├── base_spec.rb │ │ │ └── object_spec.rb │ │ └── stack_trace_filter_spec.rb │ ├── bullet_spec.rb │ ├── integration/ │ │ ├── active_record/ │ │ │ └── association_spec.rb │ │ ├── counter_cache_spec.rb │ │ └── mongoid/ │ │ └── association_spec.rb │ ├── models/ │ │ ├── address.rb │ │ ├── attachment.rb │ │ ├── author.rb │ │ ├── base_user.rb │ │ ├── category.rb │ │ ├── city.rb │ │ ├── client.rb │ │ ├── comment.rb │ │ ├── company.rb │ │ ├── country.rb │ │ ├── deal.rb │ │ ├── document.rb │ │ ├── entry.rb │ │ ├── firm.rb │ │ ├── folder.rb │ │ ├── group.rb │ │ ├── mongoid/ │ │ │ ├── address.rb │ │ │ ├── category.rb │ │ │ ├── comment.rb │ │ │ ├── company.rb │ │ │ ├── entry.rb │ │ │ ├── post.rb │ │ │ └── user.rb │ │ ├── newspaper.rb │ │ ├── page.rb │ │ ├── person.rb │ │ ├── pet.rb │ │ ├── post.rb │ │ ├── relationship.rb │ │ ├── reply.rb │ │ ├── role.rb │ │ ├── student.rb │ │ ├── submission.rb │ │ ├── teacher.rb │ │ ├── user.rb │ │ └── writer.rb │ ├── spec_helper.rb │ └── support/ │ ├── bullet_ext.rb │ ├── mongo_seed.rb │ ├── rack_double.rb │ └── sqlite_seed.rb ├── tasks/ │ └── bullet_tasks.rake ├── test.sh └── update.sh
SYMBOL INDEX (411 symbols across 103 files)
FILE: lib/bullet.rb
type Bullet (line 12) | module Bullet
class BulletRailtie (line 25) | class BulletRailtie < Rails::Railtie
function raise= (line 56) | def raise=(should_raise)
function enable= (line 66) | def enable=(enable)
function enable? (line 81) | def enable?
function app_root (line 88) | def app_root
function n_plus_one_query_enable? (line 92) | def n_plus_one_query_enable?
function unused_eager_loading_enable? (line 96) | def unused_eager_loading_enable?
function counter_cache_enable? (line 100) | def counter_cache_enable?
function stacktrace_includes (line 104) | def stacktrace_includes
function stacktrace_excludes (line 108) | def stacktrace_excludes
function add_safelist (line 112) | def add_safelist(options)
function delete_safelist (line 118) | def delete_safelist(options)
function get_safelist_associations (line 125) | def get_safelist_associations(type, class_name)
function reset_safelist (line 129) | def reset_safelist
function clear_safelist (line 133) | def clear_safelist
function bullet_logger= (line 137) | def bullet_logger=(active)
function debug (line 147) | def debug(title, message)
function start_request (line 151) | def start_request
function end_request (line 172) | def end_request
function start? (line 187) | def start?
function notification_collector (line 191) | def notification_collector
function notification? (line 195) | def notification?
function gather_inline_notifications (line 202) | def gather_inline_notifications
function perform_out_of_channel_notifications (line 208) | def perform_out_of_channel_notifications(env = {})
function footer_info (line 216) | def footer_info
function text_notifications (line 222) | def text_notifications
function warnings (line 230) | def warnings
function profile (line 238) | def profile
function console_enabled? (line 258) | def console_enabled?
function inject_into_page? (line 262) | def inject_into_page?
function for_each_active_notifier_with_notification (line 270) | def for_each_active_notifier_with_notification
function build_request_uri (line 279) | def build_request_uri(env)
FILE: lib/bullet/active_job.rb
type Bullet (line 3) | module Bullet
type ActiveJob (line 4) | module ActiveJob
function included (line 5) | def self.included(base)
FILE: lib/bullet/active_record4.rb
type Bullet (line 3) | module Bullet
type ActiveRecord (line 4) | module ActiveRecord
function enable (line 5) | def self.enable
FILE: lib/bullet/active_record41.rb
type Bullet (line 3) | module Bullet
type ActiveRecord (line 4) | module ActiveRecord
function enable (line 5) | def self.enable
FILE: lib/bullet/active_record42.rb
type Bullet (line 3) | module Bullet
type ActiveRecord (line 4) | module ActiveRecord
function enable (line 5) | def self.enable
FILE: lib/bullet/active_record5.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record52.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record60.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record61.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record70.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record71.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record72.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record80.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/active_record81.rb
type Bullet (line 3) | module Bullet
type SaveWithBulletSupport (line 4) | module SaveWithBulletSupport
function _create_record (line 5) | def _create_record(*)
type ActiveRecord (line 14) | module ActiveRecord
function enable (line 15) | def self.enable
FILE: lib/bullet/bullet_xhr.js
function isBulletInitiated (line 12) | function isBulletInitiated() {
function bulletXHROpen (line 15) | function bulletXHROpen(_, url) {
function bulletXHRSend (line 19) | function bulletXHRSend() {
function bulletXHROnload (line 27) | function bulletXHROnload() {
FILE: lib/bullet/dependency.rb
type Bullet (line 3) | module Bullet
type Dependency (line 4) | module Dependency
function mongoid? (line 5) | def mongoid?
function active_record? (line 9) | def active_record?
function active_record_version (line 13) | def active_record_version
function mongoid_version (line 48) | def mongoid_version
function active_record4? (line 69) | def active_record4?
function active_record5? (line 73) | def active_record5?
function active_record6? (line 77) | def active_record6?
function active_record7? (line 81) | def active_record7?
function active_record8? (line 85) | def active_record8?
function active_record40? (line 89) | def active_record40?
function active_record41? (line 93) | def active_record41?
function active_record42? (line 97) | def active_record42?
function active_record50? (line 101) | def active_record50?
function active_record51? (line 105) | def active_record51?
function active_record52? (line 109) | def active_record52?
function active_record60? (line 113) | def active_record60?
function active_record61? (line 117) | def active_record61?
function active_record70? (line 121) | def active_record70?
function active_record71? (line 125) | def active_record71?
function active_record72? (line 129) | def active_record72?
function active_record80? (line 133) | def active_record80?
function active_record81? (line 137) | def active_record81?
function mongoid4x? (line 141) | def mongoid4x?
function mongoid5x? (line 145) | def mongoid5x?
function mongoid6x? (line 149) | def mongoid6x?
function mongoid7x? (line 153) | def mongoid7x?
function mongoid8x? (line 157) | def mongoid8x?
function mongoid9x? (line 161) | def mongoid9x?
FILE: lib/bullet/detector.rb
type Bullet (line 3) | module Bullet
type Detector (line 4) | module Detector
FILE: lib/bullet/detector/association.rb
type Bullet (line 5) | module Bullet
type Detector (line 6) | module Detector
class Association (line 7) | class Association < Base
method add_object_associations (line 9) | def add_object_associations(object, associations)
method add_call_object_associations (line 22) | def add_call_object_associations(object, associations)
method possible_objects (line 38) | def possible_objects
method impossible_objects (line 47) | def impossible_objects
method object_associations (line 58) | def object_associations
method call_object_associations (line 66) | def call_object_associations
method inversed_objects (line 73) | def inversed_objects
method eager_loadings (line 80) | def eager_loadings
method call_stacks (line 86) | def call_stacks
FILE: lib/bullet/detector/base.rb
type Bullet (line 3) | module Bullet
type Detector (line 4) | module Detector
class Base (line 5) | class Base
FILE: lib/bullet/detector/counter_cache.rb
type Bullet (line 5) | module Bullet
type Detector (line 6) | module Detector
class CounterCache (line 7) | class CounterCache < Base
method add_counter_cache (line 9) | def add_counter_cache(object, associations)
method add_possible_objects (line 21) | def add_possible_objects(object_or_objects)
method add_impossible_object (line 35) | def add_impossible_object(object)
method conditions_met? (line 44) | def conditions_met?(object, _associations)
method possible_objects (line 48) | def possible_objects
method impossible_objects (line 52) | def impossible_objects
method create_notification (line 58) | def create_notification(klazz, associations)
FILE: lib/bullet/detector/n_plus_one_query.rb
type Bullet (line 5) | module Bullet
type Detector (line 6) | module Detector
class NPlusOneQuery (line 7) | class NPlusOneQuery < Association
method call_association (line 16) | def call_association(object, associations, caller_stack = nil)
method add_possible_objects (line 35) | def add_possible_objects(object_or_objects)
method add_impossible_object (line 59) | def add_impossible_object(object)
method add_inversed_object (line 68) | def add_inversed_object(object, association)
method update_inversed_object (line 80) | def update_inversed_object(object)
method conditions_met? (line 91) | def conditions_met?(object, associations)
method possible? (line 95) | def possible?(object)
method impossible? (line 99) | def impossible?(object)
method association? (line 104) | def association?(object, associations)
method create_notification (line 119) | def create_notification(callers, klazz, associations)
FILE: lib/bullet/detector/unused_eager_loading.rb
type Bullet (line 6) | module Bullet
type Detector (line 7) | module Detector
class UnusedEagerLoading (line 8) | class UnusedEagerLoading < Association
method check_unused_preload_associations (line 17) | def check_unused_preload_associations
method add_eager_loadings (line 30) | def add_eager_loadings(objects, associations)
method create_notification (line 70) | def create_notification(callers, klazz, associations)
method call_associations (line 82) | def call_associations(bullet_key, associations)
method diff_object_associations (line 93) | def diff_object_associations(bullet_key, associations)
FILE: lib/bullet/ext/object.rb
type Bullet (line 3) | module Bullet
type Ext (line 4) | module Ext
type Object (line 5) | module Object
function bullet_key (line 9) | def bullet_key
function bullet_primary_key_value (line 15) | def bullet_primary_key_value
function bullet_join_potential_composite_primary_key (line 28) | def bullet_join_potential_composite_primary_key(primary_keys)
FILE: lib/bullet/ext/string.rb
type Bullet (line 3) | module Bullet
type Ext (line 4) | module Ext
type String (line 5) | module String
function bullet_class_name (line 7) | def bullet_class_name
FILE: lib/bullet/mongoid4x.rb
type Bullet (line 3) | module Bullet
type Mongoid (line 4) | module Mongoid
function enable (line 5) | def self.enable
FILE: lib/bullet/mongoid5x.rb
type Bullet (line 3) | module Bullet
type Mongoid (line 4) | module Mongoid
function enable (line 5) | def self.enable
FILE: lib/bullet/mongoid6x.rb
type Bullet (line 3) | module Bullet
type Mongoid (line 4) | module Mongoid
function enable (line 5) | def self.enable
FILE: lib/bullet/mongoid7x.rb
type Bullet (line 3) | module Bullet
type Mongoid (line 4) | module Mongoid
function enable (line 5) | def self.enable
FILE: lib/bullet/mongoid8x.rb
type Bullet (line 1) | module Bullet
type Mongoid (line 2) | module Mongoid
function enable (line 3) | def self.enable
FILE: lib/bullet/mongoid9x.rb
type Bullet (line 3) | module Bullet
type Mongoid (line 4) | module Mongoid
function enable (line 5) | def self.enable
FILE: lib/bullet/notification.rb
type Bullet (line 3) | module Bullet
type Notification (line 4) | module Notification
class UnoptimizedQueryError (line 10) | class UnoptimizedQueryError < StandardError
FILE: lib/bullet/notification/base.rb
type Bullet (line 3) | module Bullet
type Notification (line 4) | module Notification
class Base (line 5) | class Base
method initialize (line 9) | def initialize(base_class, association_or_associations, path = nil)
method title (line 16) | def title
method body (line 20) | def body
method call_stack_messages (line 24) | def call_stack_messages
method whoami (line 28) | def whoami
method body_with_caller (line 41) | def body_with_caller
method notify_inline (line 45) | def notify_inline
method notify_out_of_channel (line 49) | def notify_out_of_channel
method short_notice (line 53) | def short_notice
method notification_data (line 63) | def notification_data
method eql? (line 72) | def eql?(other)
method hash (line 76) | def hash
method klazz_associations_str (line 82) | def klazz_associations_str
method associations_str (line 86) | def associations_str
FILE: lib/bullet/notification/counter_cache.rb
type Bullet (line 3) | module Bullet
type Notification (line 4) | module Notification
class CounterCache (line 5) | class CounterCache < Base
method body (line 6) | def body
method title (line 10) | def title
FILE: lib/bullet/notification/n_plus_one_query.rb
type Bullet (line 3) | module Bullet
type Notification (line 4) | module Notification
class NPlusOneQuery (line 5) | class NPlusOneQuery < Base
method initialize (line 6) | def initialize(callers, base_class, associations, path = nil)
method body (line 12) | def body
method title (line 16) | def title
method notification_data (line 20) | def notification_data
method call_stack_messages (line 26) | def call_stack_messages
FILE: lib/bullet/notification/unused_eager_loading.rb
type Bullet (line 3) | module Bullet
type Notification (line 4) | module Notification
class UnusedEagerLoading (line 5) | class UnusedEagerLoading < Base
method initialize (line 6) | def initialize(callers, base_class, associations, path = nil)
method body (line 12) | def body
method title (line 16) | def title
method notification_data (line 20) | def notification_data
method call_stack_messages (line 26) | def call_stack_messages
FILE: lib/bullet/notification_collector.rb
type Bullet (line 5) | module Bullet
class NotificationCollector (line 6) | class NotificationCollector
method initialize (line 9) | def initialize
method reset (line 13) | def reset
method add (line 17) | def add(value)
method notifications_present? (line 21) | def notifications_present?
FILE: lib/bullet/rack.rb
type Bullet (line 7) | module Bullet
class Rack (line 8) | class Rack
method initialize (line 13) | def initialize(app)
method call (line 17) | def call(env)
method empty? (line 53) | def empty?(response)
method append_to_html_body (line 62) | def append_to_html_body(response_body, content)
method footer_note (line 73) | def footer_note(nonce = nil)
method footer_style (line 78) | def footer_style(nonce = nil)
method set_header (line 91) | def set_header(headers, header_name, header_array)
method skip_html_injection? (line 99) | def skip_html_injection?(request)
method simple_parse_query_string (line 108) | def simple_parse_query_string(query_string)
method file? (line 117) | def file?(headers)
method sse? (line 121) | def sse?(headers)
method html_request? (line 125) | def html_request?(headers, response)
method response_body (line 129) | def response_body(response)
method footer_console_message (line 139) | def footer_console_message(nonce = nil)
method xhr_script (line 155) | def xhr_script(nonce = nil)
method with_security_policy_nonce (line 165) | def with_security_policy_nonce(headers)
FILE: lib/bullet/registry.rb
type Bullet (line 3) | module Bullet
type Registry (line 4) | module Registry
FILE: lib/bullet/registry/association.rb
type Bullet (line 3) | module Bullet
type Registry (line 4) | module Registry
class Association (line 5) | class Association < Base
method merge (line 6) | def merge(base, associations)
method similarly_associated (line 10) | def similarly_associated(base, associations)
FILE: lib/bullet/registry/base.rb
type Bullet (line 3) | module Bullet
type Registry (line 4) | module Registry
class Base (line 5) | class Base
method initialize (line 8) | def initialize
method [] (line 12) | def [](key)
method each (line 16) | def each(&block)
method delete (line 20) | def delete(base)
method select (line 24) | def select(*args, &block)
method add (line 28) | def add(key, value)
method include? (line 37) | def include?(key, value)
method key? (line 41) | def key?(key)
FILE: lib/bullet/registry/call_stack.rb
type Bullet (line 3) | module Bullet
type Registry (line 4) | module Registry
class CallStack (line 5) | class CallStack < Base
method add (line 8) | def add(key, backtrace = nil)
FILE: lib/bullet/registry/object.rb
type Bullet (line 6) | module Bullet
type Registry (line 7) | module Registry
class Object (line 8) | class Object < Base
method add (line 9) | def add(bullet_key)
method include? (line 13) | def include?(bullet_key)
FILE: lib/bullet/stack_trace_filter.rb
type Bullet (line 7) | module Bullet
type StackTraceFilter (line 8) | module StackTraceFilter
function caller_in_project (line 12) | def caller_in_project(bullet_key = nil)
function excluded_stacktrace_path? (line 24) | def excluded_stacktrace_path?
function pattern_matches? (line 32) | def pattern_matches?(location, pattern)
function location_as_path (line 55) | def location_as_path(location)
function select_caller_locations (line 61) | def select_caller_locations(bullet_key = nil)
FILE: lib/bullet/version.rb
type Bullet (line 3) | module Bullet
FILE: lib/generators/bullet/install_generator.rb
type Bullet (line 3) | module Bullet
type Generators (line 4) | module Generators
class InstallGenerator (line 5) | class InstallGenerator < ::Rails::Generators::Base
method enable_in_development (line 11) | def enable_in_development
method enable_in_test (line 29) | def enable_in_test
FILE: perf/benchmark.rb
class Post (line 16) | class Post < ActiveRecord::Base
class Comment (line 21) | class Comment < ActiveRecord::Base
class User (line 26) | class User < ActiveRecord::Base
FILE: spec/bullet/detector/association_spec.rb
type Bullet (line 7) | module Bullet
type Detector (line 8) | module Detector
FILE: spec/bullet/detector/base_spec.rb
type Bullet (line 5) | module Bullet
type Detector (line 6) | module Detector
FILE: spec/bullet/detector/counter_cache_spec.rb
type Bullet (line 7) | module Bullet
type Detector (line 8) | module Detector
FILE: spec/bullet/detector/n_plus_one_query_spec.rb
type Bullet (line 8) | module Bullet
type Detector (line 9) | module Detector
FILE: spec/bullet/detector/unused_eager_loading_spec.rb
type Bullet (line 7) | module Bullet
type Detector (line 8) | module Detector
FILE: spec/bullet/notification/base_spec.rb
type Bullet (line 5) | module Bullet
type Notification (line 6) | module Notification
function temp_env_variable (line 48) | def temp_env_variable(name, value)
FILE: spec/bullet/notification/counter_cache_spec.rb
type Bullet (line 5) | module Bullet
type Notification (line 6) | module Notification
FILE: spec/bullet/notification/n_plus_one_query_spec.rb
type Bullet (line 5) | module Bullet
type Notification (line 6) | module Notification
FILE: spec/bullet/notification/unused_eager_loading_spec.rb
type Bullet (line 5) | module Bullet
type Notification (line 6) | module Notification
FILE: spec/bullet/notification_collector_spec.rb
type Bullet (line 5) | module Bullet
FILE: spec/bullet/rack_spec.rb
type Bullet (line 5) | module Bullet
FILE: spec/bullet/registry/association_spec.rb
type Bullet (line 5) | module Bullet
type Registry (line 6) | module Registry
FILE: spec/bullet/registry/base_spec.rb
type Bullet (line 5) | module Bullet
type Registry (line 6) | module Registry
FILE: spec/bullet/registry/object_spec.rb
type Bullet (line 7) | module Bullet
type Registry (line 8) | module Registry
FILE: spec/bullet/stack_trace_filter_spec.rb
type Bullet (line 5) | module Bullet
FILE: spec/integration/active_record/association_spec.rb
function post_comments_empty_call_site (line 6) | def post_comments_empty_call_site(post)
FILE: spec/models/address.rb
class Address (line 3) | class Address < ActiveRecord::Base
FILE: spec/models/attachment.rb
class Attachment (line 3) | class Attachment < ActiveRecord::Base
FILE: spec/models/author.rb
class Author (line 3) | class Author < ActiveRecord::Base
FILE: spec/models/base_user.rb
class BaseUser (line 3) | class BaseUser < ActiveRecord::Base
FILE: spec/models/category.rb
class Category (line 3) | class Category < ActiveRecord::Base
method draft_post (line 9) | def draft_post
FILE: spec/models/city.rb
class City (line 3) | class City < ActiveRecord::Base
FILE: spec/models/client.rb
class Client (line 3) | class Client < ActiveRecord::Base
FILE: spec/models/comment.rb
class Comment (line 3) | class Comment < ActiveRecord::Base
FILE: spec/models/company.rb
class Company (line 3) | class Company < ActiveRecord::Base
FILE: spec/models/country.rb
class Country (line 3) | class Country < ActiveRecord::Base
FILE: spec/models/deal.rb
class Deal (line 3) | class Deal < ActiveRecord::Base
FILE: spec/models/document.rb
class Document (line 3) | class Document < ActiveRecord::Base
FILE: spec/models/entry.rb
class Entry (line 3) | class Entry < ActiveRecord::Base
FILE: spec/models/firm.rb
class Firm (line 3) | class Firm < ActiveRecord::Base
FILE: spec/models/folder.rb
class Folder (line 3) | class Folder < Document
FILE: spec/models/group.rb
class Group (line 3) | class Group < ActiveRecord::Base
FILE: spec/models/mongoid/address.rb
class Mongoid::Address (line 3) | class Mongoid::Address
FILE: spec/models/mongoid/category.rb
class Mongoid::Category (line 3) | class Mongoid::Category
FILE: spec/models/mongoid/comment.rb
class Mongoid::Comment (line 3) | class Mongoid::Comment
FILE: spec/models/mongoid/company.rb
class Mongoid::Company (line 3) | class Mongoid::Company
FILE: spec/models/mongoid/entry.rb
class Mongoid::Entry (line 3) | class Mongoid::Entry
FILE: spec/models/mongoid/post.rb
class Mongoid::Post (line 3) | class Mongoid::Post
FILE: spec/models/mongoid/user.rb
class Mongoid::User (line 3) | class Mongoid::User
FILE: spec/models/newspaper.rb
class Newspaper (line 3) | class Newspaper < ActiveRecord::Base
FILE: spec/models/page.rb
class Page (line 3) | class Page < Document
FILE: spec/models/person.rb
class Person (line 3) | class Person < ActiveRecord::Base
FILE: spec/models/pet.rb
class Pet (line 3) | class Pet < ActiveRecord::Base
FILE: spec/models/post.rb
class Post (line 3) | class Post < ActiveRecord::Base
method link= (line 15) | def link=(*)
FILE: spec/models/relationship.rb
class Relationship (line 3) | class Relationship < ActiveRecord::Base
FILE: spec/models/reply.rb
class Reply (line 3) | class Reply < ActiveRecord::Base
FILE: spec/models/role.rb
class Role (line 3) | class Role < ActiveRecord::Base
FILE: spec/models/student.rb
class Student (line 3) | class Student < ActiveRecord::Base
FILE: spec/models/submission.rb
class Submission (line 3) | class Submission < ActiveRecord::Base
FILE: spec/models/teacher.rb
class Teacher (line 3) | class Teacher < ActiveRecord::Base
FILE: spec/models/user.rb
class User (line 3) | class User < ActiveRecord::Base
FILE: spec/models/writer.rb
class Writer (line 3) | class Writer < BaseUser
FILE: spec/spec_helper.rb
type Rails (line 14) | module Rails
function root (line 16) | def root
function env (line 20) | def env
FILE: spec/support/bullet_ext.rb
type Bullet (line 5) | module Bullet
function collected_notifications_of_class (line 6) | def self.collected_notifications_of_class(notification_class)
function collected_counter_cache_notifications (line 10) | def self.collected_counter_cache_notifications
function collected_n_plus_one_query_notifications (line 14) | def self.collected_n_plus_one_query_notifications
function collected_unused_eager_association_notifications (line 18) | def self.collected_unused_eager_association_notifications
type Detector (line 24) | module Detector
class Association (line 25) | class Association
method completely_preloading_associations? (line 28) | def completely_preloading_associations?
method has_unused_preload_associations? (line 32) | def has_unused_preload_associations?
method creating_object_association_for? (line 37) | def creating_object_association_for?(object, association)
method detecting_unpreloaded_association_for? (line 43) | def detecting_unpreloaded_association_for?(klass, association)
method unused_preload_associations_for? (line 50) | def unused_preload_associations_for?(klass, association)
type Bullet (line 23) | module Bullet
function collected_notifications_of_class (line 6) | def self.collected_notifications_of_class(notification_class)
function collected_counter_cache_notifications (line 10) | def self.collected_counter_cache_notifications
function collected_n_plus_one_query_notifications (line 14) | def self.collected_n_plus_one_query_notifications
function collected_unused_eager_association_notifications (line 18) | def self.collected_unused_eager_association_notifications
type Detector (line 24) | module Detector
class Association (line 25) | class Association
method completely_preloading_associations? (line 28) | def completely_preloading_associations?
method has_unused_preload_associations? (line 32) | def has_unused_preload_associations?
method creating_object_association_for? (line 37) | def creating_object_association_for?(object, association)
method detecting_unpreloaded_association_for? (line 43) | def detecting_unpreloaded_association_for?(klass, association)
method unused_preload_associations_for? (line 50) | def unused_preload_associations_for?(klass, association)
FILE: spec/support/mongo_seed.rb
type Support (line 3) | module Support
type MongoSeed (line 4) | module MongoSeed
function seed_db (line 7) | def seed_db
function setup_db (line 39) | def setup_db
function teardown_db (line 61) | def teardown_db
FILE: spec/support/rack_double.rb
type Support (line 3) | module Support
class AppDouble (line 4) | class AppDouble
method call (line 5) | def call(_env)
method headers (line 14) | def headers
method status (line 23) | def status
method response (line 27) | def response
class ResponseDouble (line 32) | class ResponseDouble
method initialize (line 33) | def initialize(actual_body = nil)
method body (line 37) | def body
method each (line 43) | def each
method close (line 47) | def close; end
FILE: spec/support/sqlite_seed.rb
type Support (line 3) | module Support
type SqliteSeed (line 4) | module SqliteSeed
function seed_db (line 7) | def seed_db
function setup_db (line 117) | def setup_db
Condensed preview — 143 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (391K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 118,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n"
},
{
"path": ".github/workflows/close_inactive_issues.yml",
"chars": 706,
"preview": "name: Close inactive issues\non:\n schedule:\n - cron: \"30 1 * * *\"\n\njobs:\n close-issues:\n runs-on: ubuntu-latest\n "
},
{
"path": ".github/workflows/main.yml",
"chars": 3048,
"preview": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n"
},
{
"path": ".gitignore",
"chars": 172,
"preview": "log/**\npkg/**\n.DS_Store\nlib/.DS_Store\n.*.swp\ncoverage.data\ntags\n.bundle\n*.gem\nbenchmark_profile*\n/nbproject/private/\ncov"
},
{
"path": ".rspec",
"chars": 27,
"preview": "--colour\n--format progress\n"
},
{
"path": "CHANGELOG.md",
"chars": 9163,
"preview": "## Next Release\n\n## 8.1.0 (10/23/2025)\n\n* Make `get_relation` private\n* Support Rails 8.1\n\n## 8.0.8 (05/30/2025)\n\n* Add "
},
{
"path": "Gemfile",
"chars": 499,
"preview": "source 'https://rubygems.org'\n\ngit_source(:github) do |repo_name|\n repo_name = \"#{repo_name}/#{repo_name}\" unless repo_"
},
{
"path": "Gemfile.mongoid",
"chars": 233,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adap"
},
{
"path": "Gemfile.mongoid-4.0",
"chars": 278,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 4.0.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbc"
},
{
"path": "Gemfile.mongoid-5.0",
"chars": 278,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 4.0.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbc"
},
{
"path": "Gemfile.mongoid-6.0",
"chars": 278,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.0.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbc"
},
{
"path": "Gemfile.mongoid-7.0",
"chars": 276,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsq"
},
{
"path": "Gemfile.mongoid-8.0",
"chars": 274,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 6.1'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsq"
},
{
"path": "Gemfile.mongoid-9.0",
"chars": 174,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 8.0'\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', plat"
},
{
"path": "Gemfile.rails-4.2",
"chars": 344,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 4.2.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-"
},
{
"path": "Gemfile.rails-5.0",
"chars": 270,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.0.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-"
},
{
"path": "Gemfile.rails-5.1",
"chars": 270,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.1.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-"
},
{
"path": "Gemfile.rails-5.2",
"chars": 270,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.2.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-"
},
{
"path": "Gemfile.rails-6.0",
"chars": 258,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 6.0.0'\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', pl"
},
{
"path": "Gemfile.rails-6.1",
"chars": 258,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 6.1.0'\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', pl"
},
{
"path": "Gemfile.rails-7.0",
"chars": 187,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 7.0.0'\ngem 'sqlite3', '~> 1.4'\ngem 'activerecord-jdbcsqlite3-ad"
},
{
"path": "Gemfile.rails-7.1",
"chars": 187,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 7.1.0'\ngem 'sqlite3', '~> 1.4'\ngem 'activerecord-jdbcsqlite3-ad"
},
{
"path": "Gemfile.rails-7.2",
"chars": 177,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"rails\", \"~> 7.2.0\"\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', pl"
},
{
"path": "Gemfile.rails-8.0",
"chars": 177,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"rails\", \"~> 8.0.0\"\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', pl"
},
{
"path": "Gemfile.rails-8.1",
"chars": 177,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"rails\", \"~> 8.1.0\"\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', pl"
},
{
"path": "Guardfile",
"chars": 334,
"preview": "# A sample Guardfile\n# More info at https://github.com/guard/guard#readme\n\nguard 'rspec', version: 2, all_after_pass: fa"
},
{
"path": "Hacking.md",
"chars": 3400,
"preview": "# Bullet Overview for Developers\n\nThis file aims to give developers a quick tour of the bullet internals, making\nit (hop"
},
{
"path": "MIT-LICENSE",
"chars": 1085,
"preview": "Copyright (c) 2009 - 2024 Richard Huang (flyerhzm@gmail.com)\n\nPermission is hereby granted, free of charge, to any perso"
},
{
"path": "README.md",
"chars": 16333,
"preview": "# Bullet\n\n\n[\nrequire 'bundler'\nBundler.setup\n\nrequire 'rake'\nrequire 'rspec'\nrequ"
},
{
"path": "bullet.gemspec",
"chars": 1227,
"preview": "# frozen_string_literal: true\n\nlib = File.expand_path('lib', __dir__)\n$LOAD_PATH.unshift lib unless $LOAD_PATH.include?("
},
{
"path": "lib/bullet/active_job.rb",
"chars": 237,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module ActiveJob\n def self.included(base)\n base.class_eval do\n "
},
{
"path": "lib/bullet/active_record4.rb",
"chars": 7766,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module ActiveRecord\n def self.enable\n require 'active_record'\n "
},
{
"path": "lib/bullet/active_record41.rb",
"chars": 7494,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module ActiveRecord\n def self.enable\n require 'active_record'\n "
},
{
"path": "lib/bullet/active_record42.rb",
"chars": 10342,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module ActiveRecord\n def self.enable\n require 'active_record'\n "
},
{
"path": "lib/bullet/active_record5.rb",
"chars": 10953,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record52.rb",
"chars": 10508,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record60.rb",
"chars": 11631,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record61.rb",
"chars": 11697,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record70.rb",
"chars": 12090,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record71.rb",
"chars": 12090,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record72.rb",
"chars": 12110,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record80.rb",
"chars": 12110,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/active_record81.rb",
"chars": 12110,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module SaveWithBulletSupport\n def _create_record(*)\n super do\n "
},
{
"path": "lib/bullet/bullet_xhr.js",
"chars": 2435,
"preview": "(function () {\n var oldOpen = window.XMLHttpRequest.prototype.open;\n var oldSend = window.XMLHttpRequest.prototype.sen"
},
{
"path": "lib/bullet/dependency.rb",
"chars": 3898,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Dependency\n def mongoid?\n @mongoid ||= defined?(::Mongoid)"
},
{
"path": "lib/bullet/detector/association.rb",
"chars": 3569,
"preview": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module Detector\n class Association < Base\n "
},
{
"path": "lib/bullet/detector/base.rb",
"chars": 96,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Detector\n class Base\n end\n end\nend\n"
},
{
"path": "lib/bullet/detector/counter_cache.rb",
"chars": 2333,
"preview": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module Detector\n class CounterCache < Base\n"
},
{
"path": "lib/bullet/detector/n_plus_one_query.rb",
"chars": 4972,
"preview": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module Detector\n class NPlusOneQuery < Asso"
},
{
"path": "lib/bullet/detector/unused_eager_loading.rb",
"chars": 3701,
"preview": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\nusing Bullet::Ext::String\n\nmodule Bullet\n module Detector\n "
},
{
"path": "lib/bullet/detector.rb",
"chars": 370,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Detector\n autoload :Base, 'bullet/detector/base'\n autoload :"
},
{
"path": "lib/bullet/ext/object.rb",
"chars": 1024,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Ext\n module Object\n refine ::Object do\n attr_writer"
},
{
"path": "lib/bullet/ext/string.rb",
"chars": 270,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Ext\n module String\n refine ::String do\n def bullet_"
},
{
"path": "lib/bullet/mongoid4x.rb",
"chars": 1829,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Mongoid\n def self.enable\n require 'mongoid'\n ::Mongoi"
},
{
"path": "lib/bullet/mongoid5x.rb",
"chars": 1829,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Mongoid\n def self.enable\n require 'mongoid'\n ::Mongoi"
},
{
"path": "lib/bullet/mongoid6x.rb",
"chars": 1859,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Mongoid\n def self.enable\n require 'mongoid'\n ::Mongoi"
},
{
"path": "lib/bullet/mongoid7x.rb",
"chars": 2351,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Mongoid\n def self.enable\n require 'mongoid'\n require "
},
{
"path": "lib/bullet/mongoid8x.rb",
"chars": 1904,
"preview": "module Bullet\n module Mongoid\n def self.enable\n require 'mongoid'\n ::Mongoid::Contextual::Mongo.class_eval"
},
{
"path": "lib/bullet/mongoid9x.rb",
"chars": 2352,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Mongoid\n def self.enable\n require 'mongoid'\n require "
},
{
"path": "lib/bullet/notification/base.rb",
"chars": 2116,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Notification\n class Base\n attr_accessor :notifier, :url\n "
},
{
"path": "lib/bullet/notification/counter_cache.rb",
"chars": 251,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Notification\n class CounterCache < Base\n def body\n "
},
{
"path": "lib/bullet/notification/n_plus_one_query.rb",
"chars": 646,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Notification\n class NPlusOneQuery < Base\n def initialize(c"
},
{
"path": "lib/bullet/notification/unused_eager_loading.rb",
"chars": 658,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Notification\n class UnusedEagerLoading < Base\n def initial"
},
{
"path": "lib/bullet/notification.rb",
"chars": 390,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Notification\n autoload :Base, 'bullet/notification/base'\n au"
},
{
"path": "lib/bullet/notification_collector.rb",
"chars": 340,
"preview": "# frozen_string_literal: true\n\nrequire 'set'\n\nmodule Bullet\n class NotificationCollector\n attr_reader :collection\n\n "
},
{
"path": "lib/bullet/rack.rb",
"chars": 6342,
"preview": "# frozen_string_literal: true\n\nrequire 'rack/request'\nrequire 'json'\nrequire 'cgi'\n\nmodule Bullet\n class Rack\n inclu"
},
{
"path": "lib/bullet/registry/association.rb",
"chars": 394,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Registry\n class Association < Base\n def merge(base, associ"
},
{
"path": "lib/bullet/registry/base.rb",
"chars": 791,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Registry\n class Base\n attr_reader :registry\n\n def ini"
},
{
"path": "lib/bullet/registry/call_stack.rb",
"chars": 419,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Registry\n class CallStack < Base\n # remembers found associ"
},
{
"path": "lib/bullet/registry/object.rb",
"chars": 348,
"preview": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\nusing Bullet::Ext::String\n\nmodule Bullet\n module Registry\n "
},
{
"path": "lib/bullet/registry.rb",
"chars": 274,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Registry\n autoload :Base, 'bullet/registry/base'\n autoload :"
},
{
"path": "lib/bullet/stack_trace_filter.rb",
"chars": 1955,
"preview": "# frozen_string_literal: true\n\nrequire \"bundler\"\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module StackTraceFilter\n "
},
{
"path": "lib/bullet/version.rb",
"chars": 69,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n VERSION = '8.1.0'\nend\n"
},
{
"path": "lib/bullet.rb",
"chars": 9490,
"preview": "# frozen_string_literal: true\n\nrequire 'active_support/core_ext/string/inflections'\nrequire 'active_support/core_ext/mod"
},
{
"path": "lib/generators/bullet/install_generator.rb",
"chars": 1261,
"preview": "# frozen_string_literal: true\n\nmodule Bullet\n module Generators\n class InstallGenerator < ::Rails::Generators::Base\n"
},
{
"path": "perf/benchmark.rb",
"chars": 3231,
"preview": "# frozen_string_literal: true\n\n$LOAD_PATH << 'lib'\nrequire 'benchmark'\nrequire 'rails'\nrequire 'active_record'\nrequire '"
},
{
"path": "rails/init.rb",
"chars": 48,
"preview": "# frozen_string_literal: true\n\nrequire 'bullet'\n"
},
{
"path": "spec/bullet/detector/association_spec.rb",
"chars": 852,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module Detector\n des"
},
{
"path": "spec/bullet/detector/base_spec.rb",
"chars": 125,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n module Detector\n describe Base do\n end\n end"
},
{
"path": "spec/bullet/detector/counter_cache_spec.rb",
"chars": 2162,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module Detector\n des"
},
{
"path": "spec/bullet/detector/n_plus_one_query_spec.rb",
"chars": 8211,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\nrequire 'ostruct'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n modul"
},
{
"path": "spec/bullet/detector/unused_eager_loading_spec.rb",
"chars": 6343,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module Detector\n des"
},
{
"path": "spec/bullet/ext/object_spec.rb",
"chars": 1949,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\ndescribe Object do\n context 'bullet_ke"
},
{
"path": "spec/bullet/ext/string_spec.rb",
"chars": 391,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::String\n\ndescribe String do\n context 'bullet_cl"
},
{
"path": "spec/bullet/notification/base_spec.rb",
"chars": 3753,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n module Notification\n describe Base do\n sub"
},
{
"path": "spec/bullet/notification/counter_cache_spec.rb",
"chars": 359,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n module Notification\n describe CounterCache do\n "
},
{
"path": "spec/bullet/notification/n_plus_one_query_spec.rb",
"chars": 1055,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n module Notification\n describe NPlusOneQuery do\n"
},
{
"path": "spec/bullet/notification/unused_eager_loading_spec.rb",
"chars": 465,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n module Notification\n describe UnusedEagerLoadin"
},
{
"path": "spec/bullet/notification_collector_spec.rb",
"chars": 821,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n describe NotificationCollector do\n subject { No"
},
{
"path": "spec/bullet/rack_spec.rb",
"chars": 18687,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n describe Rack do\n let(:middleware) { Bullet::Ra"
},
{
"path": "spec/bullet/registry/association_spec.rb",
"chars": 790,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n module Registry\n describe Association do\n "
},
{
"path": "spec/bullet/registry/base_spec.rb",
"chars": 1157,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n module Registry\n describe Base do\n subject"
},
{
"path": "spec/bullet/registry/object_spec.rb",
"chars": 646,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n module Registry\n des"
},
{
"path": "spec/bullet/stack_trace_filter_spec.rb",
"chars": 769,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n RSpec.describe StackTraceFilter do\n let(:dummy_"
},
{
"path": "spec/bullet_spec.rb",
"chars": 5796,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\ndescribe Bullet do\n subject { Bullet }\n\n describe '#enable' do\n "
},
{
"path": "spec/integration/active_record/association_spec.rb",
"chars": 41269,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nif active_record?\n def post_comments_empty_call_site(post)\n po"
},
{
"path": "spec/integration/counter_cache_spec.rb",
"chars": 3292,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nif !mongoid? && active_record?\n describe Bullet::Detector::Counte"
},
{
"path": "spec/integration/mongoid/association_spec.rb",
"chars": 11596,
"preview": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nif mongoid?\n describe Bullet::Detector::Association do\n contex"
},
{
"path": "spec/models/address.rb",
"chars": 92,
"preview": "# frozen_string_literal: true\n\nclass Address < ActiveRecord::Base\n belongs_to :company\nend\n"
},
{
"path": "spec/models/attachment.rb",
"chars": 98,
"preview": "# frozen_string_literal: true\n\nclass Attachment < ActiveRecord::Base\n belongs_to :submission\nend\n"
},
{
"path": "spec/models/author.rb",
"chars": 91,
"preview": "# frozen_string_literal: true\n\nclass Author < ActiveRecord::Base\n has_many :documents\nend\n"
},
{
"path": "spec/models/base_user.rb",
"chars": 134,
"preview": "# frozen_string_literal: true\n\nclass BaseUser < ActiveRecord::Base\n has_many :comments\n has_many :posts\n belongs_to :"
},
{
"path": "spec/models/category.rb",
"chars": 207,
"preview": "# frozen_string_literal: true\n\nclass Category < ActiveRecord::Base\n has_many :posts, inverse_of: :category\n has_many :"
},
{
"path": "spec/models/city.rb",
"chars": 89,
"preview": "# frozen_string_literal: true\n\nclass City < ActiveRecord::Base\n belongs_to :country\nend\n"
},
{
"path": "spec/models/client.rb",
"chars": 159,
"preview": "# frozen_string_literal: true\n\nclass Client < ActiveRecord::Base\n belongs_to :group\n\n has_many :relationships\n has_ma"
},
{
"path": "spec/models/comment.rb",
"chars": 192,
"preview": "# frozen_string_literal: true\n\nclass Comment < ActiveRecord::Base\n belongs_to :post, inverse_of: :comments\n belongs_to"
},
{
"path": "spec/models/company.rb",
"chars": 89,
"preview": "# frozen_string_literal: true\n\nclass Company < ActiveRecord::Base\n has_one :address\nend\n"
},
{
"path": "spec/models/country.rb",
"chars": 89,
"preview": "# frozen_string_literal: true\n\nclass Country < ActiveRecord::Base\n has_many :cities\nend\n"
},
{
"path": "spec/models/deal.rb",
"chars": 100,
"preview": "# frozen_string_literal: true\n\nclass Deal < ActiveRecord::Base\n has_and_belongs_to_many :posts\nend\n"
},
{
"path": "spec/models/document.rb",
"chars": 234,
"preview": "# frozen_string_literal: true\n\nclass Document < ActiveRecord::Base\n has_many :children, class_name: 'Document', foreign"
},
{
"path": "spec/models/entry.rb",
"chars": 91,
"preview": "# frozen_string_literal: true\n\nclass Entry < ActiveRecord::Base\n belongs_to :category\nend\n"
},
{
"path": "spec/models/firm.rb",
"chars": 176,
"preview": "# frozen_string_literal: true\n\nclass Firm < ActiveRecord::Base\n has_many :relationships\n has_many :clients, through: :"
},
{
"path": "spec/models/folder.rb",
"chars": 59,
"preview": "# frozen_string_literal: true\n\nclass Folder < Document\nend\n"
},
{
"path": "spec/models/group.rb",
"chars": 68,
"preview": "# frozen_string_literal: true\n\nclass Group < ActiveRecord::Base\nend\n"
},
{
"path": "spec/models/mongoid/address.rb",
"chars": 156,
"preview": "# frozen_string_literal: true\n\nclass Mongoid::Address\n include Mongoid::Document\n\n field :name\n\n belongs_to :company,"
},
{
"path": "spec/models/mongoid/category.rb",
"chars": 200,
"preview": "# frozen_string_literal: true\n\nclass Mongoid::Category\n include Mongoid::Document\n\n field :name\n\n has_many :posts, cl"
},
{
"path": "spec/models/mongoid/comment.rb",
"chars": 150,
"preview": "# frozen_string_literal: true\n\nclass Mongoid::Comment\n include Mongoid::Document\n\n field :name\n\n belongs_to :post, cl"
},
{
"path": "spec/models/mongoid/company.rb",
"chars": 153,
"preview": "# frozen_string_literal: true\n\nclass Mongoid::Company\n include Mongoid::Document\n\n field :name\n\n has_one :address, cl"
},
{
"path": "spec/models/mongoid/entry.rb",
"chars": 156,
"preview": "# frozen_string_literal: true\n\nclass Mongoid::Entry\n include Mongoid::Document\n\n field :name\n\n belongs_to :category, "
},
{
"path": "spec/models/mongoid/post.rb",
"chars": 314,
"preview": "# frozen_string_literal: true\n\nclass Mongoid::Post\n include Mongoid::Document\n\n field :name\n\n has_many :comments, cla"
},
{
"path": "spec/models/mongoid/user.rb",
"chars": 98,
"preview": "# frozen_string_literal: true\n\nclass Mongoid::User\n include Mongoid::Document\n\n field :name\nend\n"
},
{
"path": "spec/models/newspaper.rb",
"chars": 116,
"preview": "# frozen_string_literal: true\n\nclass Newspaper < ActiveRecord::Base\n has_many :writers, class_name: 'BaseUser'\nend\n"
},
{
"path": "spec/models/page.rb",
"chars": 57,
"preview": "# frozen_string_literal: true\n\nclass Page < Document\nend\n"
},
{
"path": "spec/models/person.rb",
"chars": 86,
"preview": "# frozen_string_literal: true\n\nclass Person < ActiveRecord::Base\n has_many :pets\nend\n"
},
{
"path": "spec/models/pet.rb",
"chars": 108,
"preview": "# frozen_string_literal: true\n\nclass Pet < ActiveRecord::Base\n belongs_to :person, counter_cache: true\nend\n"
},
{
"path": "spec/models/post.rb",
"chars": 935,
"preview": "# frozen_string_literal: true\n\nclass Post < ActiveRecord::Base\n belongs_to :category, inverse_of: :posts\n belongs_to :"
},
{
"path": "spec/models/relationship.rb",
"chars": 115,
"preview": "# frozen_string_literal: true\n\nclass Relationship < ActiveRecord::Base\n belongs_to :firm\n belongs_to :client\nend\n"
},
{
"path": "spec/models/reply.rb",
"chars": 93,
"preview": "# frozen_string_literal: true\n\nclass Reply < ActiveRecord::Base\n belongs_to :submission\nend\n"
},
{
"path": "spec/models/role.rb",
"chars": 143,
"preview": "# frozen_string_literal: true\n\nclass Role < ActiveRecord::Base\n has_and_belongs_to_many :users\n\n belongs_to :resource,"
},
{
"path": "spec/models/student.rb",
"chars": 106,
"preview": "# frozen_string_literal: true\n\nclass Student < ActiveRecord::Base\n has_and_belongs_to_many :teachers\nend\n"
},
{
"path": "spec/models/submission.rb",
"chars": 134,
"preview": "# frozen_string_literal: true\n\nclass Submission < ActiveRecord::Base\n belongs_to :user\n has_many :replies\n has_one :a"
},
{
"path": "spec/models/teacher.rb",
"chars": 106,
"preview": "# frozen_string_literal: true\n\nclass Teacher < ActiveRecord::Base\n has_and_belongs_to_many :students\nend\n"
},
{
"path": "spec/models/user.rb",
"chars": 247,
"preview": "# frozen_string_literal: true\n\nclass User < ActiveRecord::Base\n has_one :submission\n has_one :submission_attachment, t"
},
{
"path": "spec/models/writer.rb",
"chars": 59,
"preview": "# frozen_string_literal: true\n\nclass Writer < BaseUser\nend\n"
},
{
"path": "spec/spec_helper.rb",
"chars": 2295,
"preview": "# frozen_string_literal: true\n\nrequire 'rspec'\nrequire 'logger'\nbegin\n require 'active_record'\nrescue LoadError\nend\nbeg"
},
{
"path": "spec/support/bullet_ext.rb",
"chars": 2122,
"preview": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n def self.collected_notifications_of_class(noti"
},
{
"path": "spec/support/mongo_seed.rb",
"chars": 2365,
"preview": "# frozen_string_literal: true\n\nmodule Support\n module MongoSeed\n module_function\n\n def seed_db\n category1 = "
},
{
"path": "spec/support/rack_double.rb",
"chars": 716,
"preview": "# frozen_string_literal: true\n\nmodule Support\n class AppDouble\n def call(_env)\n env = @env\n [status, heade"
},
{
"path": "spec/support/sqlite_seed.rb",
"chars": 8828,
"preview": "# frozen_string_literal: true\n\nmodule Support\n module SqliteSeed\n module_function\n\n def seed_db\n newspaper1 "
},
{
"path": "tasks/bullet_tasks.rake",
"chars": 221,
"preview": "# frozen_string_literal: true\n\nnamespace :bullet do\n namespace :log do\n desc 'Truncates the bullet log file to zero "
},
{
"path": "test.sh",
"chars": 2063,
"preview": "#bundle update rails && bundle exec rspec spec\n#BUNDLE_GEMFILE=Gemfile.mongoid bundle update mongoid && BUNDLE_GEMFILE=G"
},
{
"path": "update.sh",
"chars": 576,
"preview": "BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle update\nBUNDLE_GEMFILE=Gemfile.rails-5.1 bundle update\nBUNDLE_GEMFILE=Gemfile.rai"
}
]
About this extraction
This page contains the full source code of the flyerhzm/bullet GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 143 files (363.1 KB), approximately 87.1k tokens, and a symbol index with 411 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.