-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
188 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,126 @@ | ||
# Rspec::Sqlimit | ||
# Test-Driven way of fighting N + 1 queries | ||
|
||
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/rspec/sqlimit`. To experiment with that code, run `bin/console` for an interactive prompt. | ||
RSpec matcher to control number of SQL queries executed by a block of code. | ||
|
||
TODO: Delete this and the text above, and describe your gem | ||
It wraps [the answer at Stack Overflow][stack-answer] by [Ryan Bigg][ryan-bigg], which based on Active Support [Notification][notification] and [Instrumentation][instrumentation] mechanisms. | ||
|
||
[![Gem Version][gem-badger]][gem] | ||
[![Build Status][travis-badger]][travis] | ||
[![Dependency Status][gemnasium-badger]][gemnasium] | ||
[![Code Climate][codeclimate-badger]][codeclimate] | ||
|
||
<a href="https://evilmartians.com/"> | ||
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a> | ||
|
||
## Installation | ||
|
||
Add this line to your application's Gemfile: | ||
```ruby | ||
# Gemfile | ||
gem "rspec-sqlimit" | ||
``` | ||
|
||
## Usage | ||
|
||
The gem defines matcher `exceed_query_limit` that takes maximum number of SQL requests to be made inside the block. | ||
|
||
```ruby | ||
gem 'rspec-sqlimit' | ||
require "rspec-sqlimit" | ||
|
||
RSpec.describe "N+1 safety" do | ||
it "doesn't send unnecessary requests to db" do | ||
expect { User.create }.not_to exceed_query_limit(1) | ||
end | ||
end | ||
``` | ||
|
||
And then execute: | ||
The above specification fails with the following description: | ||
|
||
$ bundle | ||
``` | ||
Failure/Error: expect { User.create }.not_to exceed_query_limit(1) | ||
Or install it yourself as: | ||
Expected to run maximum 1 queries | ||
The following 3 queries were invoked: | ||
1) begin transaction (0.045 ms) | ||
2) INSERT INTO "users" DEFAULT VALUES (0.19 ms) | ||
3) commit transaction (148.935 ms) | ||
``` | ||
|
||
$ gem install rspec-sqlimit | ||
You can restrict the matcher using regex: | ||
|
||
## Usage | ||
```ruby | ||
require "rspec-sqlimit" | ||
|
||
RSpec.describe "N+1 safety" do | ||
it "doesn't send unnecessary requests to db" do | ||
expect { User.create }.not_to exceed_query_limit(1).with(/^INSERT/) | ||
end | ||
end | ||
``` | ||
|
||
TODO: Write usage instructions here | ||
This time test passes. | ||
|
||
## Development | ||
When a specification with a restriction fails, you'll see an error as follows: | ||
|
||
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. | ||
```ruby | ||
require "rspec-sqlimit" | ||
|
||
RSpec.describe "N+1 safety" do | ||
it "doesn't send unnecessary requests to db" do | ||
expect { User.create }.not_to exceed_query_limit(1).with(/^INSERT/) | ||
end | ||
end | ||
``` | ||
|
||
``` | ||
Failure/Error: expect { User.create }.not_to exceed_query_limit(0).with(/INSERT/) | ||
Expected to run maximum 0 queries that match (?-mix:INSERT) | ||
The following 1 queries were invoked among others (see mark ->): | ||
1) begin transaction (0.072 ms) | ||
-> 2) INSERT INTO "users" DEFAULT VALUES (0.368 ms) | ||
3) commit transaction (147.559 ms) | ||
``` | ||
|
||
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). | ||
## Further Development | ||
|
||
## Contributing | ||
For now the gem uses unbinded Active Record queries in error descriptions. For example, when your query contains arguments, the error message will look like | ||
|
||
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rspec-sqlimit. | ||
```ruby | ||
require "rspec-sqlimit" | ||
|
||
RSpec.describe "N+1 safety" do | ||
it "doesn't send unnecessary requests to db" do | ||
expect { User.create(name: "Joe") }.not_to exceed_query_limit(1) | ||
end | ||
end | ||
``` | ||
|
||
``` | ||
Failure/Error: expect { User.create }.not_to exceed_query_limit(0).with(/INSERT/) | ||
Expected to run maximum 0 queries that match (?-mix:INSERT) | ||
The following 1 queries were invoked among others (see mark ->): | ||
1) begin transaction (0.072 ms) | ||
-> 2) INSERT INTO "users" ("name") VALUES (?) (0.368 ms) | ||
3) commit transaction (147.559 ms) | ||
``` | ||
|
||
This is because [Active Record instrumentation hook][hook] keeps a query and bindings separately (under `:sql` and `:binds` keys). So the challenge is **to bind arguments to the query** in the report to make a debugging a bit simpler. | ||
|
||
## License | ||
|
||
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). | ||
|
||
[codeclimate-badger]: https://img.shields.io/codeclimate/github/nepalez/rspec-sqlimit.svg?style=flat | ||
[codeclimate]: https://codeclimate.com/github/nepalez/rspec-sqlimit | ||
[gem-badger]: https://img.shields.io/gem/v/rspec-sqlimit.svg?style=flat | ||
[gem]: https://rubygems.org/gems/rspec-sqlimit | ||
[gemnasium-badger]: https://img.shields.io/gemnasium/nepalez/rspec-sqlimit.svg?style=flat | ||
[gemnasium]: https://gemnasium.com/nepalez/rspec-sqlimit | ||
[travis-badger]: https://img.shields.io/travis/nepalez/rspec-sqlimit/master.svg?style=flat | ||
[travis]: https://travis-ci.org/nepalez/rspec-sqlimit | ||
[stack-answer]: http://stackoverflow.com/a/5492207/1869912 | ||
[ryan-bigg]: http://ryanbigg.com/ | ||
[notification]: http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html | ||
[instrumentation]: http://guides.rubyonrails.org/active_support_instrumentation.html | ||
[hook]: http://guides.rubyonrails.org/active_support_instrumentation.html#sql-active-record |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,5 @@ | ||
class CreateUsers < ActiveRecord::Migration | ||
def change | ||
create_table :users do |t| | ||
t.string :name, index: true | ||
end | ||
create_table :users | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,31 @@ | ||
require "spec_helper" | ||
|
||
describe RSpec::SQLimit do | ||
it "does something useful" do | ||
expect(false).to eq(true) | ||
describe "exceed_query_limit" do | ||
context "without restrictions" do | ||
it "works when no queries are made" do | ||
expect { User.new }.not_to exceed_query_limit(0) | ||
end | ||
|
||
it "works when actual number of queries is below the limit" do | ||
expect { User.create }.not_to exceed_query_limit(3) | ||
end | ||
|
||
it "works when actual number of queries exceeds the limit" do | ||
expect { User.create }.to exceed_query_limit(2) | ||
end | ||
end | ||
|
||
context "with a restriction" do | ||
it "works when no queries are made" do | ||
expect { User.new }.not_to exceed_query_limit(0).with(/INSERT/) | ||
end | ||
|
||
it "works when actual number of queries is below the limit" do | ||
expect { User.create }.not_to exceed_query_limit(1).with(/INSERT/) | ||
end | ||
|
||
it "works when actual number of queries exceeds the limit" do | ||
expect { User.create id: 3 }.to exceed_query_limit(0).with(/INSERT/) | ||
end | ||
end | ||
end |