[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/close_inactive_issues.yml",
    "content": "name: Close inactive issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v10\n        with:\n          days-before-issue-stale: 30\n          days-before-issue-close: 14\n          stale-issue-label: \"stale\"\n          stale-issue-message: \"This issue is stale because it has been open for 30 days with no activity.\"\n          close-issue-message: \"This issue was closed because it has been inactive for 14 days since being marked as stale.\"\n          days-before-pr-stale: -1\n          days-before-pr-close: -1\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake\n# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby\n\nname: CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  test_rails_4:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        gemfile: ['Gemfile.rails-4.2']\n    env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps\n      BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}\n    steps:\n    - uses: actions/checkout@v5\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 2.7\n        bundler: 1\n        bundler-cache: true\n    - name: Run tests\n      run: bundle exec rake\n  test_rails_5:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        gemfile: ['Gemfile.rails-5.0', 'Gemfile.rails-5.1', 'Gemfile.rails-5.2']\n    env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps\n      BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}\n    steps:\n    - uses: actions/checkout@v5\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 2.7\n        bundler: 1\n        bundler-cache: true\n    - name: Run tests\n      run: bundle exec rake\n  test_rails_6:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        gemfile: ['Gemfile.rails-6.0', 'Gemfile.rails-6.1']\n    env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps\n      BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}\n    steps:\n    - uses: actions/checkout@v5\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 2.7\n        bundler-cache: true\n    - name: Run tests\n      run: bundle exec rake\n  test_rails_7:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        gemfile: ['Gemfile.rails-7.0', 'Gemfile.rails-7.1', 'Gemfile.rails-7.2']\n    env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps\n      BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}\n    steps:\n    - uses: actions/checkout@v5\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 3.1\n        bundler-cache: true\n    - name: Run tests\n      run: bundle exec rake\n  test_rails_8:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        gemfile: [ 'Gemfile.rails-8.0', 'Gemfile.rails-8.1' ]\n    env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps\n      BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}\n    steps:\n      - uses: actions/checkout@v5\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: 3.2\n          bundler-cache: true\n      - name: Run tests\n        run: bundle exec rake\n"
  },
  {
    "path": ".gitignore",
    "content": "log/**\npkg/**\n.DS_Store\nlib/.DS_Store\n.*.swp\ncoverage.data\ntags\n.bundle\n*.gem\nbenchmark_profile*\n/nbproject/private/\ncoverage/\n.coveralls.yml\nGemfile*.lock\n.idea/\n.vscode/\n"
  },
  {
    "path": ".rspec",
    "content": "--colour\n--format progress\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 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 middleware after initializers\n* Fix bullet composite primary key retrieval\n\n## 8.0.7 (05/15/2025)\n\n* Try to insert `Bullet::Rack` properly\n\n## 8.0.6 (05/07/2025)\n\n* Add CSP nonce for footer styles as well\n* Add support for OpenTelemetry reporting\n\n## 8.0.5 (04/21/2025)\n\n* Properly insert ContentSecurityPolicy middleware\n* Properly parse query string\n\n## 8.0.4 (04/18/2025)\n\n* Insert bullet middleware before `ContentSecurityPolicy`\n* Support url query `skip_html_injection=true`\n* Mark object as impossible after updating inversed\n\n## 8.0.3 (04/04/2025)\n\n* Update non persisted `inversed_objects`\n\n## 8.0.2 (04/02/2025)\n\n* Do not cache `bullet_key` if object is not persisted\n\n## 8.0.1 (02/10/2025)\n\n* Update benchmark to use sqlite\n* Reduce mem allocations\n* Require active_support's inflections module before requiring the delegation module\n\n## 8.0.0 (11/10/2024)\n\n* Support Rails 8\n* Drop Rails 4.0 and 4.1 support\n* Require Ruby at least 2.7.0\n* Store global objects into thread-local variables\n* Avoid globally polluting `::String` and `::Object` classes\n\n## 7.2.0 (07/12/2024)\n\n* Support Rails 7.2\n* Fix count method signature for active_record5 and active_record60\n\n## 7.1.6 (01/16/2024)\n\n* Allow apps to not include the user in a notification\n\n## 7.1.5 (01/05/2024)\n\n* Fix mongoid8\n\n## 7.1.4 (11/17/2023)\n\n* Call association also on through reflection\n\n## 7.1.3 (11/05/2023)\n\n* Call NPlusOneQuery's call_association when calling count on collection association\n\n## 7.1.2 (10/13/2023)\n\n* Handle Rails 7.1 composite primary keys\n\n## 7.1.1 (10/07/2023)\n\n* Add support for `Content-Security-Policy-Report-Only` nonces\n* Fix count method signature\n\n## 7.1.0 (10/06/2023)\n\n* Support rails 7.1\n* Alias `Bullet.enable?` to `enabled?`, and `Bullet.enable=` to `enabled=`\n* Added `always_append_html_body` option, so the html snippet is always included even if there are no notifications\n* Added detection of n+1 count queries from `count` method\n* Changed the counter cache notification title to recommend using `size`\n\n## 7.0.7 (03/01/2023)\n\n* Check `Rails.application.config.content_security_policy` before insert `Bullet::Rack`\n\n## 7.0.6 (03/01/2023)\n\n* Better way to check if `ActionDispatch::ContentSecurityPolicy::Middleware` exists\n\n## 7.0.5 (01/01/2023)\n\n* Fix n+1 false positives in AR 7.0\n* Fix eager_load nested has_many :through false positives\n* Respect Content-Security-Policy nonces\n* Added CallStacks support for avoid eager loading\n* Iterate fewer times over objects\n\n## 7.0.4 (11/28/2022)\n\n* Fix `eager_load` `has_many :through` false positives\n* mongoid7x: add dynamic methods\n\n## 7.0.3 (08/13/2022)\n\n* Replace `Array()` with `Array.wrap()`\n\n## 7.0.2 (05/31/2022)\n\n* Drop growl support\n* Do not check html tag in Bullet::Rack anymore\n\n## 7.0.1 (01/15/2022)\n\n* Get rid of *_whitelist methods\n* Hack ActiveRecord::Associations::Preloader::Batch in rails 7\n\n## 7.0.0 (12/18/2021)\n\n* Support rails 7\n* Fix Mongoid 7 view iteration\n* Move CI from Travis to Github Actions\n\n## 6.1.5 (08/16/2021)\n\n* Rename whitelist to safelist\n* Fix onload called twice\n* Support Rack::Files::Iterator responses\n* Ensure HABTM associations are not incorrectly labeled n+1\n\n## 6.1.4 (02/26/2021)\n\n* Added an option to stop adding HTTP headers to API requests\n\n## 6.1.3 (01/21/2021)\n\n* Consider ThroughAssociation at SingularAssociation like CollectionAssociation\n* Add xhr_script only when add_footer is enabled\n\n## 6.1.2 (12/12/2020)\n\n* Revert \"Make whitelist thread safe\"\n\n## 6.1.1 (12/12/2020)\n\n* Add support Rails 6.1\n* Make whitelist thread safe\n\n## 6.1.0 (12/28/2019)\n\n* Add skip_html_injection flag\n* Remove writer hack in active_record6\n* Use modern includes syntax in warnings\n* Fix warning: The last argument is used as the keyword parameter\n\n## 6.0.2 (08/20/2019)\n\n* Fully support Rails 6.0\n\n## 6.0.1 (06/26/2019)\n\n* Add Bullet::ActiveJob\n* Prevent \"Maximum call stack exceeded\" errors when used with Turbolinks\n\n## 6.0.0 (04/25/2019)\n\n* Add XHR support to Bullet\n* Support Rails 6.0\n* Handle case where ID is manually set on unpersisted record\n\n## 5.9.0 (11/11/2018)\n\n* Require Ruby 2.3+\n* Support Mongo 7.x\n\n## 5.8.0 (10/29/2018)\n\n* Fix through reflection for rails 5.x\n* Fix false positive in after_save/after_create callbacks\n* Don't trigger a preload error on \"manual\" preloads\n* Avoid Bullet from making extra queries in mongoid6\n* Support option for #first and #last on mongoid6.x\n* Fix duplicate logs in mongoid 4.x and 5.x version\n* Use caller for ruby 1.9 while caller_locations for 2.0+\n* Extend stacktrace matching for sub-file precision\n* Exclude configured bundler path in addition to '/vendor'\n* Fix `caller_path` in `excluded_stacktrace_path`\n* Update `uniform_notifier` dependency to add Sentry support\n* Integrate awesomecode.io and refactor code\n\n## 5.7.0 (12/03/2017)\n\n* Support rails 5.2\n* Implement Bullet.delete_whitelist to delete a specific whitelist definition\n* Fix caller_path in the case of nil\n\n## 5.6.0 (07/16/2017)\n\n* Migrate alias_method to Module#prepend\n* Add install generator\n* Stack trace filter\n* Fix rails 5.1 compatibility\n* Fix inverse_of for rails 5\n* Fix detect file attachment for rack #319\n\n## 5.5.0 (12/30/2016)\n\n* Display http request method #311\n* Add close button to footer\n* Raise an error if bullet does not support AR or Mongoid\n* Avoid double backtrace\n* Fix false alert on counter cache when associations are already loaded #288\n* Fix \"false alert\" in rails 5 #239\n* Do not support ActiveRecord 3.x and Mongoid 3.x / 4.x anymore\n\n## 5.4.0 (10/09/2016)\n\n* Support rails 5.1\n* Extract stack trace filtering into module\n\n## 5.3.0 (15/08/2016)\n\n* Fix false alert on through association with join sql #301\n* Fix association.target in `through_association` can be singular #302\n* Support `find_by_sql` #303\n* Fix env `REQUEST_URI`\n\n## 5.2.0 (07/26/2016)\n\n* Fix `has_cached_counter?` is not defined in HABTM #297\n* Fix false alert if preloaded association has no records #260\n* Support Rails 5.0.0\n\n## 5.1.0 (05/21/2016)\n\n* Fix false alert when `empty?` used with `counter_cache`\n* Fix `alias_method_chain` deprecation for rails 5\n* Add response handling for non-Rails Rack responses\n* Fix false alert when querying immediately after creation\n* Fix UnusedEagerLoading bug when multiple eager loading query include same objects\n\n## 5.0.0 (01/06/2016)\n\n* Support Rails 5.0.0.beta1\n* Fix `has_many :through` infinite loop issue\n* Support mongoid 5.0.0\n* Do not report association queries immediately after object creation to\n  require a preload\n* Detect `counter_cache` for `has_many :through` association\n* Compatible with `composite_primary_keys` gem\n* Fix AR 4.2 SingularAssociation#reader result can be nil\n* `perform_out_of_channel_notifications` should always be triggered\n* Fix false positive with `belongs_to` -> `belongs_to` for active\\_record 4.2\n* Activate active\\_record hacks only when Bullet already start\n* Don't execute query when running `to_sql`\n* Send backtrace to `uniform_notifier`\n* Fix sse response check\n* Dynamically delegate available notifiers to UniformNotifier\n* Hotfix nil object when `add_impossible_object`\n* Fix `has_one` then `has_many` associations in rails 4.2\n* Append js and dom to html body in proper position\n\n## 4.14.0 (10/03/2014)\n\n* Support rails 4.2\n* Polish notification output\n* Fix warning: `*' interpreted as argument prefix\n\n## 4.13.0 (07/19/2014)\n\n* Support include? call on ar associations\n\n## 4.12.0 (07/13/2014)\n\n* Fix false n+1 queries caused by inversed objects.\n* Replace .id with .primary_key_value\n* Rename bullet_ar_key to bullet_key\n* Fix rails sse detect\n* Fix bullet using in test environment\n* Memoize whoami\n\n## 4.11.0 (06/24/2014)\n\n* Support empty? call on ar associations\n* Skip detecting if object is a new record\n\n## 4.10.0 (06/06/2014)\n\n* Handle join query smarter\n* Support mongoid 4.0\n* Thread safe\n* Add debug mode\n\n## 4.9.0 (04/30/2014)\n\n* Add Bullet.stacktrace_includes option\n* Applied keyword argument fixes on Ruby 2.2.0\n* Add bugsnag notifier\n* Support rails 4.1.0\n\n## 4.8.0 (02/16/2014)\n\n* Support rails 4.1.0.beta1\n* Update specs to be RSpec 3.0 compatible\n* Update latest minor version activerecord and mongoid on travis\n\n## 4.7.0 (11/03/2013)\n\n* Add coverall support\n* Add helper to profile code outside a request\n* Add activesupport dependency\n* Add Bullet.raise notification\n* Add Bullet.add_footer notification\n* Fix activerecord4 warnings in test code\n\n## 4.6.0 (04/18/2013)\n\n* Fix Bullet::Rack to support sinatra\n\n## 4.5.0 (03/24/2013)\n\n* Add api way to access captured association\n* Allow disable n_plus_one_query, unused_eager_loading and counter_cache respectively\n* Add whitelist\n\n## 4.4.0 (03/15/2013)\n\n* Remove disable_browser_cache option\n* Compatible with Rails 4.0.0.beta1\n\n## 4.3.0 (12/28/2012)\n\n* Fix content-length for non ascii html\n* Add mongoid 2.5.x support\n\n## 4.2.0 (09/29/2012)\n\n* Add Bullet::Dependency to check AR and mongoid version\n* Add Rails 4 support\n* Add airbrake notifier support\n\n## 4.1.0 (05/30/2012)\n\n* Add mongoid 3 support\n\n## 4.0.0 (05/09/2012)\n\n* Add mongoid support\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\ngit_source(:github) do |repo_name|\n  repo_name = \"#{repo_name}/#{repo_name}\" unless repo_name.include?('/')\n  \"https://github.com/#{repo_name}.git\"\nend\n\ngemspec\n\ngem 'rails', github: 'rails'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem 'rspec'\ngem 'guard'\ngem 'guard-rspec'\n\ngem 'coveralls', require: false\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.mongoid",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'mongoid', github: 'mongoid/mongoid'\n\ngem \"rspec\"\n\ngem 'coveralls', require: false\n"
  },
  {
    "path": "Gemfile.mongoid-4.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 4.0.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'mongoid', '~> 4.0.0'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.mongoid-5.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 4.0.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'mongoid', '~> 5.1.0'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.mongoid-6.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.0.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'mongoid', '~> 6.0.0'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.mongoid-7.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.0'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'mongoid', '~> 7.0.0'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.mongoid-8.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 6.1'\ngem 'sqlite3', platforms: [:ruby]\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'mongoid', '~> 8.0'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.mongoid-9.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 8.0'\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'mongoid', '~> 9.0'\ngem 'rspec'\n\n\n"
  },
  {
    "path": "Gemfile.rails-4.2",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 4.2.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\ngem 'tins', '~> 1.6.0', platforms: [:ruby_19]\n\ngem 'bigdecimal', '~> 1.4'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.rails-5.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.0.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.rails-5.1",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.1.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.rails-5.2",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 5.2.0'\ngem 'sqlite3', '~> 1.3.6'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.rails-6.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 6.0.0'\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.rails-6.1",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 6.1.0'\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n\nplatforms :rbx do\n  gem 'rubysl', '~> 2.0'\n  gem 'rubinius-developer_tools'\nend\n"
  },
  {
    "path": "Gemfile.rails-7.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 7.0.0'\ngem 'sqlite3', '~> 1.4'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n"
  },
  {
    "path": "Gemfile.rails-7.1",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem 'rails', '~> 7.1.0'\ngem 'sqlite3', '~> 1.4'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n"
  },
  {
    "path": "Gemfile.rails-7.2",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"rails\", \"~> 7.2.0\"\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n"
  },
  {
    "path": "Gemfile.rails-8.0",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"rails\", \"~> 8.0.0\"\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n"
  },
  {
    "path": "Gemfile.rails-8.1",
    "content": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"rails\", \"~> 8.1.0\"\ngem 'sqlite3'\ngem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]\ngem 'activerecord-import'\n\ngem \"rspec\"\n"
  },
  {
    "path": "Guardfile",
    "content": "# A sample Guardfile\n# More info at https://github.com/guard/guard#readme\n\nguard 'rspec', version: 2, all_after_pass: false, all_on_start: false, cli: '--color --format nested --fail-fast' do\n  watch(%r{^spec/.+_spec\\.rb$})\n  watch(%r{^lib/(.+)\\.rb$})     { |m| \"spec/#{m[1]}_spec.rb\" }\n  watch('spec/spec_helper.rb')  { 'spec' }\nend\n"
  },
  {
    "path": "Hacking.md",
    "content": "# Bullet Overview for Developers\n\nThis file aims to give developers a quick tour of the bullet internals, making\nit (hopefully) easier to extend or enhance the Bullet gem.\n\n## General Control Flow aka. 10000 Meter View\n\nWhen Rails is initialized, Bullet will extend ActiveRecord (and if you're using\nRails 2.x ActiveController too) with the relevant modules and methods found\nin lib/bullet/active_recordX.rb and lib/bullet/action_controller2.rb. If you're\nrunning Rails 3, Bullet will integrate itself as a middleware into the Rack\nstack, so ActionController does not need to be extended.\n\nThe ActiveRecord extensions will call methods in a given detector class, when\ncertain methods are called.\n\nDetector classes contain all the logic to recognize\na noteworthy event. If such an event is detected, an instance of the\ncorresponding Notification class is created and stored in a Set instance in the\nmain Bullet module (the 'notification collector').\n\nNotification instances contain the message that will be displayed, and will\nuse a Presenter class to display their message to the user.\n\nSo the flow of a request goes like this:\n\n1. Bullet.start_request is called, which resets all the detectors and empties\n   the notification collector\n2. The request is handled by Rails, and the installed ActiveRecord extensions\n   trigger Detector callbacks\n3. Detectors once called, will determine whether something noteworthy happened.\n   If yes, then a Notification is created and stored in the notification collector.\n4. Rails finishes handling the request\n5. For each notification in the collector, Bullet will iterate over each\n   Presenter and will try to generate an inline message that will be appended to\n   the generated response body.\n6. The response is returned to the client.\n7. Bullet will try to generate an out-of-channel message for each notification.\n8. Bullet calls end_request for each detector.\n9. Goto 1.\n\n## Adding Notification Types\n\nIf you want to add more kinds of things that Bullet can detect, a little more\nwork is needed than if you were just adding a Presenter, but the concepts are\nsimilar.\n\n* Add the class to the DETECTORS constant in the main Bullet module\n* Add (if needed) Rails monkey patches to Bullet.enable\n* Add an autoload directive to lib/bullet/detector.rb\n* Create a corresponding notification class in the Bullet::Notification namespace\n* Add an autoload directive to lib/bullet/notification.rb\n\nAs a rule of thumb, you can assume that each Detector will have its own\nNotification class. If you follow the principle of Separation of Concerns I\ncan't really think of an example where one would deviate from this rule.\n\nSince the detection of pathological associations is a bit hairy, I'd recommend\nhaving a look at the counter cache detector and associated notification to get\na feel for what is needed to get off the ground.\n\n### Detectors\n\nThe only things you'll need to consider when building your Detector class is\nthat it will need to supply the .start_request, .end_request and .clear class\nmethods.\n\nSimple implementations are provided by Bullet::Detector::Base for start_request\nand end_request, you will have to supply your own clear method.\n\n### Notifications\n\nFor notifications you will want to supply a #title and #body instance method,\nand check to see if the #initialize and #full_notice methods in the\nBullet::Notification::Base class fit your needs.\n"
  },
  {
    "path": "MIT-LICENSE",
    "content": "Copyright (c) 2009 - 2024 Richard Huang (flyerhzm@gmail.com)\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Bullet\n\n![Main workflow](https://github.com/flyerhzm/bullet/actions/workflows/main.yml/badge.svg)\n[![Gem Version](https://badge.fury.io/rb/bullet.svg)](http://badge.fury.io/rb/bullet)\n[![AwesomeCode Status for flyerhzm/bullet](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/repos/flyerhzm/bullet)\n[![Coderwall Endorse](https://coderwall.com/flyerhzm/endorsecount.png)](https://coderwall.com/flyerhzm)\n\nThe 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.\n\nBest 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.\n\nBullet gem now supports **activerecord** >= 4.0 and **mongoid** >= 4.0.\n\nIf you use activerecord 2.x, please use bullet <= 4.5.0\n\nIf you use activerecord 3.x, please use bullet < 5.5.0\n\n## External Introduction\n\n* [https://rubyonrails.org/2009/10/22/community-highlights](https://rubyonrails.org/2009/10/22/community-highlights)\n* [https://www.shakacode.com/blog/eliminate-N-1-queries-with-bullet/](https://www.shakacode.com/blog/eliminate-N-1-queries-with-bullet/)\n* [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)\n\n## Install\n\nYou can install it as a gem:\n\n```\ngem install bullet\n```\n\nor add it into a Gemfile (Bundler):\n\n\n```ruby\ngem 'bullet', group: 'development'\n```\n\nenable the Bullet gem with generate command\n\n```ruby\nbundle exec rails g bullet:install\n```\nThe generate command will auto generate the default configuration and may ask to include in the test environment as well. See below for custom configuration.\n\n**Note**: make sure `bullet` gem is added after activerecord (rails) and\nmongoid.\n\n## Configuration\n\nBullet won't enable any notification systems unless you tell it to explicitly. Append to\n`config/environments/development.rb` initializer with the following code:\n\n```ruby\nconfig.after_initialize do\n  Bullet.enable = true\n  Bullet.sentry = true\n  Bullet.alert = true\n  Bullet.bullet_logger = true\n  Bullet.console = true\n  Bullet.xmpp = { :account  => 'bullets_account@jabber.org',\n                  :password => 'bullets_password_for_jabber',\n                  :receiver => 'your_account@jabber.org',\n                  :show_online_status => true }\n  Bullet.rails_logger = true\n  Bullet.honeybadger = true\n  Bullet.bugsnag = true\n  Bullet.appsignal = true\n  Bullet.airbrake = true\n  Bullet.rollbar = true\n  Bullet.add_footer = true\n  Bullet.skip_html_injection = false\n  Bullet.skip_http_headers = false\n  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]\n  Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]\n  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }\n  Bullet.opentelemetry = true\n  Bullet.raise = false\n  Bullet.always_append_html_body = false\n  Bullet.skip_user_in_notification = false\nend\n```\n\nThe notifier of Bullet is a wrap of [uniform_notifier](https://github.com/flyerhzm/uniform_notifier)\n\nThe code above will enable all of the Bullet notification systems:\n* `Bullet.enable`: enable Bullet gem, otherwise do nothing\n* `Bullet.sentry`: add notifications to sentry\n* `Bullet.alert`: pop up a JavaScript alert in the browser\n* `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)\n* `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)\n* `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.\n* `Bullet.rails_logger`: add warnings directly to the Rails log\n* `Bullet.honeybadger`: add notifications to Honeybadger\n* `Bullet.bugsnag`: add notifications to bugsnag\n* `Bullet.appsignal`: add notifications to AppSignal\n* `Bullet.airbrake`: add notifications to airbrake\n* `Bullet.rollbar`: add notifications to rollbar\n* `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.\n* `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.\n* `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.\n* `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app\n* `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.\n   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\n   item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file.\n* `Bullet.slack`: add notifications to slack\n* `Bullet.opentelemetry`: add notifications to OpenTelemetry\n* `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries\n* `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.\n* `Bullet.skip_user_in_notification`: exclude the OS user (`whoami`) from notifications.\n\n\nBullet also allows you to disable any of its detectors.\n\n```ruby\n# Each of these settings defaults to true\n\n# Detect N+1 queries\nBullet.n_plus_one_query_enable     = false\n\n# Detect eager-loaded associations which are not used\nBullet.unused_eager_loading_enable = false\n\n# Detect unnecessary COUNT queries which could be avoided\n# with a counter_cache\nBullet.counter_cache_enable        = false\n```\n\n## Safe list\n\nSometimes Bullet may notify you of query problems you don't care to fix, or\nwhich come from outside your code. You can add them to a safe list to ignore them:\n\n```ruby\nBullet.add_safelist :type => :n_plus_one_query, :class_name => \"Post\", :association => :comments\nBullet.add_safelist :type => :unused_eager_loading, :class_name => \"Post\", :association => :comments\nBullet.add_safelist :type => :counter_cache, :class_name => \"Country\", :association => :cities\n```\n\nIf you want to skip bullet in some specific controller actions, you can\ndo like\n\n```ruby\nclass ApplicationController < ActionController::Base\n  around_action :skip_bullet, if: -> { defined?(Bullet) }\n\n  def skip_bullet\n    previous_value = Bullet.enable?\n    Bullet.enable = false\n    yield\n  ensure\n    Bullet.enable = previous_value\n  end\nend\n```\n\n## Log\n\nThe Bullet log `log/bullet.log` will look something like this:\n\n* N+1 Query:\n\n```\n2009-08-25 20:40:17[INFO] USE eager loading detected:\n  Post => [:comments]·\n  Add to your query: .includes([:comments])\n2009-08-25 20:40:17[INFO] Call stack\n  /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'\n  /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'\n```\n\nThe 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.\n\n* Unused eager loading:\n\n```\n2009-08-25 20:53:56[INFO] AVOID eager loading detected\n  Post => [:comments]·\n  Remove from your query: .includes([:comments])\n2009-08-25 20:53:56[INFO] Call stack\n```\n\nThese lines are notifications that unused eager loadings have been encountered.\n\n* Need counter cache:\n\n```\n2009-09-11 09:46:50[INFO] Need Counter Cache\n  Post => [:comments]\n```\n\n## XMPP/Jabber and Airbrake Support\n\nsee [https://github.com/flyerhzm/uniform_notifier](https://github.com/flyerhzm/uniform_notifier)\n\n## Growl Support\n\nGrowl support is dropped from uniform_notifier 1.16.0, if you still want it, please use uniform_notifier 1.15.0.\n\n## URL query control\n\nYou can add the URL query parameter `skip_html_injection` to make the current HTML request behave as if `Bullet.skip_html_injection` is enabled,\ne.g. `http://localhost:3000/posts?skip_html_injection=true`\n\n## Important\n\nIf you find Bullet does not work for you, *please disable your browser's cache*.\n\n## Advanced\n\n### Work with ActiveJob\n\nInclude `Bullet::ActiveJob` in your `ApplicationJob`.\n\n```ruby\nclass ApplicationJob < ActiveJob::Base\n  include Bullet::ActiveJob if Rails.env.development?\nend\n```\n\n### Work with other background job solution\n\nUse the Bullet.profile method.\n\n```ruby\nclass ApplicationJob < ActiveJob::Base\n  around_perform do |_job, block|\n    Bullet.profile do\n      block.call\n    end\n  end\nend\n```\n\n### Work with sinatra\n\nConfigure and use `Bullet::Rack`.\n\n```ruby\nconfigure :development do\n  Bullet.enable = true\n  Bullet.bullet_logger = true\n  use Bullet::Rack\nend\n```\n\nIf your application generates a Content-Security-Policy via a separate middleware, ensure that `Bullet::Rack` is loaded _before_ that middleware.\n\n### Run in tests\n\nFirst you need to enable Bullet in the test environment.\n\n```ruby\n# config/environments/test.rb\nconfig.after_initialize do\n  Bullet.enable = true\n  Bullet.bullet_logger = true\n  Bullet.raise = true # raise an error if n+1 query occurs\nend\n```\n\nThen wrap each test in the Bullet api.\n\nWith RSpec:\n\n```ruby\n# spec/rails_helper.rb\nRSpec.configure do |config|\n  config.before(:each) do\n    Bullet.start_request\n  end\n\n  config.after(:each) do\n    Bullet.perform_out_of_channel_notifications if Bullet.notification?\n    Bullet.end_request\n  end\nend\n```\n\nWith Minitest:\n\n```ruby\n# test/test_helper.rb\nmodule ActiveSupport\n  class TestCase\n    def before_setup\n      Bullet.start_request\n      super\n    end\n\n    def after_teardown\n      super\n      Bullet.perform_out_of_channel_notifications if Bullet.notification?\n      Bullet.end_request\n    end\n  end\nend\n```\n\n## Debug Mode\n\nBullet outputs some details info, to enable debug mode, set\n`BULLET_DEBUG=true` env.\n\n## Contributors\n\n[https://github.com/flyerhzm/bullet/contributors](https://github.com/flyerhzm/bullet/contributors)\n\n## Demo\n\nBullet is designed to function as you browse through your application in development. To see it in action,\nyou can follow these steps to create, detect, and fix example query problems.\n\n1\\. Create an example application\n\n```\n$ rails new test_bullet\n$ cd test_bullet\n$ rails g scaffold post name:string\n$ rails g scaffold comment name:string post_id:integer\n$ bundle exec rails db:migrate\n```\n\n2\\. Change `app/models/post.rb` and `app/models/comment.rb`\n\n```ruby\nclass Post < ApplicationRecord\n  has_many :comments\nend\n\nclass Comment < ApplicationRecord\n  belongs_to :post\nend\n```\n\n3\\. Go to `rails c` and execute\n\n```ruby\npost1 = Post.create(:name => 'first')\npost2 = Post.create(:name => 'second')\npost1.comments.create(:name => 'first')\npost1.comments.create(:name => 'second')\npost2.comments.create(:name => 'third')\npost2.comments.create(:name => 'fourth')\n```\n\n4\\. Change the `app/views/posts/index.html.erb` to produce a N+1 query\n\n```\n<% @posts.each do |post| %>\n  <tr>\n    <td><%= post.name %></td>\n    <td><%= post.comments.map(&:name) %></td>\n    <td><%= link_to 'Show', post %></td>\n    <td><%= link_to 'Edit', edit_post_path(post) %></td>\n    <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>\n  </tr>\n<% end %>\n```\n\n5\\. Add the `bullet` gem to the `Gemfile`\n\n```ruby\ngem \"bullet\"\n```\n\nAnd run\n\n```\nbundle install\n```\n\n6\\. enable the Bullet gem with generate command\n\n```\nbundle exec rails g bullet:install\n```\n\n7\\. Start the server\n\n```\n$ rails s\n```\n\n8\\. Visit `http://localhost:3000/posts` in browser, and you will see a popup alert box that says\n\n```\nThe request has unused preload associations as follows:\nNone\nThe request has N+1 queries as follows:\nmodel: Post => associations: [comment]\n```\n\nwhich means there is a N+1 query from the Post object to its Comment association.\n\nIn the meantime, there's a log appended into `log/bullet.log` file\n\n```\n2010-03-07 14:12:18[INFO] N+1 Query in /posts\n  Post => [:comments]\n  Add to your finder: :include => [:comments]\n2010-03-07 14:12:18[INFO] N+1 Query method call stack\n  /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:14:in `_render_template__600522146_80203160_0'\n  /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `each'\n  /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `_render_template__600522146_80203160_0'\n  /home/flyerhzm/Downloads/test_bullet/app/controllers/posts_controller.rb:7:in `index'\n```\n\nThe generated SQL is:\n\n```\nPost Load (1.0ms)   SELECT * FROM \"posts\"\nComment Load (0.4ms)   SELECT * FROM \"comments\" WHERE (\"comments\".post_id = 1)\nComment Load (0.3ms)   SELECT * FROM \"comments\" WHERE (\"comments\".post_id = 2)\n```\n\n9\\. To fix the N+1 query, change `app/controllers/posts_controller.rb` file\n\n```ruby\ndef index\n  @posts = Post.includes(:comments)\n\n  respond_to do |format|\n    format.html # index.html.erb\n    format.xml  { render :xml => @posts }\n  end\nend\n```\n\n10\\. Refresh `http://localhost:3000/posts`. Now there's no alert box and nothing new in the log.\n\nThe generated SQL is:\n\n```\nPost Load (0.5ms)   SELECT * FROM \"posts\"\nComment Load (0.5ms)   SELECT \"comments\".* FROM \"comments\" WHERE (\"comments\".post_id IN (1,2))\n```\n\nN+1 query fixed. Cool!\n\n11\\. Now simulate unused eager loading. Change\n`app/controllers/posts_controller.rb` and\n`app/views/posts/index.html.erb`\n\n```ruby\ndef index\n  @posts = Post.includes(:comments)\n\n  respond_to do |format|\n    format.html # index.html.erb\n    format.xml  { render :xml => @posts }\n  end\nend\n```\n\n```\n<% @posts.each do |post| %>\n  <tr>\n    <td><%= post.name %></td>\n    <td><%= link_to 'Show', post %></td>\n    <td><%= link_to 'Edit', edit_post_path(post) %></td>\n    <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>\n  </tr>\n<% end %>\n```\n\n12\\. Refresh `http://localhost:3000/posts`, and you will see a popup alert box that says\n\n```\nThe request has unused preload associations as follows:\nmodel: Post => associations: [comment]\nThe request has N+1 queries as follows:\nNone\n```\n\nMeanwhile, there's a line appended to `log/bullet.log`\n\n```\n2009-08-25 21:13:22[INFO] Unused preload associations: PATH_INFO: /posts;    model: Post => associations: [comments]·\nRemove from your finder: :include => [:comments]\n```\n\n13\\. Simulate counter_cache. Change `app/controllers/posts_controller.rb`\nand `app/views/posts/index.html.erb`\n\n```ruby\ndef index\n  @posts = Post.all\n\n  respond_to do |format|\n    format.html # index.html.erb\n    format.xml  { render :xml => @posts }\n  end\nend\n```\n\n```\n<% @posts.each do |post| %>\n  <tr>\n    <td><%= post.name %></td>\n    <td><%= post.comments.size %></td>\n    <td><%= link_to 'Show', post %></td>\n    <td><%= link_to 'Edit', edit_post_path(post) %></td>\n    <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>\n  </tr>\n<% end %>\n```\n\n14\\. Refresh `http://localhost:3000/posts`, then you will see a popup alert box that says\n\n```\nNeed counter cache\n  Post => [:comments]\n```\n\nMeanwhile, there's a line appended to `log/bullet.log`\n\n```\n2009-09-11 10:07:10[INFO] Need Counter Cache\n  Post => [:comments]\n```\n\nCopyright (c) 2009 - 2022 Richard Huang (flyerhzm@gmail.com), released under the MIT license\n"
  },
  {
    "path": "Rakefile",
    "content": "$LOAD_PATH.unshift File.expand_path('lib', __dir__)\nrequire 'bundler'\nBundler.setup\n\nrequire 'rake'\nrequire 'rspec'\nrequire 'rspec/core/rake_task'\n\nrequire 'bullet/version'\n\ntask :build do\n  system 'gem build bullet.gemspec'\nend\n\ntask install: :build do\n  system \"sudo gem install bullet-#{Bullet::VERSION}.gem\"\nend\n\ntask release: :build do\n  puts \"Tagging #{Bullet::VERSION}...\"\n  system \"git tag -a #{Bullet::VERSION} -m 'Tagging #{Bullet::VERSION}'\"\n  puts 'Pushing to Github...'\n  system 'git push --tags'\n  puts 'Pushing to rubygems.org...'\n  system \"gem push bullet-#{Bullet::VERSION}.gem\"\nend\n\nRSpec::Core::RakeTask.new(:spec) do |spec|\n  spec.pattern = 'spec/**/*_spec.rb'\nend\n\nRSpec::Core::RakeTask.new('spec:progress') do |spec|\n  spec.rspec_opts = %w[--format progress]\n  spec.pattern = 'spec/**/*_spec.rb'\nend\n\nbegin\n  require 'rdoc/task'\n\n  desc 'Generate documentation for the plugin.'\n  Rake::RDocTask.new do |rdoc|\n    rdoc.rdoc_dir = 'rdoc'\n    rdoc.title = \"bullet #{Bullet::VERSION}\"\n    rdoc.rdoc_files.include('README*')\n    rdoc.rdoc_files.include('lib/**/*.rb')\n  end\nrescue LoadError\n  puts 'RDocTask is not supported for this platform'\nend\n\ntask default: :spec\n"
  },
  {
    "path": "bullet.gemspec",
    "content": "# frozen_string_literal: true\n\nlib = File.expand_path('lib', __dir__)\n$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)\n\nrequire 'bullet/version'\n\nGem::Specification.new do |s|\n  s.name        = 'bullet'\n  s.version     = Bullet::VERSION\n  s.platform    = Gem::Platform::RUBY\n  s.authors     = ['Richard Huang']\n  s.email       = ['flyerhzm@gmail.com']\n  s.homepage    = 'https://github.com/flyerhzm/bullet'\n  s.summary     = 'help to kill N+1 queries and unused eager loading.'\n  s.description = 'help to kill N+1 queries and unused eager loading.'\n  s.metadata    = {\n    'changelog_uri' => 'https://github.com/flyerhzm/bullet/blob/main/CHANGELOG.md',\n    'source_code_uri' => 'https://github.com/flyerhzm/bullet'\n  }\n\n  s.license = 'MIT'\n\n  s.required_ruby_version = '>= 2.7.0'\n  s.required_rubygems_version = '>= 1.3.6'\n\n  s.add_runtime_dependency 'activesupport', '>= 3.0.0'\n  s.add_runtime_dependency 'uniform_notifier', '~> 1.11'\n\n  s.files = Dir.chdir(__dir__) do\n    `git ls-files -z`.split(\"\\x0\").reject do |file|\n      file.start_with?(*%w[.git .rspec Gemfile Guardfile Hacking Rakefile\n                           bullet.gemspec perf rails spec test.sh update.sh])\n    end\n  end\n  s.require_paths = ['lib']\nend\n"
  },
  {
    "path": "lib/bullet/active_job.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module ActiveJob\n    def self.included(base)\n      base.class_eval do\n        around_perform do |_job, block|\n          Bullet.profile { block.call }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record4.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.class_eval do\n        class << self\n          alias_method :origin_find_by_sql, :find_by_sql\n          def find_by_sql(sql, binds = [])\n            result = origin_find_by_sql(sql, binds)\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      end\n\n      ::ActiveRecord::Relation.class_eval do\n        alias_method :origin_to_a, :to_a\n        # if select a collection of objects, then these objects have possible to cause N+1 query.\n        # if select only one object, then the only one object has impossible to cause N+1 query.\n        def to_a\n          records = origin_to_a\n          if Bullet.start?\n            if records.size > 1\n              Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n              Bullet::Detector::CounterCache.add_possible_objects(records)\n            elsif records.size == 1\n              Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n              Bullet::Detector::CounterCache.add_impossible_object(records.first)\n            end\n          end\n          records\n        end\n      end\n\n      ::ActiveRecord::Persistence.class_eval do\n        def _create_record_with_bullet(*args)\n          _create_record_without_bullet(*args).tap do\n            Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n          end\n        end\n        alias_method_chain :_create_record, :bullet\n      end\n\n      ::ActiveRecord::Associations::Preloader.class_eval do\n        # include query for one to many associations.\n        # keep this eager loadings.\n        alias_method :origin_initialize, :initialize\n        def initialize(records, associations, preload_scope = nil)\n          origin_initialize(records, associations, preload_scope)\n\n          if Bullet.start?\n            records = [records].flatten.compact.uniq\n            return if records.empty?\n\n            records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }\n            Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)\n          end\n        end\n      end\n\n      ::ActiveRecord::FinderMethods.class_eval do\n        # add includes in scope\n        alias_method :origin_find_with_associations, :find_with_associations\n        def find_with_associations\n          records = origin_find_with_associations\n          if Bullet.start?\n            associations = (eager_load_values + includes_values).uniq\n            records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }\n            Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)\n          end\n          records\n        end\n      end\n\n      ::ActiveRecord::Associations::JoinDependency.class_eval do\n        alias_method :origin_instantiate, :instantiate\n        alias_method :origin_construct_association, :construct_association\n\n        def instantiate(rows)\n          @bullet_eager_loadings = {}\n          records = origin_instantiate(rows)\n\n          if Bullet.start?\n            @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n              objects = eager_loadings_hash.keys\n              Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)\n            end\n          end\n          records\n        end\n\n        # call join associations\n        def construct_association(record, join, row)\n          result = origin_construct_association(record, join, row)\n\n          if Bullet.start?\n            associations = [join.reflection.name]\n            if join.reflection.nested?\n              associations << join.reflection.through_reflection.name\n            end\n            associations.each do |association|\n              Bullet::Detector::Association.add_object_associations(record, association)\n              Bullet::Detector::NPlusOneQuery.call_association(record, association)\n              @bullet_eager_loadings[record.class] ||= {}\n              @bullet_eager_loadings[record.class][record] ||= Set.new\n              @bullet_eager_loadings[record.class][record] << association\n            end\n          end\n\n          result\n        end\n      end\n\n      ::ActiveRecord::Associations::CollectionAssociation.class_eval do\n        # call one to many associations\n        alias_method :origin_load_target, :load_target\n        def load_target\n          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?\n          origin_load_target\n        end\n\n        alias_method :origin_include?, :include?\n        def include?(object)\n          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?\n          origin_include?(object)\n        end\n      end\n\n      ::ActiveRecord::Associations::HasManyAssociation.class_eval do\n        alias_method :origin_empty?, :empty?\n        def empty?\n          if Bullet.start? && !loaded? && !has_cached_counter?(@reflection)\n            Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)\n          end\n          origin_empty?\n        end\n      end\n\n      ::ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do\n        alias_method :origin_empty?, :empty?\n        def empty?\n          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start? && !loaded?\n          origin_empty?\n        end\n      end\n\n      ::ActiveRecord::Associations::SingularAssociation.class_eval do\n        # call has_one and belongs_to associations\n        alias_method :origin_reader, :reader\n        def reader(force_reload = false)\n          result = origin_reader(force_reload)\n          if Bullet.start?\n            unless @inversed\n              Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)\n              Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n            end\n          end\n          result\n        end\n      end\n\n      ::ActiveRecord::Associations::HasManyAssociation.class_eval do\n        alias_method :origin_has_cached_counter?, :has_cached_counter?\n\n        def has_cached_counter?(reflection = reflection())\n          result = origin_has_cached_counter?(reflection)\n          Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) if Bullet.start? && !result\n          result\n        end\n      end\n\n      ::ActiveRecord::Associations::CollectionProxy.class_eval do\n        def count(column_name = nil, options = {})\n          if Bullet.start?\n            Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)\n            Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)\n          end\n          super(column_name, options)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record41.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.class_eval do\n        class << self\n          alias_method :origin_find_by_sql, :find_by_sql\n          def find_by_sql(sql, binds = [])\n            result = origin_find_by_sql(sql, binds)\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      end\n\n      ::ActiveRecord::Relation.class_eval do\n        alias_method :origin_to_a, :to_a\n\n        # if select a collection of objects, then these objects have possible to cause N+1 query.\n        # if select only one object, then the only one object has impossible to cause N+1 query.\n        def to_a\n          records = origin_to_a\n          if Bullet.start?\n            if records.first.class.name !~ /^HABTM_/\n              if records.size > 1\n                Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                Bullet::Detector::CounterCache.add_possible_objects(records)\n              elsif records.size == 1\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                Bullet::Detector::CounterCache.add_impossible_object(records.first)\n              end\n            end\n          end\n          records\n        end\n      end\n\n      ::ActiveRecord::Persistence.class_eval do\n        def _create_record_with_bullet(*args)\n          _create_record_without_bullet(*args).tap do\n            Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n          end\n        end\n        alias_method_chain :_create_record, :bullet\n      end\n\n      ::ActiveRecord::Associations::Preloader.class_eval do\n        alias_method :origin_preloaders_on, :preloaders_on\n\n        def preloaders_on(association, records, scope)\n          if Bullet.start?\n            records.compact!\n            if records.first.class.name !~ /^HABTM_/\n              records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }\n              Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)\n            end\n          end\n          origin_preloaders_on(association, records, scope)\n        end\n      end\n\n      ::ActiveRecord::FinderMethods.class_eval do\n        # add includes in scope\n        alias_method :origin_find_with_associations, :find_with_associations\n        def find_with_associations\n          return origin_find_with_associations { |r| yield r } if block_given?\n\n          records = origin_find_with_associations\n          if Bullet.start?\n            associations = (eager_load_values + includes_values).uniq\n            records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }\n            Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)\n          end\n          records\n        end\n      end\n\n      ::ActiveRecord::Associations::JoinDependency.class_eval do\n        alias_method :origin_instantiate, :instantiate\n        alias_method :origin_construct_model, :construct_model\n\n        def instantiate(result_set, aliases)\n          @bullet_eager_loadings = {}\n          records = origin_instantiate(result_set, aliases)\n\n          if Bullet.start?\n            @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n              objects = eager_loadings_hash.keys\n              Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)\n            end\n          end\n          records\n        end\n\n        # call join associations\n        def construct_model(record, node, row, model_cache, id, aliases)\n          result = origin_construct_model(record, node, row, model_cache, id, aliases)\n\n          if Bullet.start?\n            associations = [node.reflection.name]\n            if node.reflection.nested?\n              associations << node.reflection.through_reflection.name\n            end\n            associations.each do |association|\n              Bullet::Detector::Association.add_object_associations(record, association)\n              Bullet::Detector::NPlusOneQuery.call_association(record, association)\n              @bullet_eager_loadings[record.class] ||= {}\n              @bullet_eager_loadings[record.class][record] ||= Set.new\n              @bullet_eager_loadings[record.class][record] << association\n            end\n          end\n\n          result\n        end\n      end\n\n      ::ActiveRecord::Associations::CollectionAssociation.class_eval do\n        # call one to many associations\n        alias_method :origin_load_target, :load_target\n        def load_target\n          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start? && !@inversed\n          origin_load_target\n        end\n\n        alias_method :origin_empty?, :empty?\n        def empty?\n          if Bullet.start? && !has_cached_counter?(@reflection)\n            Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)\n          end\n          origin_empty?\n        end\n\n        alias_method :origin_include?, :include?\n        def include?(object)\n          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?\n          origin_include?(object)\n        end\n      end\n\n      ::ActiveRecord::Associations::SingularAssociation.class_eval do\n        # call has_one and belongs_to associations\n        alias_method :origin_reader, :reader\n        def reader(force_reload = false)\n          result = origin_reader(force_reload)\n          if Bullet.start?\n            if @owner.class.name !~ /^HABTM_/ && !@inversed\n              Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)\n              Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n            end\n          end\n          result\n        end\n      end\n\n      ::ActiveRecord::Associations::HasManyAssociation.class_eval do\n        alias_method :origin_count_records, :count_records\n        def count_records\n          result = has_cached_counter?\n          Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) if Bullet.start? && !result\n          origin_count_records\n        end\n      end\n\n      ::ActiveRecord::Associations::CollectionProxy.class_eval do\n        def count(column_name = nil, options = {})\n          if Bullet.start?\n            Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)\n            Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)\n          end\n          super(column_name, options)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record42.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.class_eval do\n        class << self\n          alias_method :origin_find, :find\n          def find(*args)\n            result = origin_find(*args)\n            if Bullet.start?\n              if result.is_a? Array\n                Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                Bullet::Detector::CounterCache.add_possible_objects(result)\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n\n          alias_method :origin_find_by_sql, :find_by_sql\n          def find_by_sql(sql, binds = [])\n            result = origin_find_by_sql(sql, binds)\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      end\n\n      ::ActiveRecord::Persistence.class_eval do\n        def _create_record_with_bullet(*args)\n          _create_record_without_bullet(*args).tap do\n            Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n          end\n        end\n        alias_method_chain :_create_record, :bullet\n      end\n\n      ::ActiveRecord::Relation.class_eval do\n        alias_method :origin_to_a, :to_a\n\n        # if select a collection of objects, then these objects have possible to cause N+1 query.\n        # if select only one object, then the only one object has impossible to cause N+1 query.\n        def to_a\n          records = origin_to_a\n          if Bullet.start?\n            if records.first.class.name !~ /^HABTM_/\n              if records.size > 1\n                Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                Bullet::Detector::CounterCache.add_possible_objects(records)\n              elsif records.size == 1\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                Bullet::Detector::CounterCache.add_impossible_object(records.first)\n              end\n            end\n          end\n          records\n        end\n      end\n\n      ::ActiveRecord::Associations::Preloader.class_eval do\n        alias_method :origin_preloaders_on, :preloaders_on\n\n        def preloaders_on(association, records, scope)\n          if Bullet.start?\n            records.compact!\n            if records.first.class.name !~ /^HABTM_/\n              records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }\n              Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)\n            end\n          end\n          origin_preloaders_on(association, records, scope)\n        end\n      end\n\n      ::ActiveRecord::FinderMethods.class_eval do\n        # add includes in scope\n        alias_method :origin_find_with_associations, :find_with_associations\n        def find_with_associations\n          return origin_find_with_associations { |r| yield r } if block_given?\n\n          records = origin_find_with_associations\n          if Bullet.start?\n            associations = (eager_load_values + includes_values).uniq\n            records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }\n            Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)\n          end\n          records\n        end\n      end\n\n      ::ActiveRecord::Associations::JoinDependency.class_eval do\n        alias_method :origin_instantiate, :instantiate\n        alias_method :origin_construct, :construct\n        alias_method :origin_construct_model, :construct_model\n\n        def instantiate(result_set, aliases)\n          @bullet_eager_loadings = {}\n          records = origin_instantiate(result_set, aliases)\n\n          if Bullet.start?\n            @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n              objects = eager_loadings_hash.keys\n              Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)\n            end\n          end\n          records\n        end\n\n        def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)\n          if Bullet.start?\n            unless ar_parent.nil?\n              parent.children.each do |node|\n                key = aliases.column_alias(node, node.primary_key)\n                id = row[key]\n                next unless id.nil?\n\n                associations = [node.reflection.name]\n                if node.reflection.nested?\n                  associations << node.reflection.through_reflection.name\n                end\n                associations.each do |association|\n                  Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                  Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                  @bullet_eager_loadings[ar_parent.class] ||= {}\n                  @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                  @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                end\n              end\n            end\n          end\n\n          origin_construct(ar_parent, parent, row, rs, seen, model_cache, aliases)\n        end\n\n        # call join associations\n        def construct_model(record, node, row, model_cache, id, aliases)\n          result = origin_construct_model(record, node, row, model_cache, id, aliases)\n\n          if Bullet.start?\n            associations = [node.reflection.name]\n            if node.reflection.nested?\n              associations << node.reflection.through_reflection.name\n            end\n            associations.each do |association|\n              Bullet::Detector::Association.add_object_associations(record, association)\n              Bullet::Detector::NPlusOneQuery.call_association(record, association)\n              @bullet_eager_loadings[record.class] ||= {}\n              @bullet_eager_loadings[record.class][record] ||= Set.new\n              @bullet_eager_loadings[record.class][record] << association\n            end\n          end\n\n          result\n        end\n      end\n\n      ::ActiveRecord::Associations::CollectionAssociation.class_eval do\n        # call one to many associations\n        alias_method :origin_load_target, :load_target\n        def load_target\n          records = origin_load_target\n\n          if Bullet.start?\n            Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed\n            if records.first.class.name !~ /^HABTM_/\n              if records.size > 1\n                Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                Bullet::Detector::CounterCache.add_possible_objects(records)\n              elsif records.size == 1\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                Bullet::Detector::CounterCache.add_impossible_object(records.first)\n              end\n            end\n          end\n          records\n        end\n\n        alias_method :origin_empty?, :empty?\n        def empty?\n          if Bullet.start? && !has_cached_counter?(@reflection)\n            Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)\n          end\n          origin_empty?\n        end\n\n        alias_method :origin_include?, :include?\n        def include?(object)\n          Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?\n          origin_include?(object)\n        end\n      end\n\n      ::ActiveRecord::Associations::SingularAssociation.class_eval do\n        # call has_one and belongs_to associations\n        alias_method :origin_reader, :reader\n        def reader(force_reload = false)\n          result = origin_reader(force_reload)\n\n          if Bullet.start?\n            if @owner.class.name !~ /^HABTM_/ && !@inversed\n              Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)\n\n              if Bullet::Detector::NPlusOneQuery.impossible?(@owner)\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n              else\n                Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n              end\n            end\n          end\n          result\n        end\n      end\n\n      ::ActiveRecord::Associations::HasManyAssociation.class_eval do\n        alias_method :origin_many_empty?, :empty?\n        def empty?\n          result = origin_many_empty?\n          if Bullet.start? && !has_cached_counter?(@reflection)\n            Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)\n          end\n          result\n        end\n\n        alias_method :origin_count_records, :count_records\n        def count_records\n          result = has_cached_counter?\n          Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) if Bullet.start? && !result\n          origin_count_records\n        end\n      end\n\n      ::ActiveRecord::Associations::CollectionProxy.class_eval do\n        def count(column_name = nil, options = {})\n          if Bullet.start?\n            Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)\n            Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)\n          end\n          super(column_name, options)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record5.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader.prepend(\n        Module.new do\n          def preloaders_for_one(association, records, scope)\n            if Bullet.start?\n              records.compact!\n              if records.first.class.name !~ /^HABTM_/\n                records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::FinderMethods.prepend(\n        Module.new do\n          # add includes in scope\n          def find_with_associations\n            return super { |r| yield r } if block_given?\n\n            records = super\n            if Bullet.start?\n              associations = (eager_load_values + includes_values).uniq\n              records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }\n              Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)\n            end\n            records\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          if ::ActiveRecord::Associations::JoinDependency.instance_method(:instantiate).parameters.last[0] == :block\n            # ActiveRecord >= 5.1.5\n            def instantiate(result_set, &block)\n              @bullet_eager_loadings = {}\n              records = super\n\n              if Bullet.start?\n                @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                  objects = eager_loadings_hash.keys\n                  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                    objects,\n                    eager_loadings_hash[objects.first].to_a\n                  )\n                end\n              end\n              records\n            end\n          else\n            # ActiveRecord <= 5.1.4\n            def instantiate(result_set, aliases)\n              @bullet_eager_loadings = {}\n              records = super\n\n              if Bullet.start?\n                @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                  objects = eager_loadings_hash.keys\n                  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                    objects,\n                    eager_loadings_hash[objects.first].to_a\n                  )\n                end\n              end\n              records\n            end\n          end\n\n          def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, aliases)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                refl = reflection.through_reflection\n                association = owner.association(refl.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if refl.through_reflection?\n                    refl = refl.through_reflection while refl.through_reflection?\n\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def target\n            result = super()\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/ && !@inversed\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record52.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader.prepend(\n        Module.new do\n          def preloaders_for_one(association, records, scope)\n            if Bullet.start?\n              records.compact!\n              if records.first.class.name !~ /^HABTM_/\n                records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, aliases)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def target\n            result = super()\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/ && !@inversed\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association reflection.through_reflection.name\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record60.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader.prepend(\n        Module.new do\n          def preloaders_for_one(association, records, scope, polymorphic_parent)\n            if Bullet.start?\n              records.compact!\n              if records.first.class.name !~ /^HABTM_/\n                records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)\n              end\n            end\n            super\n          end\n\n          def preloaders_for_reflection(reflection, records, scope)\n            if Bullet.start?\n              records.compact!\n              if records.first.class.name !~ /^HABTM_/\n                records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(\n        Module.new do\n          def preloaded_records\n            if Bullet.start? && !defined?(@preloaded_records)\n              source_preloaders.each do |source_preloader|\n                reflection_name = source_preloader.send(:reflection).name\n                source_preloader.send(:owners).each do |owner|\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)\n                end\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, seen, model_cache)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def target\n            result = super()\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/ && !@inversed\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association(reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name,\n                caller_locations\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record61.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader.prepend(\n        Module.new do\n          def preloaders_for_one(association, records, scope, polymorphic_parent)\n            if Bullet.start?\n              records.compact!\n              if records.first.class.name !~ /^HABTM_/\n                records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)\n              end\n            end\n            super\n          end\n\n          def preloaders_for_reflection(reflection, records, scope)\n            if Bullet.start?\n              records.compact!\n              if records.first.class.name !~ /^HABTM_/\n                records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(\n        Module.new do\n          def preloaded_records\n            if Bullet.start? && !defined?(@preloaded_records)\n              source_preloaders.each do |source_preloader|\n                reflection_name = source_preloader.send(:reflection).name\n                source_preloader.send(:owners).each do |owner|\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)\n                end\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, strict_loading_value, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, strict_loading_value)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def target\n            result = super()\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/ && !@inversed\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association(reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name,\n                caller_locations\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record70.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Batch.prepend(\n        Module.new do\n          def call\n            if Bullet.start?\n              @preloaders.each do |preloader|\n                preloader.records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, preloader.associations)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Branch.prepend(\n        Module.new do\n          def preloaders_for_reflection(reflection, reflection_records)\n            if Bullet.start?\n              reflection_records.compact!\n              if reflection_records.first.class.name !~ /^HABTM_/\n                reflection_records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, reflection.name)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(\n        Module.new do\n          def source_preloaders\n            if Bullet.start? && !defined?(@source_preloaders)\n              preloaders = super\n              preloaders.each do |preloader|\n                reflection_name = preloader.send(:reflection).name\n                preloader.send(:owners).each do |owner|\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)\n                end\n              end\n            else\n              super\n            end\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, strict_loading_value, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, strict_loading_value)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n\n          def inversed_from_queries(record)\n            if Bullet.start? && inversable?(record)\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def reader\n            result = super\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association(reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name,\n                caller_locations\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record71.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Batch.prepend(\n        Module.new do\n          def call\n            if Bullet.start?\n              @preloaders.each do |preloader|\n                preloader.records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, preloader.associations)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Branch.prepend(\n        Module.new do\n          def preloaders_for_reflection(reflection, reflection_records)\n            if Bullet.start?\n              reflection_records.compact!\n              if reflection_records.first.class.name !~ /^HABTM_/\n                reflection_records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, reflection.name)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(\n        Module.new do\n          def source_preloaders\n            if Bullet.start? && !defined?(@source_preloaders)\n              preloaders = super\n              preloaders.each do |preloader|\n                reflection_name = preloader.send(:reflection).name\n                preloader.send(:owners).each do |owner|\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)\n                end\n              end\n            else\n              super\n            end\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, strict_loading_value, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, strict_loading_value)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n\n          def inversed_from_queries(record)\n            if Bullet.start? && inversable?(record)\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def reader\n            result = super\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association(reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name,\n                caller_locations\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record72.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Batch.prepend(\n        Module.new do\n          def call\n            if Bullet.start?\n              @preloaders.each do |preloader|\n                preloader.records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, preloader.associations)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Branch.prepend(\n        Module.new do\n          def preloaders_for_reflection(reflection, reflection_records)\n            if Bullet.start?\n              reflection_records.compact!\n              if reflection_records.first.class.name !~ /^HABTM_/\n                reflection_records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, reflection.name)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(\n        Module.new do\n          def source_preloaders\n            if Bullet.start? && !defined?(@source_preloaders)\n              preloaders = super\n              preloaders.each do |preloader|\n                reflection_name = preloader.send(:reflection).name\n                preloader.send(:owners).each do |owner|\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)\n                end\n              end\n            else\n              super\n            end\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, strict_loading_value, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, strict_loading_value)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n\n          def inversed_from_queries(record)\n            if Bullet.start? && inversable?(record)\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def reader\n            result = super\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association(reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name,\n                caller_locations\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record80.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Batch.prepend(\n        Module.new do\n          def call\n            if Bullet.start?\n              @preloaders.each do |preloader|\n                preloader.records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, preloader.associations)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Branch.prepend(\n        Module.new do\n          def preloaders_for_reflection(reflection, reflection_records)\n            if Bullet.start?\n              reflection_records.compact!\n              if reflection_records.first.class.name !~ /^HABTM_/\n                reflection_records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, reflection.name)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(\n        Module.new do\n          def source_preloaders\n            if Bullet.start? && !defined?(@source_preloaders)\n              preloaders = super\n              preloaders.each do |preloader|\n                reflection_name = preloader.send(:reflection).name\n                preloader.send(:owners).each do |owner|\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)\n                end\n              end\n            else\n              super\n            end\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, strict_loading_value, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, strict_loading_value)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n\n          def inversed_from_queries(record)\n            if Bullet.start? && inversable?(record)\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def reader\n            result = super\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association(reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name,\n                caller_locations\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/active_record81.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module SaveWithBulletSupport\n    def _create_record(*)\n      super do\n        Bullet::Detector::NPlusOneQuery.update_inversed_object(self)\n        Bullet::Detector::NPlusOneQuery.add_impossible_object(self)\n        yield(self) if block_given?\n      end\n    end\n  end\n\n  module ActiveRecord\n    def self.enable\n      require 'active_record'\n      ::ActiveRecord::Base.extend(\n        Module.new do\n          def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)\n            result = super\n            if Bullet.start?\n              if result.is_a? Array\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              elsif result.is_a? ::ActiveRecord::Base\n                Bullet::Detector::NPlusOneQuery.add_impossible_object(result)\n                Bullet::Detector::CounterCache.add_impossible_object(result)\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Base.prepend(SaveWithBulletSupport)\n\n      ::ActiveRecord::Relation.prepend(\n        Module.new do\n          # if select a collection of objects, then these objects have possible to cause N+1 query.\n          # if select only one object, then the only one object has impossible to cause N+1 query.\n          def records\n            result = super\n            if Bullet.start?\n              if result.first.class.name !~ /^HABTM_/\n                if result.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)\n                  Bullet::Detector::CounterCache.add_possible_objects(result)\n                elsif result.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(result.first)\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Batch.prepend(\n        Module.new do\n          def call\n            if Bullet.start?\n              @preloaders.each do |preloader|\n                preloader.records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, preloader.associations)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::Branch.prepend(\n        Module.new do\n          def preloaders_for_reflection(reflection, reflection_records)\n            if Bullet.start?\n              reflection_records.compact!\n              if reflection_records.first.class.name !~ /^HABTM_/\n                reflection_records.each { |record|\n                  Bullet::Detector::Association.add_object_associations(record, reflection.name)\n                }\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)\n              end\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(\n        Module.new do\n          def source_preloaders\n            if Bullet.start? && !defined?(@source_preloaders)\n              preloaders = super\n              preloaders.each do |preloader|\n                reflection_name = preloader.send(:reflection).name\n                preloader.send(:owners).each do |owner|\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)\n                end\n              end\n            else\n              super\n            end\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::JoinDependency.prepend(\n        Module.new do\n          def instantiate(result_set, strict_loading_value, &block)\n            @bullet_eager_loadings = {}\n            records = super\n\n            if Bullet.start?\n              @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|\n                objects = eager_loadings_hash.keys\n                Bullet::Detector::UnusedEagerLoading.add_eager_loadings(\n                  objects,\n                  eager_loadings_hash[objects.first].to_a\n                )\n              end\n            end\n            records\n          end\n\n          def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)\n            if Bullet.start?\n              unless ar_parent.nil?\n                parent.children.each do |node|\n                  key = aliases.column_alias(node, node.primary_key)\n                  id = row[key]\n                  next unless id.nil?\n\n                  associations = [node.reflection.name]\n                  if node.reflection.through_reflection?\n                    associations << node.reflection.through_reflection.name\n                  end\n                  associations.each do |association|\n                    Bullet::Detector::Association.add_object_associations(ar_parent, association)\n                    Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)\n                    @bullet_eager_loadings[ar_parent.class] ||= {}\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new\n                    @bullet_eager_loadings[ar_parent.class][ar_parent] << association\n                  end\n                end\n              end\n            end\n\n            super\n          end\n\n          # call join associations\n          def construct_model(record, node, row, model_cache, id, strict_loading_value)\n            result = super\n\n            if Bullet.start?\n              associations = [node.reflection.name]\n              if node.reflection.through_reflection?\n                associations << node.reflection.through_reflection.name\n              end\n              associations.each do |association|\n                Bullet::Detector::Association.add_object_associations(record, association)\n                Bullet::Detector::NPlusOneQuery.call_association(record, association)\n                @bullet_eager_loadings[record.class] ||= {}\n                @bullet_eager_loadings[record.class][record] ||= Set.new\n                @bullet_eager_loadings[record.class][record] << association\n              end\n            end\n\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::Association.prepend(\n        Module.new do\n          def inversed_from(record)\n            if Bullet.start?\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n\n          def inversed_from_queries(record)\n            if Bullet.start? && inversable?(record)\n              Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionAssociation.prepend(\n        Module.new do\n          def load_target\n            records = super\n\n            if Bullet.start?\n              if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                association = owner.association(reflection.through_reflection.name)\n                if association.loaded?\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n              end\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n              if records.first.class.name !~ /^HABTM_/\n                if records.size > 1\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n                  Bullet::Detector::CounterCache.add_possible_objects(records)\n                elsif records.size == 1\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n                  Bullet::Detector::CounterCache.add_impossible_object(records.first)\n                end\n              end\n            end\n            records\n          end\n\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def include?(object)\n            Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::SingularAssociation.prepend(\n        Module.new do\n          # call has_one and belongs_to associations\n          def reader\n            result = super\n\n            if Bullet.start?\n              if owner.class.name !~ /^HABTM_/\n                if is_a? ::ActiveRecord::Associations::ThroughAssociation\n                  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)\n                  association = owner.association(reflection.through_reflection.name)\n                  Array.wrap(association.target).each do |through_record|\n                    Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)\n                  end\n\n                  if reflection.through_reflection != through_reflection\n                    Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)\n                  end\n                end\n                Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)\n\n                if Bullet::Detector::NPlusOneQuery.impossible?(owner)\n                  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n                else\n                  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result\n                end\n              end\n            end\n            result\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::HasManyAssociation.prepend(\n        Module.new do\n          def empty?\n            if Bullet.start? && !reflection.has_cached_counter?\n              Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)\n            end\n            super\n          end\n\n          def count_records\n            result = reflection.has_cached_counter?\n            if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)\n            end\n            super\n          end\n        end\n      )\n\n      ::ActiveRecord::Associations::CollectionProxy.prepend(\n        Module.new do\n          def count(column_name = nil)\n            if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)\n              Bullet::Detector::CounterCache.add_counter_cache(\n                proxy_association.owner,\n                proxy_association.reflection.name\n              )\n              Bullet::Detector::NPlusOneQuery.call_association(\n                proxy_association.owner,\n                proxy_association.reflection.name,\n                caller_locations\n              )\n            end\n            super(column_name)\n          end\n        end\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/bullet_xhr.js",
    "content": "(function () {\n  var oldOpen = window.XMLHttpRequest.prototype.open;\n  var oldSend = window.XMLHttpRequest.prototype.send;\n\n  /**\n   * Return early if we've already extended prototype. This prevents\n   * \"maximum call stack exceeded\" errors when used with Turbolinks.\n   * See https://github.com/flyerhzm/bullet/issues/454\n   */\n  if (isBulletInitiated()) return;\n\n  function isBulletInitiated() {\n    return oldOpen.name == \"bulletXHROpen\" && oldSend.name == \"bulletXHRSend\";\n  }\n  function bulletXHROpen(_, url) {\n    this._storedUrl = url;\n    return Reflect.apply(oldOpen, this, arguments);\n  }\n  function bulletXHRSend() {\n    if (this.onload) {\n      this._storedOnload = this.onload;\n    }\n    this.onload = null;\n    this.addEventListener(\"load\", bulletXHROnload);\n    return Reflect.apply(oldSend, this, arguments);\n  }\n  function bulletXHROnload() {\n    if (\n      this._storedUrl.startsWith(window.location.protocol + \"//\" + window.location.host) ||\n      !this._storedUrl.startsWith(\"http\") // For relative paths\n    ) {\n      var bulletFooterText = this.getResponseHeader(\"X-bullet-footer-text\");\n      if (bulletFooterText) {\n        setTimeout(function () {\n          var oldHtml = document.querySelector(\"#bullet-footer\").innerHTML.split(\"<br>\");\n          var header = oldHtml[0];\n          oldHtml = oldHtml.slice(1, oldHtml.length);\n          var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));\n          newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent\n          document.querySelector(\"#bullet-footer\").innerHTML = `${header}<br>${newHtml.join(\"<br>\")}`;\n        }, 0);\n      }\n      var bulletConsoleText = this.getResponseHeader(\"X-bullet-console-text\");\n      if (bulletConsoleText && typeof console !== \"undefined\" && console.log) {\n        setTimeout(function () {\n          JSON.parse(bulletConsoleText).forEach((message) => {\n            if (console.groupCollapsed && console.groupEnd) {\n              console.groupCollapsed(\"Uniform Notifier\");\n              console.log(message);\n              console.groupEnd();\n            } else {\n              console.log(message);\n            }\n          });\n        }, 0);\n      }\n    }\n    if (this._storedOnload) {\n      return Reflect.apply(this._storedOnload, this, arguments);\n    }\n  }\n  window.XMLHttpRequest.prototype.open = bulletXHROpen;\n  window.XMLHttpRequest.prototype.send = bulletXHRSend;\n})();\n"
  },
  {
    "path": "lib/bullet/dependency.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Dependency\n    def mongoid?\n      @mongoid ||= defined?(::Mongoid)\n    end\n\n    def active_record?\n      @active_record ||= defined?(::ActiveRecord)\n    end\n\n    def active_record_version\n      @active_record_version ||=\n        begin\n          if active_record40?\n            'active_record4'\n          elsif active_record41?\n            'active_record41'\n          elsif active_record42?\n            'active_record42'\n          elsif active_record50?\n            'active_record5'\n          elsif active_record51?\n            'active_record5'\n          elsif active_record52?\n            'active_record52'\n          elsif active_record60?\n            'active_record60'\n          elsif active_record61?\n            'active_record61'\n          elsif active_record70?\n            'active_record70'\n          elsif active_record71?\n            'active_record71'\n          elsif active_record72?\n            'active_record72'\n          elsif active_record80?\n            'active_record80'\n          elsif active_record81?\n            'active_record81'\n          else\n            raise \"Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet\"\n          end\n        end\n    end\n\n    def mongoid_version\n      @mongoid_version ||=\n        begin\n          if mongoid4x?\n            'mongoid4x'\n          elsif mongoid5x?\n            'mongoid5x'\n          elsif mongoid6x?\n            'mongoid6x'\n          elsif mongoid7x?\n            'mongoid7x'\n          elsif mongoid8x?\n            'mongoid8x'\n          elsif mongoid9x?\n            'mongoid9x'\n          else\n            raise \"Bullet does not support mongoid #{::Mongoid::VERSION} yet\"\n          end\n        end\n    end\n\n    def active_record4?\n      active_record? && ::ActiveRecord::VERSION::MAJOR == 4\n    end\n\n    def active_record5?\n      active_record? && ::ActiveRecord::VERSION::MAJOR == 5\n    end\n\n    def active_record6?\n      active_record? && ::ActiveRecord::VERSION::MAJOR == 6\n    end\n\n    def active_record7?\n      active_record? && ::ActiveRecord::VERSION::MAJOR == 7\n    end\n\n    def active_record8?\n      active_record? && ::ActiveRecord::VERSION::MAJOR == 8\n    end\n\n    def active_record40?\n      active_record4? && ::ActiveRecord::VERSION::MINOR == 0\n    end\n\n    def active_record41?\n      active_record4? && ::ActiveRecord::VERSION::MINOR == 1\n    end\n\n    def active_record42?\n      active_record4? && ::ActiveRecord::VERSION::MINOR == 2\n    end\n\n    def active_record50?\n      active_record5? && ::ActiveRecord::VERSION::MINOR == 0\n    end\n\n    def active_record51?\n      active_record5? && ::ActiveRecord::VERSION::MINOR == 1\n    end\n\n    def active_record52?\n      active_record5? && ::ActiveRecord::VERSION::MINOR == 2\n    end\n\n    def active_record60?\n      active_record6? && ::ActiveRecord::VERSION::MINOR == 0\n    end\n\n    def active_record61?\n      active_record6? && ::ActiveRecord::VERSION::MINOR == 1\n    end\n\n    def active_record70?\n      active_record7? && ::ActiveRecord::VERSION::MINOR == 0\n    end\n\n    def active_record71?\n      active_record7? && ::ActiveRecord::VERSION::MINOR == 1\n    end\n\n    def active_record72?\n      active_record7? && ::ActiveRecord::VERSION::MINOR == 2\n    end\n\n    def active_record80?\n      active_record8? && ::ActiveRecord::VERSION::MINOR == 0\n    end\n\n    def active_record81?\n      active_record8? && ::ActiveRecord::VERSION::MINOR == 1\n    end\n\n    def mongoid4x?\n      mongoid? && ::Mongoid::VERSION =~ /\\A4/\n    end\n\n    def mongoid5x?\n      mongoid? && ::Mongoid::VERSION =~ /\\A5/\n    end\n\n    def mongoid6x?\n      mongoid? && ::Mongoid::VERSION =~ /\\A6/\n    end\n\n    def mongoid7x?\n      mongoid? && ::Mongoid::VERSION =~ /\\A7/\n    end\n\n    def mongoid8x?\n      mongoid? && ::Mongoid::VERSION =~ /\\A8/\n    end\n\n    def mongoid9x?\n      mongoid? && ::Mongoid::VERSION =~ /\\A9/\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/detector/association.rb",
    "content": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Detector\n    class Association < Base\n      class << self\n        def add_object_associations(object, associations)\n          return unless Bullet.start?\n          return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?\n          return unless object.bullet_primary_key_value\n\n          Bullet.debug(\n            'Detector::Association#add_object_associations',\n            \"object: #{object.bullet_key}, associations: #{associations}\"\n          )\n          call_stacks.add(object.bullet_key)\n          object_associations.add(object.bullet_key, associations)\n        end\n\n        def add_call_object_associations(object, associations)\n          return unless Bullet.start?\n          return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?\n          return unless object.bullet_primary_key_value\n\n          Bullet.debug(\n            'Detector::Association#add_call_object_associations',\n            \"object: #{object.bullet_key}, associations: #{associations}\"\n          )\n          call_stacks.add(object.bullet_key)\n          call_object_associations.add(object.bullet_key, associations)\n        end\n\n        # possible_objects keep the class to object relationships\n        # that the objects may cause N+1 query.\n        # e.g. { Post => [\"Post:1\", \"Post:2\"] }\n        def possible_objects\n          Thread.current.thread_variable_get(:bullet_possible_objects)\n        end\n\n        # impossible_objects keep the class to objects relationships\n        # that the objects may not cause N+1 query.\n        # e.g. { Post => [\"Post:1\", \"Post:2\"] }\n        # if find collection returns only one object, then the object is impossible object,\n        # impossible_objects are used to avoid treating 1+1 query to N+1 query.\n        def impossible_objects\n          Thread.current.thread_variable_get(:bullet_impossible_objects)\n        end\n\n        private\n\n        # object_associations keep the object relationships\n        # that the object has many associations.\n        # e.g. { \"Post:1\" => [:comments] }\n        # the object_associations keep all associations that may be or may no be\n        # unpreload associations or unused preload associations.\n        def object_associations\n          Thread.current.thread_variable_get(:bullet_object_associations)\n        end\n\n        # call_object_associations keep the object relationships\n        # that object.associations is called.\n        # e.g. { \"Post:1\" => [:comments] }\n        # they are used to detect unused preload associations.\n        def call_object_associations\n          Thread.current.thread_variable_get(:bullet_call_object_associations)\n        end\n\n        # inversed_objects keeps object relationships\n        # that association is inversed.\n        # e.g. { \"Comment:1\" => [\"post\"] }\n        def inversed_objects\n          Thread.current.thread_variable_get(:bullet_inversed_objects)\n        end\n\n        # eager_loadings keep the object relationships\n        # that the associations are preloaded by find :include.\n        # e.g. { [\"Post:1\", \"Post:2\"] => [:comments, :user] }\n        def eager_loadings\n          Thread.current.thread_variable_get(:bullet_eager_loadings)\n        end\n\n        # call_stacks keeps stacktraces where querie-objects were called from.\n        # e.g. { 'Object:111' => [SomeProject/app/controllers/...] }\n        def call_stacks\n          Thread.current.thread_variable_get(:bullet_call_stacks)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/detector/base.rb",
    "content": "# 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",
    "content": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Detector\n    class CounterCache < Base\n      class << self\n        def add_counter_cache(object, associations)\n          return unless Bullet.start?\n          return unless Bullet.counter_cache_enable?\n          return unless object.bullet_primary_key_value\n\n          Bullet.debug(\n            'Detector::CounterCache#add_counter_cache',\n            \"object: #{object.bullet_key}, associations: #{associations}\"\n          )\n          create_notification object.class.to_s, associations if conditions_met?(object, associations)\n        end\n\n        def add_possible_objects(object_or_objects)\n          return unless Bullet.start?\n          return unless Bullet.counter_cache_enable?\n\n          objects = Array.wrap(object_or_objects)\n          return if objects.map(&:bullet_primary_key_value).compact.empty?\n\n          Bullet.debug(\n            'Detector::CounterCache#add_possible_objects',\n            \"objects: #{objects.map(&:bullet_key).join(', ')}\"\n          )\n          objects.each { |object| possible_objects.add object.bullet_key }\n        end\n\n        def add_impossible_object(object)\n          return unless Bullet.start?\n          return unless Bullet.counter_cache_enable?\n          return unless object.bullet_primary_key_value\n\n          Bullet.debug('Detector::CounterCache#add_impossible_object', \"object: #{object.bullet_key}\")\n          impossible_objects.add object.bullet_key\n        end\n\n        def conditions_met?(object, _associations)\n          possible_objects.include?(object.bullet_key) && !impossible_objects.include?(object.bullet_key)\n        end\n\n        def possible_objects\n          Thread.current.thread_variable_get(:bullet_counter_possible_objects)\n        end\n\n        def impossible_objects\n          Thread.current.thread_variable_get(:bullet_counter_impossible_objects)\n        end\n\n        private\n\n        def create_notification(klazz, associations)\n          notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)\n\n          if notify_associations.present?\n            notice = Bullet::Notification::CounterCache.new klazz, notify_associations\n            Bullet.notification_collector.add notice\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/detector/n_plus_one_query.rb",
    "content": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Detector\n    class NPlusOneQuery < Association\n      extend Dependency\n      extend StackTraceFilter\n\n      class << self\n        # executed when object.associations is called.\n        # first, it keeps this method call for object.association.\n        # then, it checks if this associations call is unpreload.\n        #   if it is, keeps this unpreload associations and caller.\n        def call_association(object, associations, caller_stack = nil)\n          return unless Bullet.start?\n          return unless Bullet.n_plus_one_query_enable?\n          return unless object.bullet_primary_key_value\n          return if inversed_objects.include?(object.bullet_key, associations)\n\n          add_call_object_associations(object, associations)\n          call_stacks.add(object.bullet_key, caller_stack) if caller_stack\n\n          Bullet.debug(\n            'Detector::NPlusOneQuery#call_association',\n            \"object: #{object.bullet_key}, associations: #{associations}\"\n          )\n          if !excluded_stacktrace_path? && conditions_met?(object, associations)\n            Bullet.debug('detect n + 1 query', \"object: #{object.bullet_key}, associations: #{associations}\")\n            create_notification(caller_in_project(object.bullet_key), object.class.to_s, associations)\n          end\n        end\n\n        def add_possible_objects(object_or_objects)\n          return unless Bullet.start?\n          return unless Bullet.n_plus_one_query_enable?\n\n          objects = Array.wrap(object_or_objects)\n          class_names_match_regex = true\n          primary_key_values_are_empty = true\n\n          keys_joined = objects.map do |obj|\n            unless obj.class.name =~ /^HABTM_/\n              class_names_match_regex = false\n            end\n            unless obj.bullet_primary_key_value.nil?\n              primary_key_values_are_empty = false\n            end\n            obj.bullet_key\n          end.join(\", \")\n\n          unless class_names_match_regex || primary_key_values_are_empty\n            Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', \"objects: #{keys_joined}\")\n            objects.each { |object| possible_objects.add object.bullet_key }\n          end\n        end\n\n        def add_impossible_object(object)\n          return unless Bullet.start?\n          return unless Bullet.n_plus_one_query_enable?\n          return unless object.bullet_primary_key_value\n\n          Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', \"object: #{object.bullet_key}\")\n          impossible_objects.add object.bullet_key\n        end\n\n        def add_inversed_object(object, association)\n          return unless Bullet.start?\n          return unless Bullet.n_plus_one_query_enable?\n\n          object_key = object.bullet_primary_key_value ? object.bullet_key : object.object_id\n          Bullet.debug(\n            'Detector::NPlusOneQuery#add_inversed_object',\n            \"object: #{object_key}, association: #{association}\"\n          )\n          inversed_objects.add object_key, association\n        end\n\n        def update_inversed_object(object)\n          if inversed_objects&.key?(object.object_id)\n            Bullet.debug(\n              'Detector::NPlusOneQuery#update_inversed_object',\n              \"object from #{object.object_id} to #{object.bullet_key}\"\n            )\n            inversed_objects.add(object.bullet_key, inversed_objects[object.object_id].to_a)\n          end\n        end\n\n        # decide whether the object.associations is unpreloaded or not.\n        def conditions_met?(object, associations)\n          possible?(object) && !impossible?(object) && !association?(object, associations)\n        end\n\n        def possible?(object)\n          possible_objects.include? object.bullet_key\n        end\n\n        def impossible?(object)\n          impossible_objects.include? object.bullet_key\n        end\n\n        # check if object => associations already exists in object_associations.\n        def association?(object, associations)\n          value = object_associations[object.bullet_key]\n          value&.each do |v|\n            # associations == v comparison order is important here because\n            # v variable might be a squeel node where :== method is redefined,\n            # so it does not compare values at all and return unexpected results\n            result = v.is_a?(Hash) ? v.key?(associations) : associations == v\n            return true if result\n          end\n\n          false\n        end\n\n        private\n\n        def create_notification(callers, klazz, associations)\n          notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)\n\n          if notify_associations.present?\n            notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)\n            Bullet.notification_collector.add(notice)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/detector/unused_eager_loading.rb",
    "content": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\nusing Bullet::Ext::String\n\nmodule Bullet\n  module Detector\n    class UnusedEagerLoading < Association\n      extend Dependency\n      extend StackTraceFilter\n\n      class << self\n        # check if there are unused preload associations.\n        #   get related_objects from eager_loadings associated with object and associations\n        #   get call_object_association from associations of call_object_associations whose object is in related_objects\n        #   if association not in call_object_association, then the object => association - call_object_association is unused preload associations\n        def check_unused_preload_associations\n          return unless Bullet.start?\n          return unless Bullet.unused_eager_loading_enable?\n\n          object_associations.each do |bullet_key, associations|\n            object_association_diff = diff_object_associations bullet_key, associations\n            next if object_association_diff.empty?\n\n            Bullet.debug('detect unused preload', \"object: #{bullet_key}, associations: #{object_association_diff}\")\n            create_notification(caller_in_project(bullet_key), bullet_key.bullet_class_name, object_association_diff)\n          end\n        end\n\n        def add_eager_loadings(objects, associations)\n          return unless Bullet.start?\n          return unless Bullet.unused_eager_loading_enable?\n          return if objects.map(&:bullet_primary_key_value).compact.empty?\n\n          Bullet.debug(\n            'Detector::UnusedEagerLoading#add_eager_loadings',\n            \"objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}\"\n          )\n          bullet_keys = objects.map(&:bullet_key)\n\n          to_add = []\n          to_merge = []\n          to_delete = []\n          eager_loadings.each do |k, _v|\n            key_objects_overlap = k & bullet_keys\n\n            next if key_objects_overlap.empty?\n\n            bullet_keys -= k\n            if key_objects_overlap == k\n              to_add << [k, associations]\n            else\n              to_merge << [key_objects_overlap, (eager_loadings[k].dup << associations)]\n\n              keys_without_objects = k - key_objects_overlap\n              to_merge << [keys_without_objects, eager_loadings[k]]\n              to_delete << k\n            end\n          end\n\n          to_add.each { |k, val| eager_loadings.add k, val }\n          to_merge.each { |k, val| eager_loadings.merge k, val }\n          to_delete.each { |k| eager_loadings.delete k }\n\n          eager_loadings.add bullet_keys, associations unless bullet_keys.empty?\n        end\n\n        private\n\n        def create_notification(callers, klazz, associations)\n          notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(\n            :unused_eager_loading,\n            klazz\n          )\n\n          if notify_associations.present?\n            notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)\n            Bullet.notification_collector.add(notice)\n          end\n        end\n\n        def call_associations(bullet_key, associations)\n          all = Set.new\n          eager_loadings.similarly_associated(bullet_key, associations).each do |related_bullet_key|\n            coa = call_object_associations[related_bullet_key]\n            next if coa.nil?\n\n            all.merge coa\n          end\n          all.to_a\n        end\n\n        def diff_object_associations(bullet_key, associations)\n          potential_associations = associations - call_associations(bullet_key, associations)\n          potential_associations.reject { |a| a.is_a?(Hash) }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/detector.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Detector\n    autoload :Base, 'bullet/detector/base'\n    autoload :Association, 'bullet/detector/association'\n    autoload :NPlusOneQuery, 'bullet/detector/n_plus_one_query'\n    autoload :UnusedEagerLoading, 'bullet/detector/unused_eager_loading'\n    autoload :CounterCache, 'bullet/detector/counter_cache'\n  end\nend\n"
  },
  {
    "path": "lib/bullet/ext/object.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Ext\n    module Object\n      refine ::Object do\n        attr_writer :bullet_key, :bullet_primary_key_value\n\n        def bullet_key\n          return \"#{self.class}:\" if respond_to?(:persisted?) && !persisted?\n\n          @bullet_key ||= \"#{self.class}:#{bullet_primary_key_value}\"\n        end\n\n        def bullet_primary_key_value\n          return if respond_to?(:persisted?) && !persisted?\n\n          @bullet_primary_key_value ||=\n            begin\n              primary_key = self.class.try(:primary_keys) || self.class.try(:primary_key) || :id\n\n              bullet_join_potential_composite_primary_key(primary_key)\n            end\n        end\n\n        private\n\n        def bullet_join_potential_composite_primary_key(primary_keys)\n          return read_attribute(primary_keys) unless primary_keys.is_a?(Enumerable)\n\n          primary_keys.map { |primary_key| read_attribute primary_key }\n                      .compact.join(',')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/ext/string.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Ext\n    module String\n      refine ::String do\n        def bullet_class_name\n          last_colon = self.rindex(':')\n          last_colon ? self[0...last_colon].dup : self.dup\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/mongoid4x.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Mongoid\n    def self.enable\n      require 'mongoid'\n      ::Mongoid::Contextual::Mongo.class_eval do\n        alias_method :origin_first, :first\n        alias_method :origin_last, :last\n        alias_method :origin_each, :each\n        alias_method :origin_eager_load, :eager_load\n\n        def first\n          result = origin_first\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def last\n          result = origin_last\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def each(&block)\n          return to_enum unless block\n\n          records = []\n          origin_each { |record| records << record }\n          if records.length > 1\n            Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n          elsif records.size == 1\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n          end\n          records.each(&block)\n        end\n\n        def eager_load(docs)\n          associations = criteria.inclusions.map(&:name)\n          docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }\n          Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)\n          origin_eager_load(docs)\n        end\n      end\n\n      ::Mongoid::Relations::Accessors.class_eval do\n        alias_method :origin_get_relation, :get_relation\n\n        private\n\n        def get_relation(name, metadata, object, reload = false)\n          result = origin_get_relation(name, metadata, object, reload)\n          Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/mongoid5x.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Mongoid\n    def self.enable\n      require 'mongoid'\n      ::Mongoid::Contextual::Mongo.class_eval do\n        alias_method :origin_first, :first\n        alias_method :origin_last, :last\n        alias_method :origin_each, :each\n        alias_method :origin_eager_load, :eager_load\n\n        def first\n          result = origin_first\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def last\n          result = origin_last\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def each(&block)\n          return to_enum unless block\n\n          records = []\n          origin_each { |record| records << record }\n          if records.length > 1\n            Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n          elsif records.size == 1\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n          end\n          records.each(&block)\n        end\n\n        def eager_load(docs)\n          associations = criteria.inclusions.map(&:name)\n          docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }\n          Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)\n          origin_eager_load(docs)\n        end\n      end\n\n      ::Mongoid::Relations::Accessors.class_eval do\n        alias_method :origin_get_relation, :get_relation\n\n        private\n\n        def get_relation(name, metadata, object, reload = false)\n          result = origin_get_relation(name, metadata, object, reload)\n          Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/mongoid6x.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Mongoid\n    def self.enable\n      require 'mongoid'\n      ::Mongoid::Contextual::Mongo.class_eval do\n        alias_method :origin_first, :first\n        alias_method :origin_last, :last\n        alias_method :origin_each, :each\n        alias_method :origin_eager_load, :eager_load\n\n        def first(opt = {})\n          result = origin_first(opt)\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def last(opt = {})\n          result = origin_last(opt)\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def each(&block)\n          return to_enum unless block\n\n          records = []\n          origin_each { |record| records << record }\n          if records.length > 1\n            Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n          elsif records.size == 1\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n          end\n          records.each(&block)\n        end\n\n        def eager_load(docs)\n          associations = criteria.inclusions.map(&:name)\n          docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }\n          Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)\n          origin_eager_load(docs)\n        end\n      end\n\n      ::Mongoid::Relations::Accessors.class_eval do\n        alias_method :origin_get_relation, :get_relation\n\n        private\n\n        def get_relation(name, metadata, object, reload = false)\n          result = origin_get_relation(name, metadata, object, reload)\n          Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/mongoid7x.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Mongoid\n    def self.enable\n      require 'mongoid'\n      require 'rubygems'\n      ::Mongoid::Contextual::Mongo.class_eval do\n        alias_method :origin_first, :first\n        alias_method :origin_last, :last\n        alias_method :origin_each, :each\n        alias_method :origin_eager_load, :eager_load\n\n        %i[first last].each do |context|\n          default = Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('7.5') ? nil : {}\n          define_method(context) do |opts = default|\n            result = send(:\"origin_#{context}\", opts)\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n            result\n          end\n        end\n\n        def each(&block)\n          return to_enum unless block_given?\n\n          first_document = nil\n          document_count = 0\n\n          origin_each do |document|\n            document_count += 1\n\n            if document_count == 1\n              first_document = document\n            elsif document_count == 2\n              Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])\n              yield(first_document)\n              first_document = nil\n              yield(document)\n            else\n              Bullet::Detector::NPlusOneQuery.add_possible_objects(document)\n              yield(document)\n            end\n          end\n\n          if document_count == 1\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)\n            yield(first_document)\n          end\n\n          self\n        end\n\n        def eager_load(docs)\n          associations = criteria.inclusions.map(&:name)\n          docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }\n          Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)\n          origin_eager_load(docs)\n        end\n      end\n\n      ::Mongoid::Association::Accessors.class_eval do\n        alias_method :origin_get_relation, :get_relation\n\n        private\n\n        def get_relation(name, association, object, reload = false)\n          result = origin_get_relation(name, association, object, reload)\n          Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/mongoid8x.rb",
    "content": "module Bullet\n  module Mongoid\n    def self.enable\n      require 'mongoid'\n      ::Mongoid::Contextual::Mongo.class_eval do\n        alias_method :origin_first, :first\n        alias_method :origin_last, :last\n        alias_method :origin_each, :each\n        alias_method :origin_eager_load, :eager_load\n\n        def first(limit = nil)\n          result = origin_first(limit)\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def last(limit = nil)\n          result = origin_last(limit)\n          Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n          result\n        end\n\n        def each(&block)\n          return to_enum unless block_given?\n\n          records = []\n          origin_each { |record| records << record }\n          if records.length > 1\n            Bullet::Detector::NPlusOneQuery.add_possible_objects(records)\n          elsif records.size == 1\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)\n          end\n          records.each(&block)\n        end\n\n        def eager_load(docs)\n          associations = criteria.inclusions.map(&:name)\n          docs.each do |doc|\n            Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)\n          end\n          Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)\n          origin_eager_load(docs)\n        end\n      end\n\n      ::Mongoid::Association::Accessors.class_eval do\n        alias_method :origin_get_relation, :get_relation\n\n        private\n\n        def get_relation(name, association, object, reload = false)\n          result = origin_get_relation(name, association, object, reload)\n          unless association.embedded?\n            Bullet::Detector::NPlusOneQuery.call_association(self, name)\n          end\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/mongoid9x.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Mongoid\n    def self.enable\n      require 'mongoid'\n      require 'rubygems'\n      ::Mongoid::Contextual::Mongo.class_eval do\n        alias_method :origin_first, :first\n        alias_method :origin_last, :last\n        alias_method :origin_each, :each\n        alias_method :origin_eager_load, :eager_load\n\n        %i[first last].each do |context|\n          default = Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('7.5') ? nil : {}\n          define_method(context) do |opts = default|\n            result = send(:\"origin_#{context}\", opts)\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result\n            result\n          end\n        end\n\n        def each(&_block)\n          return to_enum unless block_given?\n\n          first_document = nil\n          document_count = 0\n\n          origin_each do |document|\n            document_count += 1\n\n            if document_count == 1\n              first_document = document\n            elsif document_count == 2\n              Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])\n              yield(first_document)\n              first_document = nil\n              yield(document)\n            else\n              Bullet::Detector::NPlusOneQuery.add_possible_objects(document)\n              yield(document)\n            end\n          end\n\n          if document_count == 1\n            Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)\n            yield(first_document)\n          end\n\n          self\n        end\n\n        def eager_load(docs)\n          associations = criteria.inclusions.map(&:name)\n          docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }\n          Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)\n          origin_eager_load(docs)\n        end\n      end\n\n      ::Mongoid::Association::Accessors.class_eval do\n        alias_method :origin_get_relation, :get_relation\n\n        private\n\n        def get_relation(name, association, object, reload = false)\n          result = origin_get_relation(name, association, object, reload)\n          Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?\n          result\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/notification/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Notification\n    class Base\n      attr_accessor :notifier, :url\n      attr_reader :base_class, :associations, :path\n\n      def initialize(base_class, association_or_associations, path = nil)\n        @base_class = base_class\n        @associations =\n          association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]\n        @path = path\n      end\n\n      def title\n        raise NoMethodError, 'no method title defined'\n      end\n\n      def body\n        raise NoMethodError, 'no method body defined'\n      end\n\n      def call_stack_messages\n        ''\n      end\n\n      def whoami\n        @user ||=\n          ENV['USER'].presence ||\n          (\n            begin\n              `whoami`.chomp\n            rescue StandardError\n              ''\n            end\n          )\n        @user.present? ? \"user: #{@user}\" : ''\n      end\n\n      def body_with_caller\n        \"#{body}\\n#{call_stack_messages}\\n\"\n      end\n\n      def notify_inline\n        notifier.inline_notify(notification_data)\n      end\n\n      def notify_out_of_channel\n        notifier.out_of_channel_notify(notification_data)\n      end\n\n      def short_notice\n        parts = []\n        parts << whoami.presence unless Bullet.skip_user_in_notification\n        parts << url\n        parts << title\n        parts << body\n\n        parts.compact.join('  ')\n      end\n\n      def notification_data\n        hash = {}\n        hash[:user] = whoami unless Bullet.skip_user_in_notification\n        hash[:url] = url\n        hash[:title] = title\n        hash[:body] = body_with_caller\n        hash\n      end\n\n      def eql?(other)\n        self.class == other.class && klazz_associations_str == other.klazz_associations_str\n      end\n\n      def hash\n        [self.class, klazz_associations_str].hash\n      end\n\n      protected\n\n      def klazz_associations_str\n        \"  #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]\"\n      end\n\n      def associations_str\n        \".includes(#{@associations.map { |a| a.to_s.to_sym }\n.inspect})\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/notification/counter_cache.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Notification\n    class CounterCache < Base\n      def body\n        klazz_associations_str\n      end\n\n      def title\n        'Need Counter Cache with Active Record size'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/notification/n_plus_one_query.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Notification\n    class NPlusOneQuery < Base\n      def initialize(callers, base_class, associations, path = nil)\n        super(base_class, associations, path)\n\n        @callers = callers\n      end\n\n      def body\n        \"#{klazz_associations_str}\\n  Add to your query: #{associations_str}\"\n      end\n\n      def title\n        \"USE eager loading #{@path ? \"in #{@path}\" : 'detected'}\"\n      end\n\n      def notification_data\n        super.merge(backtrace: [])\n      end\n\n      protected\n\n      def call_stack_messages\n        (['Call stack'] + @callers).join(\"\\n  \")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/notification/unused_eager_loading.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Notification\n    class UnusedEagerLoading < Base\n      def initialize(callers, base_class, associations, path = nil)\n        super(base_class, associations, path)\n\n        @callers = callers\n      end\n\n      def body\n        \"#{klazz_associations_str}\\n  Remove from your query: #{associations_str}\"\n      end\n\n      def title\n        \"AVOID eager loading #{@path ? \"in #{@path}\" : 'detected'}\"\n      end\n\n      def notification_data\n        super.merge(backtrace: [])\n      end\n\n      protected\n\n      def call_stack_messages\n        (['Call stack'] + @callers).join(\"\\n  \")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/notification.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Notification\n    autoload :Base, 'bullet/notification/base'\n    autoload :UnusedEagerLoading, 'bullet/notification/unused_eager_loading'\n    autoload :NPlusOneQuery, 'bullet/notification/n_plus_one_query'\n    autoload :CounterCache, 'bullet/notification/counter_cache'\n\n    class UnoptimizedQueryError < StandardError\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/notification_collector.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'set'\n\nmodule Bullet\n  class NotificationCollector\n    attr_reader :collection\n\n    def initialize\n      reset\n    end\n\n    def reset\n      @collection = Set.new\n    end\n\n    def add(value)\n      @collection << value\n    end\n\n    def notifications_present?\n      !@collection.empty?\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/rack.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'rack/request'\nrequire 'json'\nrequire 'cgi'\n\nmodule Bullet\n  class Rack\n    include Dependency\n\n    NONCE_MATCHER = /(script|style)-src .*'nonce-(?<nonce>[A-Za-z0-9+\\/]+={0,2})'/\n\n    def initialize(app)\n      @app = app\n    end\n\n    def call(env)\n      return @app.call(env) unless Bullet.enable?\n\n      Bullet.start_request\n      status, headers, response = @app.call(env)\n\n      response_body = nil\n\n      if Bullet.notification? || Bullet.always_append_html_body\n        request = ::Rack::Request.new(env)\n        if Bullet.inject_into_page? && !skip_html_injection?(request) && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200\n          if html_request?(headers, response)\n            response_body = response_body(response)\n\n            with_security_policy_nonce(headers) do |nonce|\n              response_body = append_to_html_body(response_body, footer_note(nonce)) if Bullet.add_footer\n              response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)\n              if Bullet.add_footer && !Bullet.skip_http_headers\n                response_body = append_to_html_body(response_body, xhr_script(nonce))\n              end\n            end\n\n            headers['Content-Length'] = response_body.bytesize.to_s\n          elsif !Bullet.skip_http_headers\n            set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer\n            set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?\n          end\n        end\n        Bullet.perform_out_of_channel_notifications(env)\n      end\n      [status, headers, response_body ? [response_body] : response]\n    ensure\n      Bullet.end_request\n    end\n\n    # fix issue if response's body is a Proc\n    def empty?(response)\n      # response may be [\"Not Found\"], [\"Move Permanently\"], etc, but\n      # those should not happen if the status is 200\n      return true if !response.respond_to?(:body) && !response.respond_to?(:first)\n\n      body = response_body(response)\n      body.nil? || body.empty?\n    end\n\n    def append_to_html_body(response_body, content)\n      body = response_body.dup\n      content = content.html_safe if content.respond_to?(:html_safe)\n      if body.include?('</body>')\n        position = body.rindex('</body>')\n        body.insert(position, content)\n      else\n        body << content\n      end\n    end\n\n    def footer_note(nonce = nil)\n      %(<details id=\"bullet-footer\" data-is-bullet-footer><summary>Bullet Warnings</summary><div>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message(nonce)}</div>#{footer_style(nonce)}</details>)\n    end\n\n    # Make footer styles work with ContentSecurityPolicy style-src as self\n    def footer_style(nonce = nil)\n      css = <<~CSS\n        details#bullet-footer {cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;}\n        details#bullet-footer summary {font-weight: 600; padding: 2px 8px;}\n        details#bullet-footer div {padding: 8px; border-top: 1px solid #9b1c1c;}\n      CSS\n      if nonce\n        %(<style type=\"text/css\" nonce=\"#{nonce}\">#{css}</style>)\n      else\n        %(<style type=\"text/css\">#{css}</style>)\n      end\n    end\n\n    def set_header(headers, header_name, header_array)\n      # Many proxy applications such as Nginx and AWS ELB limit\n      # the size a header to 8KB, so truncate the list of reports to\n      # be under that limit\n      header_array.pop while JSON.generate(header_array).length > 8 * 1024\n      headers[header_name] = JSON.generate(header_array)\n    end\n\n    def skip_html_injection?(request)\n      query_string = request.env['QUERY_STRING']\n      return false if query_string.nil? || query_string.empty?\n\n      params = simple_parse_query_string(query_string)\n      params['skip_html_injection'] == 'true'\n    end\n\n    # Simple query string parser\n    def simple_parse_query_string(query_string)\n      params = {}\n      query_string.split('&').each do |pair|\n        key, value = pair.split('=', 2).map { |s| CGI.unescape(s) }\n        params[key] = value if key && !key.empty?\n      end\n      params\n    end\n\n    def file?(headers)\n      headers['Content-Transfer-Encoding'] == 'binary' || headers['Content-Disposition']\n    end\n\n    def sse?(headers)\n      headers['Content-Type'] == 'text/event-stream'\n    end\n\n    def html_request?(headers, response)\n      headers['Content-Type']&.include?('text/html')\n    end\n\n    def response_body(response)\n      if response.respond_to?(:body)\n        Array === response.body ? response.body.first : response.body\n      elsif response.respond_to?(:first)\n        response.first\n      end\n    end\n\n    private\n\n    def footer_console_message(nonce = nil)\n      if Bullet.console_enabled?\n        footer = %(<br/><span id=\"console-message\">See 'Uniform Notifier' in JS Console for Stacktrace</span>)\n        css = \"details#bullet-footer #console-message {font-style: italic;}\"\n        style =\n          if nonce\n            %(<style type=\"text/css\" nonce=\"#{nonce}\">#{css}</style>)\n          else\n            %(<style type=\"text/css\">#{css}</style>)\n          end\n\n        footer + style\n      end\n    end\n\n    # Make footer work for XHR requests by appending data to the footer\n    def xhr_script(nonce = nil)\n      script = File.read(\"#{__dir__}/bullet_xhr.js\")\n\n      if nonce\n        \"<script type='text/javascript' nonce='#{nonce}'>#{script}</script>\"\n      else\n        \"<script type='text/javascript'>#{script}</script>\"\n      end\n    end\n\n    def with_security_policy_nonce(headers)\n      csp = headers['Content-Security-Policy'] || headers['Content-Security-Policy-Report-Only'] || ''\n      matched = csp.match(NONCE_MATCHER)\n      nonce = matched[:nonce] if matched\n\n      if nonce\n        console_enabled = UniformNotifier.console\n        alert_enabled = UniformNotifier.alert\n\n        UniformNotifier.console = { attributes: { nonce: nonce } } if console_enabled\n        UniformNotifier.alert = { attributes: { nonce: nonce } } if alert_enabled\n\n        yield nonce\n\n        UniformNotifier.console = console_enabled\n        UniformNotifier.alert = alert_enabled\n      else\n        yield\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/registry/association.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Registry\n    class Association < Base\n      def merge(base, associations)\n        @registry.merge!(base => associations)\n      end\n\n      def similarly_associated(base, associations)\n        @registry.select { |key, value| key.include?(base) && value == associations }\n                 .collect(&:first).flatten\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/registry/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Registry\n    class Base\n      attr_reader :registry\n\n      def initialize\n        @registry = {}\n      end\n\n      def [](key)\n        @registry[key]\n      end\n\n      def each(&block)\n        @registry.each(&block)\n      end\n\n      def delete(base)\n        @registry.delete(base)\n      end\n\n      def select(*args, &block)\n        @registry.select(*args, &block)\n      end\n\n      def add(key, value)\n        @registry[key] ||= Set.new\n        if value.is_a? Array\n          @registry[key] += value\n        else\n          @registry[key] << value\n        end\n      end\n\n      def include?(key, value)\n        key?(key) && @registry[key].include?(value)\n      end\n\n      def key?(key)\n        @registry.key?(key)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/registry/call_stack.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Registry\n    class CallStack < Base\n      # remembers found association backtrace\n      # if backtrace is provided, it will be used and override any existing value\n      def add(key, backtrace = nil)\n        if backtrace\n          @registry[key] = backtrace\n        else\n          @registry[key] ||= Thread.current.backtrace\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/registry/object.rb",
    "content": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\nusing Bullet::Ext::String\n\nmodule Bullet\n  module Registry\n    class Object < Base\n      def add(bullet_key)\n        super(bullet_key.bullet_class_name, bullet_key)\n      end\n\n      def include?(bullet_key)\n        super(bullet_key.bullet_class_name, bullet_key)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/registry.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Registry\n    autoload :Base, 'bullet/registry/base'\n    autoload :Object, 'bullet/registry/object'\n    autoload :Association, 'bullet/registry/association'\n    autoload :CallStack, 'bullet/registry/call_stack'\n  end\nend\n"
  },
  {
    "path": "lib/bullet/stack_trace_filter.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"bundler\"\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module StackTraceFilter\n    VENDOR_PATH = '/vendor'\n\n    # @param bullet_key[String] - use this to get stored call stack from call_stacks object.\n    def caller_in_project(bullet_key = nil)\n      vendor_root = Bullet.app_root + VENDOR_PATH\n      bundler_path = Bundler.bundle_path.to_s\n      select_caller_locations(bullet_key) do |location|\n        caller_path = location_as_path(location)\n        caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&\n          !caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|\n                                                   pattern_matches?(location, include_pattern)\n                                                 }\n      end\n    end\n\n    def excluded_stacktrace_path?\n      Bullet.stacktrace_excludes.any? do |exclude_pattern|\n        caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }\n      end\n    end\n\n    private\n\n    def pattern_matches?(location, pattern)\n      path = location_as_path(location)\n      case pattern\n      when Array\n        pattern_path = pattern.first\n        filter = pattern.last\n        return false unless pattern_matches?(location, pattern_path)\n\n        case filter\n        when Range\n          filter.include?(location.lineno)\n        when Integer\n          filter == location.lineno\n        when String\n          filter == location.base_label\n        end\n      when String\n        path.include?(pattern)\n      when Regexp\n        path =~ pattern\n      end\n    end\n\n    def location_as_path(location)\n      return location if location.is_a?(String)\n\n      location.absolute_path.to_s\n    end\n\n    def select_caller_locations(bullet_key = nil)\n      call_stack = bullet_key ? call_stacks[bullet_key] : caller_locations\n\n      call_stack.select { |location| yield location }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/bullet/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  VERSION = '8.1.0'\nend\n"
  },
  {
    "path": "lib/bullet.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'active_support/core_ext/string/inflections'\nrequire 'active_support/core_ext/module/delegation'\nrequire 'set'\nrequire 'uniform_notifier'\nrequire 'bullet/ext/object'\nrequire 'bullet/ext/string'\nrequire 'bullet/dependency'\nrequire 'bullet/stack_trace_filter'\n\nmodule Bullet\n  extend Dependency\n\n  autoload :ActiveRecord, \"bullet/#{active_record_version}\" if active_record?\n  autoload :Mongoid, \"bullet/#{mongoid_version}\" if mongoid?\n  autoload :Rack, 'bullet/rack'\n  autoload :ActiveJob, 'bullet/active_job'\n  autoload :Notification, 'bullet/notification'\n  autoload :Detector, 'bullet/detector'\n  autoload :Registry, 'bullet/registry'\n  autoload :NotificationCollector, 'bullet/notification_collector'\n\n  if defined?(Rails::Railtie)\n    class BulletRailtie < Rails::Railtie\n      initializer 'bullet.add_middleware', after: :load_config_initializers do |app|\n        if defined?(ActionDispatch::ContentSecurityPolicy::Middleware) && Rails.application.config.content_security_policy && !app.config.api_only\n          app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, Bullet::Rack\n        else\n          app.middleware.use Bullet::Rack\n        end\n      end\n    end\n  end\n\n  class << self\n    attr_writer :n_plus_one_query_enable,\n                :unused_eager_loading_enable,\n                :counter_cache_enable,\n                :stacktrace_includes,\n                :stacktrace_excludes,\n                :skip_html_injection\n    attr_reader :safelist\n    attr_accessor :add_footer,\n                  :orm_patches_applied,\n                  :skip_http_headers,\n                  :always_append_html_body,\n                  :skip_user_in_notification\n\n    available_notifiers =\n      UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }\n                                          .map { |notifier| \"#{notifier}=\" }\n    available_notifiers_options = { to: UniformNotifier }\n    delegate(*available_notifiers, **available_notifiers_options)\n\n    def raise=(should_raise)\n      UniformNotifier.raise = (should_raise ? Notification::UnoptimizedQueryError : false)\n    end\n\n    DETECTORS = [\n      Bullet::Detector::NPlusOneQuery,\n      Bullet::Detector::UnusedEagerLoading,\n      Bullet::Detector::CounterCache\n    ].freeze\n\n    def enable=(enable)\n      @enable = enable\n\n      if enable?\n        reset_safelist\n        unless orm_patches_applied\n          self.orm_patches_applied = true\n          Bullet::Mongoid.enable if mongoid?\n          Bullet::ActiveRecord.enable if active_record?\n        end\n      end\n    end\n\n    alias enabled= enable=\n\n    def enable?\n      !!@enable\n    end\n\n    alias enabled? enable?\n\n    # Rails.root might be nil if `railties` is a dependency on a project that does not use Rails\n    def app_root\n      @app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s\n    end\n\n    def n_plus_one_query_enable?\n      enable? && (@n_plus_one_query_enable.nil? ? true : @n_plus_one_query_enable)\n    end\n\n    def unused_eager_loading_enable?\n      enable? && (@unused_eager_loading_enable.nil? ? true : @unused_eager_loading_enable)\n    end\n\n    def counter_cache_enable?\n      enable? && (@counter_cache_enable.nil? ? true : @counter_cache_enable)\n    end\n\n    def stacktrace_includes\n      @stacktrace_includes ||= []\n    end\n\n    def stacktrace_excludes\n      @stacktrace_excludes ||= []\n    end\n\n    def add_safelist(options)\n      reset_safelist\n      @safelist[options[:type]][options[:class_name]] ||= []\n      @safelist[options[:type]][options[:class_name]] << options[:association].to_sym\n    end\n\n    def delete_safelist(options)\n      reset_safelist\n      @safelist[options[:type]][options[:class_name]] ||= []\n      @safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)\n      @safelist[options[:type]].delete_if { |_key, val| val.empty? }\n    end\n\n    def get_safelist_associations(type, class_name)\n      Array.wrap(@safelist[type][class_name]).flat_map { |a| [a, a.to_s] }\n    end\n\n    def reset_safelist\n      @safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }\n    end\n\n    def clear_safelist\n      @safelist = nil\n    end\n\n    def bullet_logger=(active)\n      if active\n        require 'fileutils'\n        FileUtils.mkdir_p(app_root + '/log')\n        bullet_log_file = File.open(\"#{app_root}/log/bullet.log\", 'a+')\n        bullet_log_file.sync = true\n        UniformNotifier.customized_logger = bullet_log_file\n      end\n    end\n\n    def debug(title, message)\n      puts \"[Bullet][#{title}] #{message}\" if ENV['BULLET_DEBUG'] == 'true'\n    end\n\n    def start_request\n      Thread.current.thread_variable_set(:bullet_start, true)\n      Thread.current.thread_variable_set(:bullet_notification_collector, Bullet::NotificationCollector.new)\n\n      Thread.current.thread_variable_set(:bullet_object_associations, Bullet::Registry::Base.new)\n      Thread.current.thread_variable_set(:bullet_call_object_associations, Bullet::Registry::Base.new)\n      Thread.current.thread_variable_set(:bullet_possible_objects, Bullet::Registry::Object.new)\n      Thread.current.thread_variable_set(:bullet_impossible_objects, Bullet::Registry::Object.new)\n      Thread.current.thread_variable_set(:bullet_inversed_objects, Bullet::Registry::Base.new)\n      Thread.current.thread_variable_set(:bullet_eager_loadings, Bullet::Registry::Association.new)\n      Thread.current.thread_variable_set(:bullet_call_stacks, Bullet::Registry::CallStack.new)\n\n      unless Thread.current.thread_variable_get(:bullet_counter_possible_objects)\n        Thread.current.thread_variable_set(:bullet_counter_possible_objects, Bullet::Registry::Object.new)\n      end\n\n      unless Thread.current.thread_variable_get(:bullet_counter_impossible_objects)\n        Thread.current.thread_variable_set(:bullet_counter_impossible_objects, Bullet::Registry::Object.new)\n      end\n    end\n\n    def end_request\n      Thread.current.thread_variable_set(:bullet_start, nil)\n      Thread.current.thread_variable_set(:bullet_notification_collector, nil)\n\n      Thread.current.thread_variable_set(:bullet_object_associations, nil)\n      Thread.current.thread_variable_set(:bullet_call_object_associations, nil)\n      Thread.current.thread_variable_set(:bullet_possible_objects, nil)\n      Thread.current.thread_variable_set(:bullet_impossible_objects, nil)\n      Thread.current.thread_variable_set(:bullet_inversed_objects, nil)\n      Thread.current.thread_variable_set(:bullet_eager_loadings, nil)\n\n      Thread.current.thread_variable_set(:bullet_counter_possible_objects, nil)\n      Thread.current.thread_variable_set(:bullet_counter_impossible_objects, nil)\n    end\n\n    def start?\n      enable? && Thread.current.thread_variable_get(:bullet_start)\n    end\n\n    def notification_collector\n      Thread.current.thread_variable_get(:bullet_notification_collector)\n    end\n\n    def notification?\n      return unless start?\n\n      Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n      notification_collector.notifications_present?\n    end\n\n    def gather_inline_notifications\n      responses = []\n      for_each_active_notifier_with_notification { |notification| responses << notification.notify_inline }\n      responses.join(\"\\n\")\n    end\n\n    def perform_out_of_channel_notifications(env = {})\n      request_uri = build_request_uri(env)\n      for_each_active_notifier_with_notification do |notification|\n        notification.url = request_uri\n        notification.notify_out_of_channel\n      end\n    end\n\n    def footer_info\n      info = []\n      notification_collector.collection.each { |notification| info << notification.short_notice }\n      info\n    end\n\n    def text_notifications\n      info = []\n      notification_collector.collection.each do |notification|\n        info << notification.notification_data.values.compact.join(\"\\n\")\n      end\n      info\n    end\n\n    def warnings\n      notification_collector.collection.each_with_object({}) do |notification, warnings|\n        warning_type = notification.class.to_s.split(':').last.tableize\n        warnings[warning_type] ||= []\n        warnings[warning_type] << notification\n      end\n    end\n\n    def profile\n      return_value = nil\n\n      if Bullet.enable?\n        begin\n          Bullet.start_request\n\n          return_value = yield\n\n          Bullet.perform_out_of_channel_notifications if Bullet.notification?\n        ensure\n          Bullet.end_request\n        end\n      else\n        return_value = yield\n      end\n\n      return_value\n    end\n\n    def console_enabled?\n      UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)\n    end\n\n    def inject_into_page?\n      return false if defined?(@skip_html_injection) && @skip_html_injection\n\n      console_enabled? || add_footer\n    end\n\n    private\n\n    def for_each_active_notifier_with_notification\n      UniformNotifier.active_notifiers.each do |notifier|\n        notification_collector.collection.each do |notification|\n          notification.notifier = notifier\n          yield notification\n        end\n      end\n    end\n\n    def build_request_uri(env)\n      return \"#{env['REQUEST_METHOD']} #{env['REQUEST_URI']}\" if env['REQUEST_URI']\n\n      if env['QUERY_STRING'].present?\n        \"#{env['REQUEST_METHOD']} #{env['PATH_INFO']}?#{env['QUERY_STRING']}\"\n      else\n        \"#{env['REQUEST_METHOD']} #{env['PATH_INFO']}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/generators/bullet/install_generator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Bullet\n  module Generators\n    class InstallGenerator < ::Rails::Generators::Base\n      desc <<~DESC\n        Description:\n            Enable bullet in development/test for your application.\n      DESC\n\n      def enable_in_development\n        environment(nil, env: 'development') do\n          <<~FILE\n            config.after_initialize do\n              Bullet.enable        = true\n              Bullet.alert         = true\n              Bullet.bullet_logger = true\n              Bullet.console       = true\n              Bullet.rails_logger  = true\n              Bullet.add_footer    = true\n            end\n\n          FILE\n        end\n\n        say 'Enabled bullet in config/environments/development.rb'\n      end\n\n      def enable_in_test\n        return unless yes?('Would you like to enable bullet in test environment? (y/n)')\n\n        environment(nil, env: 'test') do\n          <<~FILE\n            config.after_initialize do\n              Bullet.enable        = true\n              Bullet.bullet_logger = true\n              Bullet.raise         = true # raise an error if n+1 query occurs\n            end\n\n          FILE\n        end\n\n        say 'Enabled bullet in config/environments/test.rb'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "perf/benchmark.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH << 'lib'\nrequire 'benchmark'\nrequire 'rails'\nrequire 'active_record'\nrequire 'activerecord-import'\nrequire 'bullet'\n\nbegin\n  require 'perftools'\nrescue LoadError\n  puts \"Could not load perftools.rb, profiling won't be possible\"\nend\n\nclass Post < ActiveRecord::Base\n  belongs_to :user\n  has_many :comments\nend\n\nclass Comment < ActiveRecord::Base\n  belongs_to :user\n  belongs_to :post\nend\n\nclass User < ActiveRecord::Base\n  has_many :posts\n  has_many :comments\nend\n\n# create database bullet_benchmark;\nActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'bullet_benchmark')\n\nActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }\n\nActiveRecord::Schema.define(version: 1) do\n  create_table :posts do |t|\n    t.column :title, :string\n    t.column :body, :string\n    t.column :user_id, :integer\n  end\n\n  create_table :comments do |t|\n    t.column :body, :string\n    t.column :post_id, :integer\n    t.column :user_id, :integer\n  end\n\n  create_table :users do |t|\n    t.column :name, :string\n  end\nend\n\nusers_size = 100\nposts_size = 1_000\ncomments_size = 10_000\nusers = []\nusers_size.times { |i| users << User.new(name: \"user#{i}\") }\nUser.import users\nusers = User.all\n\nposts = []\nposts_size.times { |i| posts << Post.new(title: \"Title #{i}\", body: \"Body #{i}\", user: users[i % 100]) }\nPost.import posts\nposts = Post.all\n\ncomments = []\ncomments_size.times { |i| comments << Comment.new(body: \"Comment #{i}\", post: posts[i % 1_000], user: users[i % 100]) }\nComment.import comments\n\nputs 'Start benchmarking...'\n\nBullet.enable = true\n\nBenchmark.bm(70) do |bm|\n  bm.report(\"Querying & Iterating #{posts_size} Posts with #{comments_size} Comments and #{users_size} Users\") do\n    10.times do\n      Bullet.start_request\n      Post.includes(:user, comments: :user).each do |p|\n        p.title\n        p.user.name\n        p.comments.each do |c|\n          c.body\n          c.user.name\n        end\n      end\n      Bullet.end_request\n    end\n  end\nend\n\nputs 'End benchmarking...'\n\n# Run benchmark with bundler\n#\n#     bundle exec ruby perf/benchmark.rb\n#\n# bullet 2.3.0 with rails 3.2.2\n#                                                                              user     system      total        real\n# Querying & Iterating 1000 Posts with 10000 Comments and 100 Users       16.460000   0.190000  16.650000 ( 16.968246)\n#\n# bullet 2.3.0 with rails 3.1.4\n#                                                                              user     system      total        real\n# Querying & Iterating 1000 Posts with 10000 Comments and 100 Users       14.600000   0.130000  14.730000 ( 14.937590)\n#\n# bullet 2.3.0 with rails 3.0.12\n#                                                                              user     system      total        real\n# Querying & Iterating 1000 Posts with 10000 Comments and 100 Users       26.120000   0.430000  26.550000 ( 27.179304)\n#\n#\n# bullet 2.2.1 with rails 3.0.12\n#                                                                              user     system      total        real\n# Querying & Iterating 1000 Posts with 10000 Comments and 100 Users       29.970000   0.270000  30.240000 ( 30.452083)\n"
  },
  {
    "path": "rails/init.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'bullet'\n"
  },
  {
    "path": "spec/bullet/detector/association_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Detector\n    describe Association do\n      before :all do\n        @post1 = Post.first\n        @post2 = Post.last\n      end\n\n      context '.add_object_association' do\n        it 'should add object, associations pair' do\n          Association.add_object_associations(@post1, :associations)\n          expect(Association.send(:object_associations)).to be_include(@post1.bullet_key, :associations)\n        end\n      end\n\n      context '.add_call_object_associations' do\n        it 'should add call object, associations pair' do\n          Association.add_call_object_associations(@post1, :associations)\n          expect(Association.send(:call_object_associations)).to be_include(@post1.bullet_key, :associations)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/detector/base_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  module Detector\n    describe Base do\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/detector/counter_cache_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Detector\n    describe CounterCache do\n      before :all do\n        @post1 = Post.first\n        @post2 = Post.last\n      end\n\n      context '.add_counter_cache' do\n        it 'should create notification if conditions met' do\n          expect(CounterCache).to receive(:conditions_met?).with(@post1, %i[comments]).and_return(true)\n          expect(CounterCache).to receive(:create_notification).with('Post', %i[comments])\n          CounterCache.add_counter_cache(@post1, %i[comments])\n        end\n\n        it 'should not create notification if conditions not met' do\n          expect(CounterCache).to receive(:conditions_met?).with(@post1, %i[comments]).and_return(false)\n          expect(CounterCache).to receive(:create_notification).never\n          CounterCache.add_counter_cache(@post1, %i[comments])\n        end\n      end\n\n      context '.add_possible_objects' do\n        it 'should add possible objects' do\n          CounterCache.add_possible_objects([@post1, @post2])\n          expect(CounterCache.possible_objects).to be_include(@post1.bullet_key)\n          expect(CounterCache.possible_objects).to be_include(@post2.bullet_key)\n        end\n\n        it 'should add impossible object' do\n          CounterCache.add_impossible_object(@post1)\n          expect(CounterCache.impossible_objects).to be_include(@post1.bullet_key)\n        end\n      end\n\n      context '.conditions_met?' do\n        it 'should be true when object is possible, not impossible' do\n          CounterCache.add_possible_objects(@post1)\n          expect(CounterCache.conditions_met?(@post1, :associations)).to eq true\n        end\n\n        it 'should be false when object is not possible' do\n          expect(CounterCache.conditions_met?(@post1, :associations)).to eq false\n        end\n\n        it 'should be false when object is possible, and impossible' do\n          CounterCache.add_possible_objects(@post1)\n          CounterCache.add_impossible_object(@post1)\n          expect(CounterCache.conditions_met?(@post1, :associations)).to eq false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/detector/n_plus_one_query_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\nrequire 'ostruct'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Detector\n    describe NPlusOneQuery do\n      before(:all) do\n        @post = Post.first\n        @post2 = Post.last\n      end\n\n      context '.call_association' do\n        it 'should add call_object_associations' do\n          expect(NPlusOneQuery).to receive(:add_call_object_associations).with(@post, :associations)\n          NPlusOneQuery.call_association(@post, :associations)\n        end\n      end\n\n      context '.possible?' do\n        it 'should be true if possible_objects contain' do\n          NPlusOneQuery.add_possible_objects(@post)\n          expect(NPlusOneQuery.possible?(@post)).to eq true\n        end\n      end\n\n      context '.impossible?' do\n        it 'should be true if impossible_objects contain' do\n          NPlusOneQuery.add_impossible_object(@post)\n          expect(NPlusOneQuery.impossible?(@post)).to eq true\n        end\n      end\n\n      context '.association?' do\n        it 'should be true if object, associations pair is already existed' do\n          NPlusOneQuery.add_object_associations(@post, :association)\n          expect(NPlusOneQuery.association?(@post, :association)).to eq true\n        end\n\n        it 'should be false if object, association pair is not existed' do\n          NPlusOneQuery.add_object_associations(@post, :association1)\n          expect(NPlusOneQuery.association?(@post, :association2)).to eq false\n        end\n      end\n\n      context '.conditions_met?' do\n        it 'should be true if object is possible, not impossible and object, associations pair is not already existed' do\n          allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(true)\n          allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(false)\n          allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(false)\n          expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq true\n        end\n\n        it 'should be false if object is not possible, not impossible and object, associations pair is not already existed' do\n          allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(false)\n          allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(false)\n          allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(false)\n          expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq false\n        end\n\n        it 'should be false if object is possible, but impossible and object, associations pair is not already existed' do\n          allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(true)\n          allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(true)\n          allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(false)\n          expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq false\n        end\n\n        it 'should be false if object is possible, not impossible and object, associations pair is already existed' do\n          allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(true)\n          allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(false)\n          allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(true)\n          expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq false\n        end\n      end\n\n      context '.call_association' do\n        it 'should create notification if conditions met' do\n          expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)\n          expect(NPlusOneQuery).to receive(:caller_in_project).and_return(%w[caller])\n          expect(NPlusOneQuery).to receive(:create_notification).with(%w[caller], 'Post', :association)\n          NPlusOneQuery.call_association(@post, :association)\n        end\n\n        it 'should not create notification if conditions not met' do\n          expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(false)\n          expect(NPlusOneQuery).not_to receive(:caller_in_project!)\n          expect(NPlusOneQuery).not_to receive(:create_notification).with('Post', :association)\n          NPlusOneQuery.call_association(@post, :association)\n        end\n\n        it 'stores provided caller stack in call_stacks for use by caller_in_project' do\n          root_path = Dir.pwd\n          bundler_path = Bundler.bundle_path.to_s\n\n          in_project = File.join(root_path, 'app/models/post.rb')\n          vendored = File.join(root_path, 'vendor/some_gem.rb')\n          from_bundle = File.join(bundler_path, 'rack.rb')\n\n          call_stacks = NPlusOneQuery.send(:call_stacks)\n          bullet_key = @post.bullet_key\n          call_stacks.delete(bullet_key)\n\n          custom_stack = [in_project, vendored, from_bundle]\n\n          NPlusOneQuery.call_association(@post, :association, custom_stack)\n\n          expect(call_stacks[bullet_key]).to eq(custom_stack)\n          expect(NPlusOneQuery.caller_in_project(bullet_key)).to eq([in_project])\n        end\n\n        context 'stacktrace_excludes' do\n          before { Bullet.stacktrace_excludes = [/def/] }\n          after { Bullet.stacktrace_excludes = nil }\n\n          it 'should not create notification when stacktrace contains paths that are in the exclude list' do\n            in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))\n            included_path = OpenStruct.new(absolute_path: '/ghi/ghi.rb')\n            excluded_path = OpenStruct.new(absolute_path: '/def/def.rb')\n\n            expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, included_path, excluded_path])\n            expect(NPlusOneQuery).to_not receive(:create_notification)\n            NPlusOneQuery.call_association(@post, :association)\n          end\n\n          # just a sanity spec to make sure the following spec works correctly\n          it \"should create notification when stacktrace contains methods that aren't in the exclude list\" do\n            method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location\n            in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))\n            excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)\n\n            expect(NPlusOneQuery).to receive(:caller_locations).at_least(1).and_return([in_project, excluded_path])\n            expect(NPlusOneQuery).to receive(:conditions_met?).and_return(true)\n            expect(NPlusOneQuery).to receive(:create_notification)\n            NPlusOneQuery.call_association(@post, :association)\n          end\n\n          it 'should not create notification when stacktrace contains methods that are in the exclude list' do\n            method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location\n            Bullet.stacktrace_excludes = [method]\n            in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))\n            excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)\n\n            expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, excluded_path])\n            expect(NPlusOneQuery).to_not receive(:create_notification)\n            NPlusOneQuery.call_association(@post, :association)\n          end\n        end\n      end\n\n      context '.add_possible_objects' do\n        it 'should add possible objects' do\n          NPlusOneQuery.add_possible_objects([@post, @post2])\n          expect(NPlusOneQuery.possible_objects).to be_include(@post.bullet_key)\n          expect(NPlusOneQuery.possible_objects).to be_include(@post2.bullet_key)\n        end\n\n        it 'should not raise error if object is nil' do\n          expect { NPlusOneQuery.add_possible_objects(nil) }\n            .not_to raise_error\n        end\n      end\n\n      context '.add_impossible_object' do\n        it 'should add impossible object' do\n          NPlusOneQuery.add_impossible_object(@post)\n          expect(NPlusOneQuery.impossible_objects).to be_include(@post.bullet_key)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/detector/unused_eager_loading_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Detector\n    describe UnusedEagerLoading do\n      before(:all) do\n        @post = Post.first\n        @post2 = Post.all[1]\n        @post3 = Post.last\n      end\n\n      context '.call_associations' do\n        it 'should get empty array if eager_loadings' do\n          expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty\n        end\n\n        it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do\n          UnusedEagerLoading.add_eager_loadings([@post], :association)\n          UnusedEagerLoading.add_call_object_associations(@post, :association)\n          expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(\n            [:association]\n          )\n        end\n\n        it 'should not get call associations if not exist in call_object_associations' do\n          UnusedEagerLoading.add_eager_loadings([@post], :association)\n          expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty\n        end\n      end\n\n      context '.diff_object_associations' do\n        it 'should return associations not exist in call_association' do\n          expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(\n            [:association]\n          )\n        end\n\n        it 'should return empty if associations exist in call_association' do\n          UnusedEagerLoading.add_eager_loadings([@post], :association)\n          UnusedEagerLoading.add_call_object_associations(@post, :association)\n          expect(\n            UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))\n          ).to be_empty\n        end\n      end\n\n      context '.check_unused_preload_associations' do\n        let(:paths) { %w[/dir1 /dir1/subdir] }\n        it 'should create notification if object_association_diff is not empty' do\n          UnusedEagerLoading.add_object_associations(@post, :association)\n          allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)\n          expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', [:association])\n          UnusedEagerLoading.check_unused_preload_associations\n        end\n\n        it 'should not create notification if object_association_diff is empty' do\n          UnusedEagerLoading.add_object_associations(@post, :association)\n          UnusedEagerLoading.add_eager_loadings([@post], :association)\n          UnusedEagerLoading.add_call_object_associations(@post, :association)\n          expect(\n            UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))\n          ).to be_empty\n          expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])\n          UnusedEagerLoading.check_unused_preload_associations\n        end\n\n        it 'should create call stack for notification' do\n          UnusedEagerLoading.add_object_associations(@post, :association)\n          expect(UnusedEagerLoading.send(:call_stacks).registry).not_to be_empty\n        end\n      end\n\n      context '.add_eager_loadings' do\n        it 'should add objects, associations pair when eager_loadings are empty' do\n          UnusedEagerLoading.add_eager_loadings([@post, @post2], :associations)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(\n            [@post.bullet_key, @post2.bullet_key],\n            :associations\n          )\n        end\n\n        it 'should add objects, associations pair for existing eager_loadings' do\n          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)\n          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(\n            [@post.bullet_key, @post2.bullet_key],\n            :association1\n          )\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include(\n            [@post.bullet_key, @post2.bullet_key],\n            :association2\n          )\n        end\n\n        it 'should merge objects, associations pair for existing eager_loadings' do\n          UnusedEagerLoading.add_eager_loadings([@post], :association1)\n          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association2)\n        end\n\n        it 'should vmerge objects recursively, associations pair for existing eager_loadings' do\n          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)\n          UnusedEagerLoading.add_eager_loadings([@post, @post3], :association1)\n          UnusedEagerLoading.add_eager_loadings([@post, @post3], :association2)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post3.bullet_key], :association1)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post3.bullet_key], :association2)\n        end\n\n        it 'should delete objects, associations pair for existing eager_loadings' do\n          UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)\n          UnusedEagerLoading.add_eager_loadings([@post], :association2)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)\n          expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/ext/object_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\ndescribe Object do\n  context 'bullet_key' do\n    it 'should return class and id composition' do\n      post = Post.first\n      expect(post.bullet_key).to eq(\"Post:#{post.id}\")\n    end\n\n    if mongoid?\n      it 'should return class with namespace and id composition' do\n        post = Mongoid::Post.first\n        expect(post.bullet_key).to eq(\"Mongoid::Post:#{post.id}\")\n      end\n    end\n  end\n\n  context 'bullet_primary_key_value' do\n    it 'should return id' do\n      post = Post.first\n      expect(post.bullet_primary_key_value).to eq(post.id)\n    end\n\n    it 'should return primary key value' do\n      Post.primary_key = 'name'\n      post = Post.first\n      expect(post.bullet_primary_key_value).to eq(post.name)\n      Post.primary_key = 'id'\n    end\n\n    it 'should return value for multiple primary keys from the composite_primary_key gem' do\n      allow(Post).to receive(:primary_keys).and_return(%i[category_id writer_id])\n      post = Post.first\n      expect(post.bullet_primary_key_value).to eq(\"#{post.category_id},#{post.writer_id}\")\n    end\n\n    it 'should return empty value for multiple primary keys without values' do\n      allow(Post).to receive(:primary_keys).and_return(%i[category_id writer_id])\n      post = Post.select('1 as myfield').first\n      expect(post.bullet_primary_key_value).to eq(\"\")\n    end\n\n    if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new('7.1')\n      it 'should return value for multiple primary keys from ActiveRecord 7.1' do\n        allow(Post).to receive(:primary_key).and_return(%i[category_id writer_id])\n        post = Post.first\n\n        expect(post.bullet_primary_key_value).to eq(\"#{post.category_id},#{post.writer_id}\")\n      end\n    end\n\n    it 'should return nil for unpersisted records' do\n      post = Post.new(id: 123)\n      expect(post.bullet_primary_key_value).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/ext/string_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::String\n\ndescribe String do\n  context 'bullet_class_name' do\n    it 'should only return class name' do\n      expect('Post:1'.bullet_class_name).to eq('Post')\n    end\n\n    it 'should return class name with namespace' do\n      expect('Mongoid::Post:1234567890'.bullet_class_name).to eq('Mongoid::Post')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/notification/base_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  module Notification\n    describe Base do\n      subject { Base.new(Post, %i[comments votes]) }\n\n      context '#title' do\n        it 'should raise NoMethodError' do\n          expect { subject.title }\n            .to raise_error(NoMethodError)\n        end\n      end\n\n      context '#body' do\n        it 'should raise NoMethodError' do\n          expect { subject.body }\n            .to raise_error(NoMethodError)\n        end\n      end\n\n      context '#whoami' do\n        it 'should display user name' do\n          user = `whoami`.chomp\n          expect(subject.whoami).to eq(\"user: #{user}\")\n        end\n\n        it 'should leverage ENV parameter' do\n          temp_env_variable('USER', 'bogus') { expect(subject.whoami).to eq('user: bogus') }\n        end\n\n        it 'should return blank if no user available' do\n          temp_env_variable('USER', '') do\n            expect(subject).to receive(:`).with('whoami').and_return('')\n            expect(subject.whoami).to eq('')\n          end\n        end\n\n        it 'should return blank if whoami is not available' do\n          temp_env_variable('USER', '') do\n            expect(subject).to receive(:`).with('whoami').and_raise(Errno::ENOENT)\n            expect(subject.whoami).to eq('')\n          end\n        end\n\n        def temp_env_variable(name, value)\n          old_value = ENV[name]\n          ENV[name] = value\n          yield\n        ensure\n          ENV[name] = old_value\n        end\n      end\n\n      context '#body_with_caller' do\n        it 'should return body' do\n          allow(subject).to receive(:body).and_return('body')\n          allow(subject).to receive(:call_stack_messages).and_return('call_stack_messages')\n          expect(subject.body_with_caller).to eq(\"body\\ncall_stack_messages\\n\")\n        end\n      end\n\n      context '#notification_data' do\n        it 'should return notification data' do\n          allow(subject).to receive(:whoami).and_return('whoami')\n          allow(subject).to receive(:url).and_return('url')\n          allow(subject).to receive(:title).and_return('title')\n          allow(subject).to receive(:body_with_caller).and_return('body_with_caller')\n          expect(subject.notification_data).to eq(user: 'whoami', url: 'url', title: 'title', body: 'body_with_caller')\n        end\n\n        context 'when skip_user_in_notification is true' do\n          before { allow(Bullet).to receive(:skip_user_in_notification).and_return(true) }\n\n          it 'should return notification data without user' do\n            allow(subject).to receive(:url).and_return('url')\n            allow(subject).to receive(:title).and_return('title')\n            allow(subject).to receive(:body_with_caller).and_return('body_with_caller')\n\n            expect(subject.notification_data).to eq(url: 'url', title: 'title', body: 'body_with_caller')\n          end\n        end\n      end\n\n      context '#notify_inline' do\n        it 'should send full_notice to notifier' do\n          notifier = double\n          allow(subject).to receive(:notifier).and_return(notifier)\n          allow(subject).to receive(:notification_data).and_return({ foo: :bar })\n          expect(notifier).to receive(:inline_notify).with({ foo: :bar })\n          subject.notify_inline\n        end\n      end\n\n      context '#notify_out_of_channel' do\n        it 'should send full_out_of_channel to notifier' do\n          notifier = double\n          allow(subject).to receive(:notifier).and_return(notifier)\n          allow(subject).to receive(:notification_data).and_return({ foo: :bar })\n          expect(notifier).to receive(:out_of_channel_notify).with({ foo: :bar })\n          subject.notify_out_of_channel\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/notification/counter_cache_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  module Notification\n    describe CounterCache do\n      subject { CounterCache.new(Post, %i[comments votes]) }\n\n      it { expect(subject.body).to eq('  Post => [:comments, :votes]') }\n      it { expect(subject.title).to eq('Need Counter Cache with Active Record size') }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/notification/n_plus_one_query_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  module Notification\n    describe NPlusOneQuery do\n      subject { NPlusOneQuery.new([%w[caller1 caller2]], Post, %i[comments votes], 'path') }\n\n      it do\n        expect(subject.body_with_caller).to eq(\n          \"  Post => [:comments, :votes]\\n  Add to your query: .includes([:comments, :votes])\\nCall stack\\n  caller1\\n  caller2\\n\"\n        )\n      end\n      it do\n        expect([subject.body_with_caller, subject.body_with_caller]).to eq(\n          [\n            \"  Post => [:comments, :votes]\\n  Add to your query: .includes([:comments, :votes])\\nCall stack\\n  caller1\\n  caller2\\n\",\n            \"  Post => [:comments, :votes]\\n  Add to your query: .includes([:comments, :votes])\\nCall stack\\n  caller1\\n  caller2\\n\"\n          ]\n        )\n      end\n      it do\n        expect(subject.body).to eq(\"  Post => [:comments, :votes]\\n  Add to your query: .includes([:comments, :votes])\")\n      end\n      it { expect(subject.title).to eq('USE eager loading in path') }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/notification/unused_eager_loading_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  module Notification\n    describe UnusedEagerLoading do\n      subject { UnusedEagerLoading.new([''], Post, %i[comments votes], 'path') }\n\n      it do\n        expect(subject.body).to eq(\n          \"  Post => [:comments, :votes]\\n  Remove from your query: .includes([:comments, :votes])\"\n        )\n      end\n      it { expect(subject.title).to eq('AVOID eager loading in path') }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/notification_collector_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  describe NotificationCollector do\n    subject { NotificationCollector.new.tap { |collector| collector.add('value') } }\n\n    context '#add' do\n      it 'should add a value' do\n        subject.add('value1')\n        expect(subject.collection).to be_include('value1')\n      end\n    end\n\n    context '#reset' do\n      it 'should reset collector' do\n        subject.reset\n        expect(subject.collection).to be_empty\n      end\n    end\n\n    context '#notifications_present?' do\n      it 'should be true if collection is not empty' do\n        expect(subject).to be_notifications_present\n      end\n\n      it 'should be false if collection is empty' do\n        subject.reset\n        expect(subject).not_to be_notifications_present\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/rack_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  describe Rack do\n    let(:middleware) { Bullet::Rack.new app }\n    let(:app) { Support::AppDouble.new }\n\n    context '#html_request?' do\n      it 'should be true if Content-Type is text/html and http body contains html tag' do\n        headers = { 'Content-Type' => 'text/html' }\n        response = double(body: '<html><head></head><body></body></html>')\n        expect(middleware).to be_html_request(headers, response)\n      end\n\n      it 'should be true if Content-Type is text/html and http body contains html tag with attributes' do\n        headers = { 'Content-Type' => 'text/html' }\n        response = double(body: \"<html attr='hello'><head></head><body></body></html>\")\n        expect(middleware).to be_html_request(headers, response)\n      end\n\n      it 'should be false if there is no Content-Type header' do\n        headers = {}\n        response = double(body: '<html><head></head><body></body></html>')\n        expect(middleware).not_to be_html_request(headers, response)\n      end\n\n      it 'should be false if Content-Type is javascript' do\n        headers = { 'Content-Type' => 'text/javascript' }\n        response = double(body: '<html><head></head><body></body></html>')\n        expect(middleware).not_to be_html_request(headers, response)\n      end\n    end\n\n    context '#skip_html_injection?' do\n      let(:request) { double('request') }\n\n      it 'should return false if query_string is nil' do\n        allow(request).to receive(:env).and_return({ 'QUERY_STRING' => nil })\n        expect(middleware.skip_html_injection?(request)).to be_falsey\n      end\n\n      it 'should return false if query_string is empty' do\n        allow(request).to receive(:env).and_return({ 'QUERY_STRING' => '' })\n        expect(middleware.skip_html_injection?(request)).to be_falsey\n      end\n\n      it 'should return true if skip_html_injection parameter is true' do\n        allow(request).to receive(:env).and_return({ 'QUERY_STRING' => 'skip_html_injection=true' })\n        expect(middleware.skip_html_injection?(request)).to be_truthy\n      end\n\n      it 'should return false if skip_html_injection parameter is not true' do\n        allow(request).to receive(:env).and_return({ 'QUERY_STRING' => 'skip_html_injection=false' })\n        expect(middleware.skip_html_injection?(request)).to be_falsey\n      end\n\n      it 'should return false if skip_html_injection parameter is not present' do\n        allow(request).to receive(:env).and_return({ 'QUERY_STRING' => 'other_param=value' })\n        expect(middleware.skip_html_injection?(request)).to be_falsey\n      end\n\n      it 'should handle complex query strings' do\n        allow(request).to receive(:env).and_return({ 'QUERY_STRING' => 'param1=value1&skip_html_injection=true&param2=value2' })\n        expect(middleware.skip_html_injection?(request)).to be_truthy\n      end\n    end\n\n    context 'empty?' do\n      it 'should be false if response is a string and not empty' do\n        response = double(body: '<html><head></head><body></body></html>')\n        expect(middleware).not_to be_empty(response)\n      end\n\n      it 'should be false if response is not found' do\n        response = ['Not Found']\n        expect(middleware).not_to be_empty(response)\n      end\n\n      it 'should be true if response body is empty' do\n        response = double(body: '')\n        expect(middleware).to be_empty(response)\n      end\n\n      it 'should be true if no response body' do\n        response = double\n        expect(middleware).to be_empty(response)\n      end\n    end\n\n    context '#call' do\n      context 'when Bullet is enabled' do\n        it 'should return original response body' do\n          expected_response = Support::ResponseDouble.new 'Actual body'\n          app.response = expected_response\n          _, _, response = middleware.call({})\n          expect(response).to eq(expected_response)\n        end\n\n        it 'should change response body if notification is active' do\n          expect(Bullet).to receive(:notification?).and_return(true)\n          expect(Bullet).to receive(:console_enabled?).and_return(true)\n          expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')\n          expect(Bullet).to receive(:perform_out_of_channel_notifications)\n          _, headers, response = middleware.call('Content-Type' => 'text/html')\n          expect(headers['Content-Length']).to eq('56')\n          expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])\n        end\n\n        it 'should change response body if always_append_html_body is true' do\n          expect(Bullet).to receive(:always_append_html_body).and_return(true)\n          expect(Bullet).to receive(:console_enabled?).and_return(true)\n          expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')\n          expect(Bullet).to receive(:perform_out_of_channel_notifications)\n          _, headers, response = middleware.call('Content-Type' => 'text/html')\n          expect(headers['Content-Length']).to eq('56')\n          expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])\n        end\n\n        it 'should set the right Content-Length if response body contains accents' do\n          response = Support::ResponseDouble.new\n          response.body = '<html><head></head><body>é</body></html>'\n          app.response = response\n          expect(Bullet).to receive(:notification?).and_return(true)\n          allow(Bullet).to receive(:console_enabled?).and_return(true)\n          expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')\n          _, headers, response = middleware.call('Content-Type' => 'text/html')\n          expect(headers['Content-Length']).to eq('58')\n        end\n\n        shared_examples 'inject notifiers' do\n          before do\n            allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')\n            allow(middleware).to receive(:xhr_script).and_return('<script></script>')\n            allow(middleware).to receive(:footer_note).and_return('footer')\n            expect(Bullet).to receive(:perform_out_of_channel_notifications)\n          end\n\n          it 'should change response body if add_footer is true' do\n            expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n\n            expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)\n            expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])\n          end\n\n          it 'should change response body for html safe string if add_footer is true' do\n            expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)\n            app.response =\n              Support::ResponseDouble.new.tap do |response|\n                response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')\n              end\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n\n            expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)\n            expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])\n          end\n\n          it 'should add the footer-text header for non-html requests when add_footer is true' do\n            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)\n            allow(Bullet).to receive(:footer_info).and_return(['footer text'])\n            app.headers = { 'Content-Type' => 'application/json' }\n            _, headers, _response = middleware.call({})\n            expect(headers).to include('X-bullet-footer-text' => '[\"footer text\"]')\n          end\n\n          it 'should change response body if console_enabled is true' do\n            expect(Bullet).to receive(:console_enabled?).and_return(true)\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n            expect(headers['Content-Length']).to eq('56')\n            expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])\n          end\n\n          it 'should include CSP nonce in inline script if console_enabled and a CSP is applied' do\n            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)\n            expect(Bullet).to receive(:console_enabled?).and_return(true)\n            allow(middleware).to receive(:xhr_script).and_call_original\n\n            nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='\n            app.headers = {\n              'Content-Type' => 'text/html',\n              'Content-Security-Policy' => \"default-src 'self' https:; script-src 'self' https: 'nonce-#{nonce}'\"\n            }\n\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n\n            size = 56 + middleware.send(:footer_note, nonce).length + middleware.send(:xhr_script, nonce).length\n            expect(headers['Content-Length']).to eq(size.to_s)\n          end\n\n          it 'should include CSP nonce in inline script if console_enabled and a CSP (report only) is applied' do\n            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)\n            expect(Bullet).to receive(:console_enabled?).and_return(true)\n            allow(middleware).to receive(:xhr_script).and_call_original\n\n            nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='\n            app.headers = {\n              'Content-Type' => 'text/html',\n              'Content-Security-Policy-Report-Only' =>\n                \"default-src 'self' https:; script-src 'self' https: 'nonce-#{nonce}'\"\n            }\n\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n\n            size = 56 + middleware.send(:footer_note, nonce).length + middleware.send(:xhr_script, nonce).length\n            expect(headers['Content-Length']).to eq(size.to_s)\n          end\n\n          it 'should include CSP nonce in inline style if console_enabled and a CSP is applied' do\n            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)\n            expect(Bullet).to receive(:console_enabled?).and_return(true)\n            allow(middleware).to receive(:xhr_script).and_call_original\n\n            nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='\n            app.headers = {\n              'Content-Type' => 'text/html',\n              'Content-Security-Policy' => \"default-src 'self' https:; style-src 'self' https: 'nonce-#{nonce}'\"\n            }\n\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n\n            size = 56 + middleware.send(:footer_note, nonce).length + middleware.send(:xhr_script, nonce).length\n            expect(headers['Content-Length']).to eq(size.to_s)\n          end\n\n          it 'should include CSP nonce in inline style if console_enabled and a CSP (report only) is applied' do\n            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)\n            expect(Bullet).to receive(:console_enabled?).and_return(true)\n            allow(middleware).to receive(:xhr_script).and_call_original\n\n            nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='\n            app.headers = {\n              'Content-Type' => 'text/html',\n              'Content-Security-Policy-Report-Only' =>\n                \"default-src 'self' https:; style-src 'self' https: 'nonce-#{nonce}'\"\n            }\n\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n\n            size = 56 + middleware.send(:footer_note, nonce).length + middleware.send(:xhr_script, nonce).length\n            expect(headers['Content-Length']).to eq(size.to_s)\n          end\n\n          it 'should change response body for html safe string if console_enabled is true' do\n            expect(Bullet).to receive(:console_enabled?).and_return(true)\n            app.response =\n              Support::ResponseDouble.new.tap do |response|\n                response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')\n              end\n            _, headers, response = middleware.call('Content-Type' => 'text/html')\n            expect(headers['Content-Length']).to eq('56')\n            expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])\n          end\n\n          it 'should add headers for non-html requests when console_enabled is true' do\n            allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)\n            allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])\n            app.headers = { 'Content-Type' => 'application/json' }\n            _, headers, _response = middleware.call({})\n            expect(headers).to include('X-bullet-console-text' => '[\"text notifications\"]')\n          end\n\n          it \"shouldn't change response body unnecessarily\" do\n            expected_response = Support::ResponseDouble.new 'Actual body'\n            app.response = expected_response\n            _, _, response = middleware.call({})\n            expect(response).to eq(expected_response)\n          end\n\n          it \"shouldn't add headers unnecessarily\" do\n            app.headers = { 'Content-Type' => 'application/json' }\n            _, headers, _response = middleware.call({})\n            expect(headers).not_to include('X-bullet-footer-text')\n            expect(headers).not_to include('X-bullet-console-text')\n          end\n\n          context 'when skip_http_headers is enabled' do\n            before do\n              allow(Bullet).to receive(:skip_http_headers).and_return(true)\n            end\n\n            it 'should include the footer but not the xhr script tag if add_footer is true' do\n              expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)\n              _, headers, response = middleware.call({})\n\n              expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)\n              expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])\n            end\n\n            it 'should not include the xhr script tag if console_enabled is true' do\n              expect(Bullet).to receive(:console_enabled?).and_return(true)\n              _, headers, response = middleware.call({})\n              expect(headers['Content-Length']).to eq('56')\n              expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])\n            end\n\n            it 'should not add the footer-text header for non-html requests when add_footer is true' do\n              allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)\n              app.headers = { 'Content-Type' => 'application/json' }\n              _, headers, _response = middleware.call({})\n              expect(headers).not_to include('X-bullet-footer-text')\n            end\n\n            it 'should not add headers for non-html requests when console_enabled is true' do\n              allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)\n              app.headers = { 'Content-Type' => 'application/json' }\n              _, headers, _response = middleware.call({})\n              expect(headers).not_to include('X-bullet-console-text')\n            end\n          end\n        end\n\n        context 'with notifications present' do\n          before do\n            expect(Bullet).to receive(:notification?).and_return(true)\n          end\n\n          include_examples 'inject notifiers'\n        end\n\n        context 'with always_append_html_body true' do\n          before do\n            expect(Bullet).to receive(:always_append_html_body).and_return(true)\n          end\n\n          include_examples 'inject notifiers'\n        end\n\n        context 'when skip_html_injection is enabled' do\n          it 'should not try to inject html' do\n            expected_response = Support::ResponseDouble.new 'Actual body'\n            app.response = expected_response\n            allow(Bullet).to receive(:notification?).and_return(true)\n            allow(Bullet).to receive(:skip_html_injection?).and_return(true)\n            expect(Bullet).to receive(:gather_inline_notifications).never\n            expect(middleware).to receive(:xhr_script).never\n            expect(Bullet).to receive(:perform_out_of_channel_notifications)\n            _, _, response = middleware.call('Content-Type' => 'text/html')\n            expect(response).to eq(expected_response)\n          end\n        end\n      end\n\n      context 'when Bullet is disabled' do\n        before(:each) { allow(Bullet).to receive(:enable?).and_return(false) }\n\n        it 'should not call Bullet.start_request' do\n          expect(Bullet).not_to receive(:start_request)\n          middleware.call({})\n        end\n      end\n    end\n\n    context '#set_header' do\n      it 'should truncate headers to under 8kb' do\n        long_header = ['a' * 1_024] * 10\n        expected_res = (['a' * 1_024] * 7).to_json\n        expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)\n      end\n    end\n\n    describe '#response_body' do\n      let(:response) { double }\n      let(:body_string) { '<html><body>My Body</body></html>' }\n\n      context 'when `response` responds to `body`' do\n        before { allow(response).to receive(:body).and_return(body) }\n\n        context 'when `body` returns an Array' do\n          let(:body) { [body_string, 'random string'] }\n          it 'should return the plain body string' do\n            expect(middleware.response_body(response)).to eq body_string\n          end\n        end\n\n        context 'when `body` does not return an Array' do\n          let(:body) { body_string }\n          it 'should return the plain body string' do\n            expect(middleware.response_body(response)).to eq body_string\n          end\n        end\n      end\n\n      context 'when `response` does not respond to `body`' do\n        before { allow(response).to receive(:first).and_return(body_string) }\n\n        it 'should return the plain body string' do\n          expect(middleware.response_body(response)).to eq body_string\n        end\n      end\n\n      begin\n        require 'rack/files'\n\n        context 'when `response` is a Rack::Files::Iterator' do\n          let(:response) { instance_double(::Rack::Files::Iterator) }\n          before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }\n\n          it 'should return nil' do\n            expect(middleware.response_body(response)).to be_nil\n          end\n        end\n      rescue LoadError\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/registry/association_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  module Registry\n    describe Association do\n      subject { Association.new.tap { |association| association.add(%w[key1 key2], 'value') } }\n\n      context '#merge' do\n        it 'should merge key/value' do\n          subject.merge('key0', 'value0')\n          expect(subject['key0']).to be_include('value0')\n        end\n      end\n\n      context '#similarly_associated' do\n        it 'should return similarly associated keys' do\n          expect(subject.similarly_associated('key1', Set.new(%w[value]))).to eq(%w[key1 key2])\n        end\n\n        it 'should return empty if key does not exist' do\n          expect(subject.similarly_associated('key3', Set.new(%w[value]))).to be_empty\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/registry/base_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  module Registry\n    describe Base do\n      subject { Base.new.tap { |base| base.add('key', 'value') } }\n\n      context '#[]' do\n        it 'should get value by key' do\n          expect(subject['key']).to eq(Set.new(%w[value]))\n        end\n      end\n\n      context '#delete' do\n        it 'should delete key' do\n          subject.delete('key')\n          expect(subject['key']).to be_nil\n        end\n      end\n\n      context '#add' do\n        it 'should add value with string' do\n          subject.add('key', 'new_value')\n          expect(subject['key']).to eq(Set.new(%w[value new_value]))\n        end\n\n        it 'should add value with array' do\n          subject.add('key', %w[value1 value2])\n          expect(subject['key']).to eq(Set.new(%w[value value1 value2]))\n        end\n      end\n\n      context '#include?' do\n        it 'should include key/value' do\n          expect(subject.include?('key', 'value')).to eq true\n        end\n\n        it 'should not include wrong key/value' do\n          expect(subject.include?('key', 'val')).to eq false\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/registry/object_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  module Registry\n    describe Object do\n      let(:post) { Post.first }\n      let(:another_post) { Post.last }\n      subject { Object.new.tap { |object| object.add(post.bullet_key) } }\n\n      context '#include?' do\n        it 'should include the object' do\n          expect(subject).to be_include(post.bullet_key)\n        end\n      end\n\n      context '#add' do\n        it 'should add an object' do\n          subject.add(another_post.bullet_key)\n          expect(subject).to be_include(another_post.bullet_key)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet/stack_trace_filter_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nmodule Bullet\n  RSpec.describe StackTraceFilter do\n    let(:dummy_class) { Class.new { extend StackTraceFilter } }\n    let(:root_path) { Dir.pwd }\n    let(:bundler_path) { Bundler.bundle_path }\n\n    describe '#caller_in_project' do\n      it 'gets the caller in the project' do\n        expect(dummy_class).to receive(:call_stacks).and_return(\n          {\n            'Post:1' => [\n              File.join(root_path, 'lib/bullet.rb'),\n              File.join(root_path, 'vendor/uniform_notifier.rb'),\n              File.join(bundler_path, 'rack.rb')\n            ]\n          }\n        )\n        expect(dummy_class.caller_in_project('Post:1')).to eq([File.join(root_path, 'lib/bullet.rb')])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/bullet_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\ndescribe Bullet do\n  subject { Bullet }\n\n  describe '#enable' do\n    context 'enable Bullet' do\n      before do\n        # Bullet.enable\n        # Do nothing. Bullet has already been enabled for the whole test suite.\n      end\n\n      it 'should be enabled' do\n        expect(subject).to be_enable\n      end\n\n      context 'disable Bullet' do\n        before { Bullet.enable = false }\n\n        it 'should be disabled' do\n          expect(subject).to_not be_enable\n        end\n\n        context 'enable Bullet again without patching again the orms' do\n          before do\n            expect(Bullet::Mongoid).not_to receive(:enable) if defined?(Bullet::Mongoid)\n            expect(Bullet::ActiveRecord).not_to receive(:enable) if defined?(Bullet::ActiveRecord)\n            Bullet.enable = true\n          end\n\n          it 'should be enabled again' do\n            expect(subject).to be_enable\n          end\n        end\n      end\n    end\n  end\n\n  # Testing the aliases.\n  describe '#enabled' do\n    context 'enable Bullet' do\n      before do\n        # Bullet.enable\n        # Do nothing. Bullet has already been enabled for the whole test suite.\n      end\n\n      it 'should be enabled' do\n        expect(subject).to be_enabled\n      end\n\n      context 'disable Bullet' do\n        before { Bullet.enabled = false }\n\n        it 'should be disabled' do\n          expect(subject).to_not be_enabled\n        end\n\n        context 'enable Bullet again without patching again the orms' do\n          before do\n            expect(Bullet::Mongoid).not_to receive(:enabled) if defined?(Bullet::Mongoid)\n            expect(Bullet::ActiveRecord).not_to receive(:enabled) if defined?(Bullet::ActiveRecord)\n            Bullet.enabled = true\n          end\n\n          it 'should be enabled again' do\n            expect(subject).to be_enabled\n          end\n        end\n      end\n    end\n  end\n\n  describe '#start?' do\n    context 'when bullet is disabled' do\n      before(:each) { Bullet.enable = false }\n\n      it 'should not be started' do\n        expect(Bullet).not_to be_start\n      end\n    end\n  end\n\n  describe '#debug' do\n    before(:each) { $stdout = StringIO.new }\n\n    after(:each) { $stdout = STDOUT }\n\n    context 'when debug is enabled' do\n      before(:each) { ENV['BULLET_DEBUG'] = 'true' }\n\n      after(:each) { ENV['BULLET_DEBUG'] = 'false' }\n\n      it 'should output debug information' do\n        Bullet.debug('debug_message', 'this is helpful information')\n\n        expect($stdout.string).to eq(\"[Bullet][debug_message] this is helpful information\\n\")\n      end\n    end\n\n    context 'when debug is disabled' do\n      it 'should output debug information' do\n        Bullet.debug('debug_message', 'this is helpful information')\n\n        expect($stdout.string).to be_empty\n      end\n    end\n  end\n\n  describe '#add_safelist' do\n    context \"for 'special' class names\" do\n      it 'is added to the safelist successfully' do\n        Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)\n        expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department\n      end\n    end\n\n    context 'when association is registered as string (e.g., Action Text)' do\n      it 'returns both symbol and string forms to match either' do\n        Bullet.add_safelist(type: :unused_eager_loading, class_name: 'Note', association: :rich_text_content)\n        safelist = Bullet.get_safelist_associations(:unused_eager_loading, 'Note')\n        expect(safelist).to include(:rich_text_content)\n        expect(safelist).to include('rich_text_content')\n      end\n    end\n  end\n\n  describe '#delete_safelist' do\n    context \"for 'special' class names\" do\n      it 'is deleted from the safelist successfully' do\n        Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)\n        Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)\n        expect(Bullet.safelist[:n_plus_one_query]).to eq({})\n      end\n    end\n\n    context 'when exists multiple definitions' do\n      it 'is deleted from the safelist successfully' do\n        Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)\n        Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)\n        Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)\n        expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department\n        expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team\n      end\n    end\n  end\n\n  describe '#perform_out_of_channel_notifications' do\n    let(:notification) { double }\n\n    before do\n      allow(Bullet).to receive(:for_each_active_notifier_with_notification).and_yield(notification)\n      allow(notification).to receive(:notify_out_of_channel)\n    end\n\n    context 'when called with Rack environment hash' do\n      let(:env) { { 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/path', 'QUERY_STRING' => 'foo=bar' } }\n\n      context \"when env['REQUEST_URI'] is nil\" do\n        before { env['REQUEST_URI'] = nil }\n\n        it 'should notification.url is built' do\n          expect(notification).to receive(:url=).with('GET /path?foo=bar')\n          Bullet.perform_out_of_channel_notifications(env)\n        end\n      end\n\n      context \"when env['REQUEST_URI'] is present\" do\n        before { env['REQUEST_URI'] = 'http://example.com/path' }\n\n        it \"should notification.url is env['REQUEST_URI']\" do\n          expect(notification).to receive(:url=).with('GET http://example.com/path')\n          Bullet.perform_out_of_channel_notifications(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/active_record/association_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nif active_record?\n  def post_comments_empty_call_site(post)\n    post.comments.empty?\n  end\n\n  describe Bullet::Detector::Association, 'has_many' do\n    context 'post => comments' do\n      it 'should detect non preload post => comments' do\n        Post.all.each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n      end\n\n      it 'should detect non preload post => comments for find_by_sql' do\n        Post.find_by_sql('SELECT * FROM posts').each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n      end\n\n      it 'should detect preload with post => comments' do\n        Post.includes(:comments).each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload post => comments' do\n        Post.includes(:comments).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect unused preload post => comments' do\n        Post.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect non preload comment => post with inverse_of' do\n        Post.includes(:comments).each do |post|\n          post.comments.each do |comment|\n            comment.name\n            comment.post.name\n          end\n        end\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect non preload comment => post with inverse_of from a query' do\n        Post.first.comments.find_each do |comment|\n          comment.name\n          comment.post.name\n        end\n\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Post.first.comments.count).not_to eq(0)\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect non preload post => comments with empty?' do\n        Post.all.each { |post| post.comments.empty? }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n      end\n\n      it 'should detect non preload post => comments with include?' do\n        comment = Comment.last\n        Post.all.each { |post| post.comments.include?(comment) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n      end\n\n      if ActiveRecord::VERSION::MAJOR != 4 && ActiveRecord::VERSION::MINOR != 0\n        it 'should not detect unused preload post => comment with empty?' do\n          Post.includes(:comments).each { |post| post.comments.empty? }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n\n      it 'should not detect unused preload post => comment with count' do\n        Post.includes(:comments).each { |post| post.comments.count }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect non preload post => comments with count' do\n        Post.all.each { |post| post.comments.count }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n      end\n\n      if defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR >= 5\n        it 'includes the association call site at the top of the n+1 call stack for empty?' do\n          Post.all.each { |post| post_comments_empty_call_site(post) }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n          notifications = Bullet.collected_n_plus_one_query_notifications\n          expect(notifications).not_to be_empty\n\n          body_with_caller = notifications.first.body_with_caller\n          lines = body_with_caller.split(\"\\n\")\n          call_stack_lines = lines.drop_while { |line| line != 'Call stack' }[1..]\n          first_location_line = call_stack_lines.first\n\n          expect(first_location_line).to match(/post_comments_empty_call_site/)\n        end\n      end\n\n      context 'inside Fiber' do\n        it 'should detect non preload post => comments' do\n          fiber =\n            Fiber.new do\n              Post.all.each { |post| post.comments.map(&:name) }\n            end\n          fiber.resume\n\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n        end\n      end\n    end\n\n    context 'category => posts => comments' do\n      it 'should detect non preload category => posts => comments' do\n        Category.all.each { |category| category.posts.each { |post| post.comments.map(&:name) } }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Category, :posts)\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n      end\n\n      it 'should detect preload category => posts, but no post => comments' do\n        Category.includes(:posts).each { |category| category.posts.each { |post| post.comments.map(&:name) } }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).not_to be_detecting_unpreloaded_association_for(Category, :posts)\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n      end\n\n      it 'should detect preload with category => posts => comments' do\n        Category.includes(posts: :comments).each { |category| category.posts.each { |post| post.comments.map(&:name) } }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect preload with category => posts => comments with posts.id > 0' do\n        Category.includes(posts: :comments).where('posts.id > 0').references(:posts).each do |category|\n          category.posts.each { |post| post.comments.map(&:name) }\n        end\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload with category => posts => comments' do\n        Category.includes(posts: :comments).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload with post => comments, no category => posts' do\n        Category.includes(posts: :comments).each { |category| category.posts.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n\n    context 'category => posts, category => entries' do\n      it 'should detect non preload with category => [posts, entries]' do\n        Category.all.each do |category|\n          category.posts.map(&:name)\n          category.entries.map(&:name)\n        end\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Category, :posts)\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Category, :entries)\n      end\n\n      it 'should detect preload with category => posts, but not with category => entries' do\n        Category.includes(:posts).each do |category|\n          category.posts.map(&:name)\n          category.entries.map(&:name)\n        end\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).not_to be_detecting_unpreloaded_association_for(Category, :posts)\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Category, :entries)\n      end\n\n      it 'should detect preload with category => [posts, entries]' do\n        Category.includes(%i[posts entries]).each do |category|\n          category.posts.map(&:name)\n          category.entries.map(&:name)\n        end\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload with category => [posts, entries]' do\n        Category.includes(%i[posts entries]).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Category, :posts)\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Category, :entries)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload with category => entries, but not with category => posts' do\n        Category.includes(%i[posts entries]).each { |category| category.posts.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Category, :posts)\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Category, :entries)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n\n    context 'post => comment' do\n      it 'should detect unused preload with post => comments' do\n        Post.includes(:comments).each { |post| post.comments.first&.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :comments)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect preload with post => comments' do\n        Post.first.comments.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect unused preload with category => posts' do\n        category = Category.first\n        category.draft_post.destroy!\n        post = category.draft_post\n        post.update!(link: true)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n\n        Support::SqliteSeed.setup_db\n        Support::SqliteSeed.seed_db\n      end\n    end\n\n    context 'category => posts => writer' do\n      it 'should not detect unused preload associations' do\n        category = Category.includes(posts: :writer).order('id DESC').find_by_name('first')\n        category.posts.map do |post|\n          post.name\n          post.writer.name\n        end\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Category, :posts)\n        expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :writer)\n      end\n    end\n\n    context 'scope for_category_name' do\n      it 'should detect preload with post => category' do\n        Post.in_category_name('first').references(:categories).each { |post| post.category.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not be unused preload post => category' do\n        Post.in_category_name('first').references(:categories).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n\n    context 'scope preload_comments' do\n      it 'should detect preload post => comments with scope' do\n        Post.preload_comments.each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload with scope' do\n        Post.preload_comments.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n  end\n\n  describe Bullet::Detector::Association, 'belongs_to' do\n    context 'comment => post' do\n      it 'should detect non preload with comment => post' do\n        Comment.all.each { |comment| comment.post.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Comment, :post)\n      end\n\n      it 'should detect preload with one comment => post' do\n        Comment.first.post.name\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect preload with comment => post' do\n        Comment.includes(:post).each { |comment| comment.post.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect preload with comment => post' do\n        Comment.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload with comment => post' do\n        Comment.includes(:post).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Comment, :post)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      context 'in an after_save' do\n        around do |example|\n          new_post = Post.new(category: Category.first)\n          new_post.trigger_after_save = true\n          new_post.save!\n\n          example.run\n\n          new_post.destroy\n        end\n\n        it 'should not detect newly assigned object' do\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n    end\n\n    context 'comment => post => category' do\n      it 'should detect non preload association with comment => post' do\n        Comment.all.each { |comment| comment.post.category.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Comment, :post)\n      end\n\n      it 'should not detect non preload association with only one comment' do\n        Comment.first.post.category.name\n\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect non preload association with post => category' do\n        Comment.includes(:post).each { |comment| comment.post.category.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :category)\n      end\n\n      it 'should not detect unpreload association' do\n        Comment.includes(post: :category).each { |comment| comment.post.category.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n\n    context 'comment => author, post => writer' do\n      it 'should detect non preloaded writer' do\n        Comment.includes(%i[author post]).where(['base_users.id = ?', BaseUser.first]).references(:base_users)\n               .each { |comment| comment.post.writer.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :writer)\n      end\n\n      it 'should detect unused preload with comment => author' do\n        Comment.includes([:author, { post: :writer }]).where(['base_users.id = ?', BaseUser.first]).references(\n          :base_users\n        ).each { |comment| comment.post.writer.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect non preloading with writer => newspaper' do\n        Comment.includes(post: :writer).where(\"posts.name like '%first%'\").references(:posts).each do |comment|\n          comment.post.writer.newspaper.name\n        end\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Writer, :newspaper)\n      end\n\n      it 'should not raise a stack error from posts to category' do\n        expect { Comment.includes(post: :category).each { |com| com.post.category } }\n          .not_to raise_error\n      end\n    end\n  end\n\n  describe Bullet::Detector::Association, 'has_and_belongs_to_many' do\n    context 'posts <=> deals' do\n      it 'should detect preload associations with join tables that have identifier' do\n        Post.includes(:deals).each { |post| post.deals.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n    context 'students <=> teachers' do\n      it 'should detect non preload associations' do\n        Student.all.each { |student| student.teachers.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Student, :teachers)\n      end\n\n      it 'should detect preload associations' do\n        Student.includes(:teachers).each { |student| student.teachers.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload associations' do\n        Student.includes(:teachers).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Student, :teachers)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect no unused preload associations' do\n        Student.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect non preload student => teachers with empty?' do\n        Student.all.each { |student| student.teachers.empty? }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Student, :teachers)\n      end\n    end\n\n    context 'user => roles' do\n      it 'should detect preload associations' do\n        User.first.roles.includes(:resource).each { |role| role.resource }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n  end\n\n  describe Bullet::Detector::Association, 'has_many :through' do\n    context 'firm => clients' do\n      it 'should detect non preload associations' do\n        Firm.all.each { |firm| firm.clients.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Firm, :clients)\n      end\n\n      it 'should detect preload associations' do\n        Firm.preload(:clients).each { |firm| firm.clients.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect eager load association' do\n        Firm.eager_load(:clients).each { |firm| firm.clients.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect preload associations' do\n        Firm.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload associations' do\n        Firm.includes(:clients).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Firm, :clients)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n\n    context 'firm => clients => groups' do\n      it 'should detect non preload associations' do\n        Firm.all.each { |firm| firm.groups.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Firm, :groups)\n      end\n\n      it 'should detect preload associations' do\n        Firm.preload(:groups).each { |firm| firm.groups.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect eager load associations' do\n        Firm.eager_load(:groups).each { |firm| firm.groups.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect preload associations' do\n        Firm.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload associations' do\n        Firm.includes(:groups).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Firm, :groups)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n  end\n\n  describe Bullet::Detector::Association, 'has_one' do\n    context 'company => address' do\n      it 'should detect non preload association' do\n        Company.all.each { |company| company.address.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Company, :address)\n      end\n\n      it 'should detect preload association' do\n        Company.includes(:address).each { |company| company.address.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect preload association' do\n        Company.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload association' do\n        Company.includes(:address).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Company, :address)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n  end\n\n  describe Bullet::Detector::Association, 'has_one => has_many' do\n    it 'should not detect preload association' do\n      user = User.first\n      user.submission.replies.map(&:name)\n      Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n      expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n      expect(Bullet::Detector::Association).to be_completely_preloading_associations\n    end\n  end\n\n  describe Bullet::Detector::Association, 'has_one :through' do\n    context 'user => attachment' do\n      it 'should detect non preload associations' do\n        User.all.each { |user| user.submission_attachment.file_name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(User, :submission_attachment)\n      end\n\n      it 'should not detect preload associations with includes' do\n        User.includes(:submission_attachment).each { |user| user.submission_attachment.file_name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect preload associations with eager_load' do\n        User.eager_load(:submission_attachment).each { |user| user.submission_attachment.file_name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect preload associations' do\n        User.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload associations' do\n        User.includes(:submission_attachment).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(User, :submission_attachment)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n  end\n\n  describe Bullet::Detector::Association, 'call one association that in possible objects' do\n    it 'should not detect preload association' do\n      Post.all\n      Post.first.comments.map(&:name)\n      Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n      expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n      expect(Bullet::Detector::Association).to be_completely_preloading_associations\n    end\n  end\n\n  describe Bullet::Detector::Association, 'query immediately after creation' do\n    context 'with save' do\n      context 'document => children' do\n        around do |example|\n          document1 = Document.new\n          document1.children.build\n          document1.save\n\n          document2 = Document.new(parent: document1)\n          document2.save\n          document2.parent\n\n          document1.children.each.first\n\n          example.run\n\n          document1.children.destroy_all\n          document1.destroy\n        end\n\n        it 'should not detect non preload associations' do\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n    end\n\n    context 'with save!' do\n      context 'document => children' do\n        around do |example|\n          document1 = Document.new\n          document1.children.build\n          document1.save!\n\n          document2 = Document.new(parent: document1)\n          document2.save!\n          document2.parent\n\n          document1.children.each.first\n\n          example.run\n\n          document1.children.destroy_all\n          document1.destroy\n        end\n\n        it 'should not detect non preload associations' do\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n    end\n  end\n\n  describe Bullet::Detector::Association, 'STI' do\n    context 'page => author' do\n      it 'should detect non preload associations' do\n        Page.all.each { |page| page.author.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Page, :author)\n      end\n\n      it 'should detect preload associations' do\n        Page.includes(:author).each { |page| page.author.name }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should detect unused preload associations' do\n        Page.includes(:author).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Page, :author)\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n\n      it 'should not detect preload associations' do\n        Page.all.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n\n    context 'disable n plus one query' do\n      before { Bullet.n_plus_one_query_enable = false }\n      after { Bullet.n_plus_one_query_enable = true }\n\n      it 'should not detect n plus one query' do\n        Post.all.each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).not_to be_detecting_unpreloaded_association_for(Post, :comments)\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n      end\n\n      it 'should still detect unused eager loading' do\n        Post.includes(:comments).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)\n      end\n    end\n\n    context 'disable unused eager loading' do\n      before { Bullet.unused_eager_loading_enable = false }\n      after { Bullet.unused_eager_loading_enable = true }\n\n      it 'should not detect unused eager loading' do\n        Post.includes(:comments).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n      end\n\n      it 'should still detect n plus one query' do\n        Post.all.each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n      end\n    end\n\n    context 'add n plus one query to safelist' do\n      before { Bullet.add_safelist type: :n_plus_one_query, class_name: 'Post', association: :comments }\n      after { Bullet.clear_safelist }\n\n      it 'should not detect n plus one query' do\n        Post.all.each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).not_to be_detecting_unpreloaded_association_for(Post, :comments)\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n      end\n\n      it 'should still detect unused eager loading' do\n        Post.includes(:comments).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)\n      end\n    end\n\n    context 'add unused eager loading to safelist' do\n      before { Bullet.add_safelist type: :unused_eager_loading, class_name: 'Post', association: :comments }\n      after { Bullet.clear_safelist }\n\n      it 'should not detect unused eager loading' do\n        Post.includes(:comments).map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n      end\n\n      it 'should still detect n plus one query' do\n        Post.all.each { |post| post.comments.map(&:name) }\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/counter_cache_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nif !mongoid? && active_record?\n  describe Bullet::Detector::CounterCache do\n    before(:each) { Bullet.start_request }\n\n    after(:each) { Bullet.end_request }\n\n    it 'should need counter cache with all cities' do\n      Country.all.each { |country| country.cities.size }\n      expect(Bullet.collected_counter_cache_notifications).not_to be_empty\n    end\n\n    it 'should not need counter cache if already define counter_cache' do\n      Person.all.each { |person| person.pets.size }\n      expect(Bullet.collected_counter_cache_notifications).to be_empty\n    end\n\n    it 'should not need counter cache with only one object' do\n      Country.first.cities.size\n      expect(Bullet.collected_counter_cache_notifications).to be_empty\n    end\n\n    it 'should not need counter cache without size' do\n      Country.includes(:cities).each { |country| country.cities.empty? }\n      expect(Bullet.collected_counter_cache_notifications).to be_empty\n    end\n\n    if ActiveRecord::VERSION::MAJOR > 4\n      it 'should not need counter cache for has_many through' do\n        Client.all.each { |client| client.firms.size }\n        expect(Bullet.collected_counter_cache_notifications).to be_empty\n      end\n    else\n      it 'should need counter cache for has_many through' do\n        Client.all.each { |client| client.firms.size }\n        expect(Bullet.collected_counter_cache_notifications).not_to be_empty\n      end\n    end\n\n    it 'should not need counter cache with part of cities' do\n      Country.all.each { |country| country.cities.where(name: 'first').size }\n      expect(Bullet.collected_counter_cache_notifications).to be_empty\n    end\n\n    context 'disable' do\n      before { Bullet.counter_cache_enable = false }\n      after { Bullet.counter_cache_enable = true }\n\n      it 'should not detect counter cache' do\n        Country.all.each { |country| country.cities.size }\n        expect(Bullet.collected_counter_cache_notifications).to be_empty\n      end\n    end\n\n    context 'safelist' do\n      before { Bullet.add_safelist type: :counter_cache, class_name: 'Country', association: :cities }\n      after { Bullet.clear_safelist }\n\n      it 'should not detect counter cache' do\n        Country.all.each { |country| country.cities.size }\n        expect(Bullet.collected_counter_cache_notifications).to be_empty\n      end\n    end\n\n    describe 'with count' do\n      it 'should need counter cache' do\n        Country.all.each { |country| country.cities.count }\n        expect(Bullet.collected_counter_cache_notifications).not_to be_empty\n      end\n\n      it 'should notify even with counter cache' do\n        Person.all.each { |person| person.pets.count }\n        expect(Bullet.collected_counter_cache_notifications).not_to be_empty\n      end\n\n      if ActiveRecord::VERSION::MAJOR > 4\n        it 'should not need counter cache for has_many through' do\n          Client.all.each { |client| client.firms.count }\n          expect(Bullet.collected_counter_cache_notifications).to be_empty\n        end\n      else\n        it 'should need counter cache for has_many through' do\n          Client.all.each { |client| client.firms.count }\n          expect(Bullet.collected_counter_cache_notifications).not_to be_empty\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/mongoid/association_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'spec_helper'\n\nif mongoid?\n  describe Bullet::Detector::Association do\n    context 'embeds_many' do\n      context 'posts => users' do\n        it 'should detect nothing' do\n          Mongoid::Post.all.each { |post| post.users.map(&:name) }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n    end\n\n    context 'has_many' do\n      context 'posts => comments' do\n        it 'should detect non preload posts => comments' do\n          Mongoid::Post.all.each { |post| post.comments.map(&:name) }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Mongoid::Post, :comments)\n        end\n\n        it 'should detect preload post => comments' do\n          Mongoid::Post.includes(:comments).each { |post| post.comments.map(&:name) }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect unused preload post => comments' do\n          Mongoid::Post.includes(:comments).map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Mongoid::Post, :comments)\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should not detect unused preload post => comments' do\n          Mongoid::Post.all.map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n\n      context 'category => posts, category => entries' do\n        it 'should detect non preload with category => [posts, entries]' do\n          Mongoid::Category.all.each do |category|\n            category.posts.map(&:name)\n            category.entries.map(&:name)\n          end\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Mongoid::Category, :posts)\n          expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Mongoid::Category, :entries)\n        end\n\n        it 'should detect preload with category => posts, but not with category => entries' do\n          Mongoid::Category.includes(:posts).each do |category|\n            category.posts.map(&:name)\n            category.entries.map(&:name)\n          end\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).not_to be_detecting_unpreloaded_association_for(\n            Mongoid::Category,\n            :posts\n          )\n          expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Mongoid::Category, :entries)\n        end\n\n        it 'should detect preload with category => [posts, entries]' do\n          Mongoid::Category.includes(:posts, :entries).each do |category|\n            category.posts.map(&:name)\n            category.entries.map(&:name)\n          end\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect unused preload with category => [posts, entries]' do\n          Mongoid::Category.includes(:posts, :entries).map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Mongoid::Category, :posts)\n          expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Mongoid::Category, :entries)\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect unused preload with category => entries, but not with category => posts' do\n          Mongoid::Category.includes(:posts, :entries).each { |category| category.posts.map(&:name) }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Mongoid::Category, :posts)\n          expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Mongoid::Category, :entries)\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n\n      context 'post => comment' do\n        it 'should detect unused preload with post => comments' do\n          Mongoid::Post.includes(:comments).each { |post| post.comments.first.name }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Mongoid::Post, :comments)\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect preload with post => comments' do\n          Mongoid::Post.first.comments.map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n\n      context 'scope preload_comments' do\n        it 'should detect preload post => comments with scope' do\n          Mongoid::Post.preload_comments.each { |post| post.comments.map(&:name) }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect unused preload with scope' do\n          Mongoid::Post.preload_comments.map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Mongoid::Post, :comments)\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n    end\n\n    context 'belongs_to' do\n      context 'comment => post' do\n        it 'should detect non preload with comment => post' do\n          Mongoid::Comment.all.each { |comment| comment.post.name }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Mongoid::Comment, :post)\n        end\n\n        it 'should detect preload with one comment => post' do\n          Mongoid::Comment.first.post.name\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect preload with comment => post' do\n          Mongoid::Comment.includes(:post).each { |comment| comment.post.name }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should not detect preload with comment => post' do\n          Mongoid::Comment.all.map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect unused preload with comments => post' do\n          Mongoid::Comment.includes(:post).map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Mongoid::Comment, :post)\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n    end\n\n    context 'has_one' do\n      context 'company => address' do\n        if Mongoid::VERSION !~ /\\A3.0/\n          it 'should detect non preload association' do\n            Mongoid::Company.all.each { |company| company.address.name }\n            Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n            expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n            expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(\n              Mongoid::Company,\n              :address\n            )\n          end\n        end\n\n        it 'should detect preload association' do\n          Mongoid::Company.includes(:address).each { |company| company.address.name }\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should not detect preload association' do\n          Mongoid::Company.all.map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n\n        it 'should detect unused preload association' do\n          criteria = Mongoid::Company.includes(:address)\n          criteria.map(&:name)\n          Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n          expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Mongoid::Company, :address)\n\n          expect(Bullet::Detector::Association).to be_completely_preloading_associations\n        end\n      end\n    end\n\n    context 'call one association that in possible objects' do\n      it 'should not detect preload association' do\n        Mongoid::Post.all\n        Mongoid::Post.first.comments.map(&:name)\n        Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations\n        expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations\n\n        expect(Bullet::Detector::Association).to be_completely_preloading_associations\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/address.rb",
    "content": "# frozen_string_literal: true\n\nclass Address < ActiveRecord::Base\n  belongs_to :company\nend\n"
  },
  {
    "path": "spec/models/attachment.rb",
    "content": "# frozen_string_literal: true\n\nclass Attachment < ActiveRecord::Base\n  belongs_to :submission\nend\n"
  },
  {
    "path": "spec/models/author.rb",
    "content": "# frozen_string_literal: true\n\nclass Author < ActiveRecord::Base\n  has_many :documents\nend\n"
  },
  {
    "path": "spec/models/base_user.rb",
    "content": "# frozen_string_literal: true\n\nclass BaseUser < ActiveRecord::Base\n  has_many :comments\n  has_many :posts\n  belongs_to :newspaper\nend\n"
  },
  {
    "path": "spec/models/category.rb",
    "content": "# frozen_string_literal: true\n\nclass Category < ActiveRecord::Base\n  has_many :posts, inverse_of: :category\n  has_many :entries\n\n  has_many :users\n\n  def draft_post\n    posts.draft.first_or_create\n  end\nend\n"
  },
  {
    "path": "spec/models/city.rb",
    "content": "# frozen_string_literal: true\n\nclass City < ActiveRecord::Base\n  belongs_to :country\nend\n"
  },
  {
    "path": "spec/models/client.rb",
    "content": "# frozen_string_literal: true\n\nclass Client < ActiveRecord::Base\n  belongs_to :group\n\n  has_many :relationships\n  has_many :firms, through: :relationships\nend\n"
  },
  {
    "path": "spec/models/comment.rb",
    "content": "# frozen_string_literal: true\n\nclass Comment < ActiveRecord::Base\n  belongs_to :post, inverse_of: :comments\n  belongs_to :author, class_name: 'BaseUser'\n\n  validates :post, presence: true\nend\n"
  },
  {
    "path": "spec/models/company.rb",
    "content": "# frozen_string_literal: true\n\nclass Company < ActiveRecord::Base\n  has_one :address\nend\n"
  },
  {
    "path": "spec/models/country.rb",
    "content": "# frozen_string_literal: true\n\nclass Country < ActiveRecord::Base\n  has_many :cities\nend\n"
  },
  {
    "path": "spec/models/deal.rb",
    "content": "# frozen_string_literal: true\n\nclass Deal < ActiveRecord::Base\n  has_and_belongs_to_many :posts\nend\n"
  },
  {
    "path": "spec/models/document.rb",
    "content": "# frozen_string_literal: true\n\nclass Document < ActiveRecord::Base\n  has_many :children, class_name: 'Document', foreign_key: 'parent_id'\n  belongs_to :parent, class_name: 'Document', foreign_key: 'parent_id'\n  belongs_to :author\nend\n"
  },
  {
    "path": "spec/models/entry.rb",
    "content": "# frozen_string_literal: true\n\nclass Entry < ActiveRecord::Base\n  belongs_to :category\nend\n"
  },
  {
    "path": "spec/models/firm.rb",
    "content": "# frozen_string_literal: true\n\nclass Firm < ActiveRecord::Base\n  has_many :relationships\n  has_many :clients, through: :relationships\n  has_many :groups, through: :clients\nend\n"
  },
  {
    "path": "spec/models/folder.rb",
    "content": "# frozen_string_literal: true\n\nclass Folder < Document\nend\n"
  },
  {
    "path": "spec/models/group.rb",
    "content": "# frozen_string_literal: true\n\nclass Group < ActiveRecord::Base\nend\n"
  },
  {
    "path": "spec/models/mongoid/address.rb",
    "content": "# frozen_string_literal: true\n\nclass Mongoid::Address\n  include Mongoid::Document\n\n  field :name\n\n  belongs_to :company, class_name: 'Mongoid::Company'\nend\n"
  },
  {
    "path": "spec/models/mongoid/category.rb",
    "content": "# frozen_string_literal: true\n\nclass Mongoid::Category\n  include Mongoid::Document\n\n  field :name\n\n  has_many :posts, class_name: 'Mongoid::Post'\n  has_many :entries, class_name: 'Mongoid::Entry'\nend\n"
  },
  {
    "path": "spec/models/mongoid/comment.rb",
    "content": "# frozen_string_literal: true\n\nclass Mongoid::Comment\n  include Mongoid::Document\n\n  field :name\n\n  belongs_to :post, class_name: 'Mongoid::Post'\nend\n"
  },
  {
    "path": "spec/models/mongoid/company.rb",
    "content": "# frozen_string_literal: true\n\nclass Mongoid::Company\n  include Mongoid::Document\n\n  field :name\n\n  has_one :address, class_name: 'Mongoid::Address'\nend\n"
  },
  {
    "path": "spec/models/mongoid/entry.rb",
    "content": "# frozen_string_literal: true\n\nclass Mongoid::Entry\n  include Mongoid::Document\n\n  field :name\n\n  belongs_to :category, class_name: 'Mongoid::Category'\nend\n"
  },
  {
    "path": "spec/models/mongoid/post.rb",
    "content": "# frozen_string_literal: true\n\nclass Mongoid::Post\n  include Mongoid::Document\n\n  field :name\n\n  has_many :comments, class_name: 'Mongoid::Comment'\n  belongs_to :category, class_name: 'Mongoid::Category'\n\n  embeds_many :users, class_name: 'Mongoid::User'\n\n  scope :preload_comments, -> { includes(:comments) }\nend\n"
  },
  {
    "path": "spec/models/mongoid/user.rb",
    "content": "# frozen_string_literal: true\n\nclass Mongoid::User\n  include Mongoid::Document\n\n  field :name\nend\n"
  },
  {
    "path": "spec/models/newspaper.rb",
    "content": "# frozen_string_literal: true\n\nclass Newspaper < ActiveRecord::Base\n  has_many :writers, class_name: 'BaseUser'\nend\n"
  },
  {
    "path": "spec/models/page.rb",
    "content": "# frozen_string_literal: true\n\nclass Page < Document\nend\n"
  },
  {
    "path": "spec/models/person.rb",
    "content": "# frozen_string_literal: true\n\nclass Person < ActiveRecord::Base\n  has_many :pets\nend\n"
  },
  {
    "path": "spec/models/pet.rb",
    "content": "# frozen_string_literal: true\n\nclass Pet < ActiveRecord::Base\n  belongs_to :person, counter_cache: true\nend\n"
  },
  {
    "path": "spec/models/post.rb",
    "content": "# frozen_string_literal: true\n\nclass Post < ActiveRecord::Base\n  belongs_to :category, inverse_of: :posts\n  belongs_to :writer\n  has_many :comments, inverse_of: :post\n  has_and_belongs_to_many :deals\n\n  validates :category, presence: true\n\n  scope :preload_comments, -> { includes(:comments) }\n  scope :in_category_name, ->(name) { where(['categories.name = ?', name]).includes(:category) }\n  scope :draft, -> { where(active: false) }\n\n  def link=(*)\n    comments.new\n  end\n\n  # see association_spec.rb 'should not detect newly assigned object in an after_save'\n  attr_accessor :trigger_after_save\n\n  after_save do\n    next unless trigger_after_save\n\n    temp_comment = Comment.new(post: self)\n\n    # this triggers self to be \"possible\", even though it's\n    # not saved yet\n    temp_comment.post\n\n    # category should NOT whine about not being pre-loaded, because\n    # it's obviously attached to a new object\n    category\n  end\nend\n"
  },
  {
    "path": "spec/models/relationship.rb",
    "content": "# frozen_string_literal: true\n\nclass Relationship < ActiveRecord::Base\n  belongs_to :firm\n  belongs_to :client\nend\n"
  },
  {
    "path": "spec/models/reply.rb",
    "content": "# frozen_string_literal: true\n\nclass Reply < ActiveRecord::Base\n  belongs_to :submission\nend\n"
  },
  {
    "path": "spec/models/role.rb",
    "content": "# frozen_string_literal: true\n\nclass Role < ActiveRecord::Base\n  has_and_belongs_to_many :users\n\n  belongs_to :resource, polymorphic: true\nend\n"
  },
  {
    "path": "spec/models/student.rb",
    "content": "# frozen_string_literal: true\n\nclass Student < ActiveRecord::Base\n  has_and_belongs_to_many :teachers\nend\n"
  },
  {
    "path": "spec/models/submission.rb",
    "content": "# frozen_string_literal: true\n\nclass Submission < ActiveRecord::Base\n  belongs_to :user\n  has_many :replies\n  has_one :attachment\nend\n"
  },
  {
    "path": "spec/models/teacher.rb",
    "content": "# frozen_string_literal: true\n\nclass Teacher < ActiveRecord::Base\n  has_and_belongs_to_many :students\nend\n"
  },
  {
    "path": "spec/models/user.rb",
    "content": "# frozen_string_literal: true\n\nclass User < ActiveRecord::Base\n  has_one :submission\n  has_one :submission_attachment, through: :submission, source: :attachment, class_name: 'Attachment'\n  belongs_to :category\n  has_and_belongs_to_many :roles\nend\n"
  },
  {
    "path": "spec/models/writer.rb",
    "content": "# frozen_string_literal: true\n\nclass Writer < BaseUser\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'rspec'\nrequire 'logger'\nbegin\n  require 'active_record'\nrescue LoadError\nend\nbegin\n  require 'mongoid'\nrescue LoadError\nend\n\nmodule Rails\n  class << self\n    def root\n      File.expand_path(__FILE__).split('/')[0..-3].join('/')\n    end\n\n    def env\n      'test'\n    end\n  end\nend\n\n$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))\nrequire 'bullet'\nextend Bullet::Dependency\nBullet.enable = true\n\nMODELS = File.join(File.dirname(__FILE__), 'models')\n$LOAD_PATH.unshift(MODELS)\nSUPPORT = File.join(File.dirname(__FILE__), 'support')\nDir[File.join(SUPPORT, '*.rb')].reject { |filename| filename =~ /_seed.rb$/ }\n                               .sort.each { |file| require file }\n\nRSpec.configure do |config|\n  config.extend Bullet::Dependency\n\n  config.filter_run focus: true\n  config.run_all_when_everything_filtered = true\nend\n\nif active_record?\n  ActiveRecord::Migration.verbose = false\n\n  # Autoload every active_record model for the test suite that sits in spec/models.\n  Dir[File.join(MODELS, '*.rb')].sort.each do |filename|\n    name = File.basename(filename, '.rb')\n    autoload name.camelize.to_sym, name\n  end\n  require File.join(SUPPORT, 'sqlite_seed.rb')\n\n  RSpec.configure do |config|\n    config.before(:suite) do\n      Support::SqliteSeed.setup_db\n      Support::SqliteSeed.seed_db\n    end\n\n    config.before(:example) do\n      Bullet.start_request\n      Bullet.enable = true\n    end\n\n    config.after(:example) { Bullet.end_request }\n  end\n\n  if ENV['BULLET_LOG']\n    require 'logger'\n    ActiveRecord::Base.logger = Logger.new(STDOUT)\n  end\nend\n\nif mongoid?\n  # Autoload every mongoid model for the test suite that sits in spec/models.\n  Dir[File.join(MODELS, 'mongoid', '*.rb')].sort.each { |file| require file }\n  require File.join(SUPPORT, 'mongo_seed.rb')\n\n  RSpec.configure do |config|\n    config.before(:suite) do\n      Support::MongoSeed.setup_db\n      Support::MongoSeed.seed_db\n    end\n\n    config.after(:suite) do\n      Support::MongoSeed.setup_db\n      Support::MongoSeed.teardown_db\n    end\n\n    config.before(:each) { Bullet.start_request }\n\n    config.after(:each) { Bullet.end_request }\n  end\n\n  if ENV['BULLET_LOG']\n    Mongoid.logger = Logger.new(STDOUT)\n    Moped.logger = Logger.new(STDOUT)\n  end\nend\n"
  },
  {
    "path": "spec/support/bullet_ext.rb",
    "content": "# frozen_string_literal: true\n\nusing Bullet::Ext::Object\n\nmodule Bullet\n  def self.collected_notifications_of_class(notification_class)\n    Bullet.notification_collector.collection.select { |notification| notification.is_a? notification_class }\n  end\n\n  def self.collected_counter_cache_notifications\n    collected_notifications_of_class Bullet::Notification::CounterCache\n  end\n\n  def self.collected_n_plus_one_query_notifications\n    collected_notifications_of_class Bullet::Notification::NPlusOneQuery\n  end\n\n  def self.collected_unused_eager_association_notifications\n    collected_notifications_of_class Bullet::Notification::UnusedEagerLoading\n  end\nend\n\nmodule Bullet\n  module Detector\n    class Association\n      class << self\n        # returns true if all associations are preloaded\n        def completely_preloading_associations?\n          Bullet.collected_n_plus_one_query_notifications.empty?\n        end\n\n        def has_unused_preload_associations?\n          Bullet.collected_unused_eager_association_notifications.present?\n        end\n\n        # returns true if a given object has a specific association\n        def creating_object_association_for?(object, association)\n          object_associations[object.bullet_key].present? &&\n            object_associations[object.bullet_key].include?(association)\n        end\n\n        # returns true if a given class includes the specific unpreloaded association\n        def detecting_unpreloaded_association_for?(klass, association)\n          Bullet.collected_n_plus_one_query_notifications.select do |notification|\n            notification.base_class == klass.to_s && notification.associations.include?(association)\n          end.present?\n        end\n\n        # returns true if the given class includes the specific unused preloaded association\n        def unused_preload_associations_for?(klass, association)\n          Bullet.collected_unused_eager_association_notifications.select do |notification|\n            notification.base_class == klass.to_s && notification.associations.include?(association)\n          end.present?\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/mongo_seed.rb",
    "content": "# frozen_string_literal: true\n\nmodule Support\n  module MongoSeed\n    module_function\n\n    def seed_db\n      category1 = Mongoid::Category.create(name: 'first')\n      category2 = Mongoid::Category.create(name: 'second')\n\n      post1 = category1.posts.create(name: 'first')\n      post1a = category1.posts.create(name: 'like first')\n      post2 = category2.posts.create(name: 'second')\n\n      post1.users << Mongoid::User.create(name: 'first')\n      post1.users << Mongoid::User.create(name: 'another')\n      post2.users << Mongoid::User.create(name: 'second')\n\n      comment1 = post1.comments.create(name: 'first')\n      comment2 = post1.comments.create(name: 'first2')\n      comment3 = post1.comments.create(name: 'first3')\n      comment4 = post1.comments.create(name: 'second')\n      comment8 = post1a.comments.create(name: 'like first 1')\n      comment9 = post1a.comments.create(name: 'like first 2')\n      comment5 = post2.comments.create(name: 'third')\n      comment6 = post2.comments.create(name: 'fourth')\n      comment7 = post2.comments.create(name: 'fourth')\n\n      entry1 = category1.entries.create(name: 'first')\n      entry2 = category1.entries.create(name: 'second')\n\n      company1 = Mongoid::Company.create(name: 'first')\n      company2 = Mongoid::Company.create(name: 'second')\n\n      Mongoid::Address.create(name: 'first', company: company1)\n      Mongoid::Address.create(name: 'second', company: company2)\n    end\n\n    def setup_db\n      if Mongoid::VERSION =~ /\\A4/\n        Mongoid.configure do |config|\n          config.load_configuration(sessions: { default: { database: 'bullet', hosts: %w[localhost:27017] } })\n        end\n      else\n        if %w[7.1 7.2 7.3 7.4 7.5 8 8.1 9.0].any? { |version| Mongoid::VERSION =~ /\\A#{Regexp.quote(version)}/ }\n          Mongoid.logger =\n            Logger.new(STDERR).tap do |logger|\n              logger.level = Logger::WARN\n            end\n        end\n\n        Mongoid.configure do |config|\n          config.load_configuration(clients: { default: { database: 'bullet', hosts: %w[localhost:27017] } })\n        end\n\n        # Increase the level from DEBUG in order to avoid excessive logging to the screen\n        Mongo::Logger.logger.level = Logger::WARN\n      end\n    end\n\n    def teardown_db\n      Mongoid.purge!\n      Mongoid::IdentityMap.clear if Mongoid.const_defined?(:IdentityMap)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/rack_double.rb",
    "content": "# frozen_string_literal: true\n\nmodule Support\n  class AppDouble\n    def call(_env)\n      env = @env\n      [status, headers, response]\n    end\n\n    attr_writer :status\n\n    attr_writer :headers\n\n    def headers\n      @headers ||= { 'Content-Type' => 'text/html' }\n      @headers\n    end\n\n    attr_writer :response\n\n    private\n\n    def status\n      @status || 200\n    end\n\n    def response\n      @response || ResponseDouble.new\n    end\n  end\n\n  class ResponseDouble\n    def initialize(actual_body = nil)\n      @actual_body = actual_body\n    end\n\n    def body\n      @body ||= '<html><head></head><body></body></html>'\n    end\n\n    attr_writer :body\n\n    def each\n      yield body\n    end\n\n    def close; end\n  end\nend\n"
  },
  {
    "path": "spec/support/sqlite_seed.rb",
    "content": "# frozen_string_literal: true\n\nmodule Support\n  module SqliteSeed\n    module_function\n\n    def seed_db\n      newspaper1 = Newspaper.create(name: 'First Newspaper')\n      newspaper2 = Newspaper.create(name: 'Second Newspaper')\n\n      writer1 = Writer.create(name: 'first', newspaper: newspaper1)\n      writer2 = Writer.create(name: 'second', newspaper: newspaper2)\n      user1 = BaseUser.create(name: 'third', newspaper: newspaper1)\n      user2 = BaseUser.create(name: 'fourth', newspaper: newspaper2)\n\n      category1 = Category.create(name: 'first')\n      category2 = Category.create(name: 'second')\n\n      post1 = category1.posts.create(name: 'first', writer: writer1)\n      post1a = category1.posts.create(name: 'like first', writer: writer2, active: false)\n      post2 = category2.posts.create(name: 'second', writer: writer2)\n      post3 = category2.posts.create(name: 'third', writer: writer2)\n\n      deal1 = Deal.new(name: 'Deal 1')\n      deal1.posts << post1\n      deal1.posts << post2\n      deal2 = Deal.new(name: 'Deal 2')\n      post1.deals << deal1\n      post1.deals << deal2\n\n      comment1 = post1.comments.create(name: 'first', author: writer1)\n      comment2 = post1.comments.create(name: 'first2', author: writer1)\n      comment3 = post1.comments.create(name: 'first3', author: writer1)\n      comment4 = post1.comments.create(name: 'second', author: writer2)\n      comment8 = post1a.comments.create(name: 'like first 1', author: writer1)\n      comment9 = post1a.comments.create(name: 'like first 2', author: writer2)\n      comment5 = post2.comments.create(name: 'third', author: user1)\n      comment6 = post2.comments.create(name: 'fourth', author: user2)\n      comment7 = post2.comments.create(name: 'fourth', author: writer1)\n\n      entry1 = category1.entries.create(name: 'first')\n      entry2 = category1.entries.create(name: 'second')\n\n      student1 = Student.create(name: 'first')\n      student2 = Student.create(name: 'second')\n      teacher1 = Teacher.create(name: 'first')\n      teacher2 = Teacher.create(name: 'second')\n      student1.teachers = [teacher1, teacher2]\n      student2.teachers = [teacher1, teacher2]\n      teacher1.students << student1\n      teacher2.students << student2\n\n      firm1 = Firm.create(name: 'first')\n      firm2 = Firm.create(name: 'second')\n      group1 = Group.create(name: 'first')\n      group2 = Group.create(name: 'second')\n      client1 = Client.create(name: 'first', group: group1)\n      client2 = Client.create(name: 'second', group: group2)\n      firm1.clients = [client1, client2]\n      firm2.clients = [client1, client2]\n      client1.firms << firm1\n      client2.firms << firm2\n\n      company1 = Company.create(name: 'first')\n      company2 = Company.create(name: 'second')\n\n      Address.create(name: 'first', company: company1)\n      Address.create(name: 'second', company: company2)\n\n      country1 = Country.create(name: 'first')\n      country2 = Country.create(name: 'second')\n\n      country1.cities.create(name: 'first')\n      country1.cities.create(name: 'second')\n      country2.cities.create(name: 'third')\n      country2.cities.create(name: 'fourth')\n\n      person1 = Person.create(name: 'first')\n      person2 = Person.create(name: 'second')\n\n      person1.pets.create(name: 'first')\n      person1.pets.create(name: 'second')\n      person2.pets.create(name: 'third')\n      person2.pets.create(name: 'fourth')\n\n      author1 = Author.create(name: 'author1')\n      author2 = Author.create(name: 'author2')\n      folder1 = Folder.create(name: 'folder1', author_id: author1.id)\n      folder2 = Folder.create(name: 'folder2', author_id: author2.id)\n      page1 = Page.create(name: 'page1', parent_id: folder1.id, author_id: author1.id)\n      page2 = Page.create(name: 'page2', parent_id: folder1.id, author_id: author1.id)\n      page3 = Page.create(name: 'page3', parent_id: folder2.id, author_id: author2.id)\n      page4 = Page.create(name: 'page4', parent_id: folder2.id, author_id: author2.id)\n\n      role1 = Role.create(name: 'Admin')\n      role2 = Role.create(name: 'User')\n\n      user1 = User.create(name: 'user1', category: category1)\n      user2 = User.create(name: 'user2', category: category1)\n\n      user1.roles << role1\n      user1.roles << role2\n      user2.roles << role2\n\n      submission1 = user1.create_submission(name: 'submission1')\n      submission2 = user2.create_submission(name: 'submission2')\n\n      submission1.replies.create(name: 'reply1')\n      submission1.replies.create(name: 'reply2')\n      submission2.replies.create(name: 'reply3')\n      submission2.replies.create(name: 'reply4')\n\n      submission1.create_attachment(file_name: 'submission1 file')\n      submission2.create_attachment(file_name: 'submission2 file')\n    end\n\n    def setup_db\n      ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')\n\n      ActiveRecord::Schema.define(version: 1) do\n        create_table :addresses do |t|\n          t.column :name, :string\n          t.column :company_id, :integer\n        end\n\n        create_table :authors do |t|\n          t.string :name\n        end\n\n        create_table :base_users do |t|\n          t.column :name, :string\n          t.column :type, :string\n          t.column :newspaper_id, :integer\n        end\n\n        create_table :categories do |t|\n          t.column :name, :string\n        end\n\n        create_table :cities do |t|\n          t.string :name\n          t.integer :country_id\n        end\n\n        create_table :clients do |t|\n          t.column :name, :string\n          t.column :group_id, :integer\n        end\n\n        create_table :comments do |t|\n          t.column :name, :string\n          t.column :post_id, :integer\n          t.column :author_id, :integer\n        end\n\n        create_table :companies do |t|\n          t.column :name, :string\n        end\n\n        create_table :contacts do |t|\n          t.column :name, :string\n        end\n\n        create_table :countries do |t|\n          t.string :name\n        end\n\n        create_table :deals do |t|\n          t.column :name, :string\n          t.column :hotel_id, :integer\n        end\n\n        create_table :deals_posts do |t|\n          t.column :deal_id, :integer\n          t.column :post_id, :integer\n        end\n\n        create_table :documents do |t|\n          t.string :name\n          t.string :type\n          t.integer :parent_id\n          t.integer :author_id\n        end\n\n        create_table :emails do |t|\n          t.column :name, :string\n          t.column :contact_id, :integer\n        end\n\n        create_table :entries do |t|\n          t.column :name, :string\n          t.column :category_id, :integer\n        end\n\n        create_table :firms do |t|\n          t.column :name, :string\n        end\n\n        create_table :groups do |t|\n          t.column :name, :string\n        end\n\n        create_table :hotels do |t|\n          t.column :name, :string\n          t.column :location_id, :integer\n        end\n\n        create_table :locations do |t|\n          t.column :name, :string\n        end\n\n        create_table :newspapers do |t|\n          t.column :name, :string\n        end\n\n        create_table :people do |t|\n          t.string :name\n          t.integer :pets_count\n        end\n\n        create_table :pets do |t|\n          t.string :name\n          t.integer :person_id\n        end\n\n        create_table :posts do |t|\n          t.column :name, :string\n          t.column :category_id, :integer\n          t.column :writer_id, :integer\n          t.column :active, :boolean, default: true\n        end\n\n        create_table :relationships do |t|\n          t.column :firm_id, :integer\n          t.column :client_id, :integer\n        end\n\n        create_table :students do |t|\n          t.column :name, :string\n        end\n\n        create_table :students_teachers, id: false do |t|\n          t.column :student_id, :integer\n          t.column :teacher_id, :integer\n        end\n\n        create_table :teachers do |t|\n          t.column :name, :string\n        end\n\n        create_table :replies do |t|\n          t.column :name, :string\n          t.column :submission_id, :integer\n        end\n\n        create_table :roles do |t|\n          t.column :name, :string\n          t.column :resource_id, :integer\n          t.column :resource_type, :string\n        end\n\n        create_table :roles_users do |t|\n          t.column :role_id, :integer\n          t.column :user_id, :integer\n        end\n\n        create_table :submissions do |t|\n          t.column :name, :string\n          t.column :user_id, :integer\n        end\n\n        create_table :users do |t|\n          t.column :name, :string\n          t.column :category_id, :integer\n        end\n\n        create_table :attachments do |t|\n          t.column :file_name, :string\n          t.column :submission_id, :integer\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "tasks/bullet_tasks.rake",
    "content": "# frozen_string_literal: true\n\nnamespace :bullet do\n  namespace :log do\n    desc 'Truncates the bullet log file to zero bytes'\n    task :clear do\n      f = File.open('log/bullet.log', 'w')\n      f.close\n    end\n  end\nend\n"
  },
  {
    "path": "test.sh",
    "content": "#bundle update rails && bundle exec rspec spec\n#BUNDLE_GEMFILE=Gemfile.mongoid bundle update mongoid && BUNDLE_GEMFILE=Gemfile.mongoid bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-8.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-8.1 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-8.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-8.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-7.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-7.2 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-7.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-7.1 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-7.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-7.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-6.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.1 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-6.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-5.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-5.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-5.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-4.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-4.2 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-4.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-4.1 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.rails-4.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-4.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.mongoid-9.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-9.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.mongoid-8.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-8.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.mongoid-7.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-7.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.mongoid-6.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-6.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.mongoid-5.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-5.0 bundle exec rspec spec\nBUNDLE_GEMFILE=Gemfile.mongoid-4.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-4.0 bundle exec rspec spec\n"
  },
  {
    "path": "update.sh",
    "content": "BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle update\nBUNDLE_GEMFILE=Gemfile.rails-5.1 bundle update\nBUNDLE_GEMFILE=Gemfile.rails-5.0 bundle update\nBUNDLE_GEMFILE=Gemfile.rails-4.2 bundle update\nBUNDLE_GEMFILE=Gemfile.rails-4.1 bundle update\nBUNDLE_GEMFILE=Gemfile.rails-4.0 bundle update\nBUNDLE_GEMFILE=Gemfile.mongoid-9.0 bundle update\nBUNDLE_GEMFILE=Gemfile.mongoid-8.0 bundle update\nBUNDLE_GEMFILE=Gemfile.mongoid-7.0 bundle update\nBUNDLE_GEMFILE=Gemfile.mongoid-6.0 bundle update\nBUNDLE_GEMFILE=Gemfile.mongoid-5.0 bundle update\nBUNDLE_GEMFILE=Gemfile.mongoid-4.0 bundle update\n"
  }
]