Skip to content

Commit 86bac2a

Browse files
committed
initial commit
0 parents  commit 86bac2a

24 files changed

+511
-0
lines changed

.github/FUNDING.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: ezekg

.github/workflows/test.yml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: CI
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
branches:
8+
- master
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
ruby:
15+
- '3.3'
16+
- '3.2'
17+
- '3.1'
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
- name: Install
22+
uses: ruby/setup-ruby@v1
23+
with:
24+
ruby-version: ${{matrix.ruby}}
25+
bundler-cache: true
26+
- name: Test
27+
run: bundle exec rake test

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/.bundle/
2+
/doc/
3+
/log/*.log
4+
/pkg/
5+
/tmp/
6+
/test/dummy/db/*.sqlite3
7+
/test/dummy/db/*.sqlite3-*
8+
/test/dummy/log/*.log
9+
/test/dummy/storage/
10+
/test/dummy/tmp/
11+
.rspec_status
12+
Gemfile.lock
13+
*.gem
14+
*.log

.rspec

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--format documentation
2+
--color
3+
--require spec_helper

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Changelog
2+
3+
## [Unreleased]

CONTRIBUTING.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
## Security issues
2+
3+
If you have found a security related issue, please do not file an issue on
4+
GitHub or send a PR addressing the issue. Contact [Keygen](mailto:[email protected])
5+
directly. You will be given public credit for your disclosure.
6+
7+
## Reporting issues
8+
9+
Please try to answer the following questions in your bug report:
10+
11+
- What did you do?
12+
- What did you expect to happen?
13+
- What happened instead?
14+
15+
Make sure to include as much relevant information as possible. Ruby version,
16+
Rails version, `sql_matchers` version, OS version and any stack traces
17+
you have are very valuable.
18+
19+
## Pull Requests
20+
21+
- **Add tests!** Your patch won't be accepted if it doesn't have tests.
22+
23+
- **Document any change in behaviour**. Make sure the README and any other
24+
relevant documentation are kept up-to-date.
25+
26+
- **Create topic branches**. Please don't ask us to pull from your master branch.
27+
28+
- **One pull request per feature**. If you want to do more than one thing, send
29+
multiple pull requests.
30+
31+
- **Send coherent history**. Make sure each individual commit in your pull
32+
request is meaningful. If you had to make multiple intermediate commits while
33+
developing, please squash them before sending them to us.

Gemfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
# Specify your gem's dependencies in sql_matchers.gemspec
6+
gemspec
7+
8+
gem "rake", "~> 13.0"
9+
10+
gem "rspec", "~> 3.0"

LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright 2024 Keygen LLC
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# sql_matchers
2+
3+
[![CI](https://github.com/keygen-sh/sql_matchers/actions/workflows/test.yml/badge.svg)](https://github.com/keygen-sh/sql_matchers/actions)
4+
[![Gem Version](https://badge.fury.io/rb/sql_matchers.svg)](https://badge.fury.io/rb/sql_matchers)
5+
6+
Use `sql_matchers` to create temporary tables and models in RSpec specs,
7+
rather than create and maintain a dummy Rails application or messy block-level
8+
constants.
9+
10+
This gem was extracted from [Keygen](https://keygen.sh).
11+
12+
Sponsored by:
13+
14+
<a href="https://keygen.sh?ref=sql_matchers">
15+
<div>
16+
<img src="https://keygen.sh/images/logo-pill.png" width="200" alt="Keygen">
17+
</div>
18+
</a>
19+
20+
_A fair source software licensing and distribution API._
21+
22+
## Installation
23+
24+
Add this line to your application's `Gemfile`:
25+
26+
```ruby
27+
gem 'sql_matchers'
28+
```
29+
30+
And then execute:
31+
32+
```bash
33+
$ bundle
34+
```
35+
36+
Or install it yourself as:
37+
38+
```bash
39+
$ gem install sql_matchers
40+
```
41+
42+
## Usage
43+
44+
### `temporary_table`
45+
46+
To define a temporary table:
47+
48+
```ruby
49+
describe Example do
50+
temporary_table :user do |t|
51+
t.string :email
52+
t.string :first_name
53+
t.string :last_name
54+
t.index :email, unique: true
55+
end
56+
57+
it 'should define a table' do
58+
expect(ActiveRecord::Base.connection.table_exists?(:user)).to be true
59+
end
60+
end
61+
```
62+
63+
The full Active Record schema API is available.
64+
65+
### `temporary_model`
66+
67+
To define an Active Record:
68+
69+
```ruby
70+
describe Example do
71+
temporary_model :user do
72+
has_many :posts
73+
end
74+
75+
it 'should define a record' do
76+
expect(const_defined?(:User)).to be true
77+
end
78+
end
79+
```
80+
81+
To define an Active Model:
82+
83+
```ruby
84+
describe Example do
85+
temporary_model :guest_user, table_name: nil, base_class: nil do
86+
include ActiveModel::Model
87+
end
88+
89+
it 'should define a model' do
90+
expect(const_defined?(:GuestUser)).to be true
91+
end
92+
end
93+
```
94+
95+
To define a PORO:
96+
97+
```ruby
98+
describe Example do
99+
temporary_model :null_user, table_name: nil, base_class: nil do
100+
# ...
101+
end
102+
103+
it 'should define a PORO' do
104+
expect(const_defined?(:NullUser)).to be true
105+
end
106+
end
107+
```
108+
109+
## Future
110+
111+
Right now, the gem only supports RSpec, but we're open to pull requests that
112+
extend the functionality to other testing frameworks.
113+
114+
## Supported Rubies
115+
116+
**`sql_matchers` supports Ruby 3.1 and above.** We encourage you to upgrade
117+
if you're on an older version. Ruby 3 provides a lot of great features, like
118+
better pattern matching and a new shorthand hash syntax.
119+
120+
## Is it any good?
121+
122+
Yes.
123+
124+
## Contributing
125+
126+
If you have an idea, or have discovered a bug, please open an issue or create a
127+
pull request.
128+
129+
## License
130+
131+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

Rakefile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'bundler/gem_tasks'
2+
require 'rspec/core/rake_task'
3+
4+
RSpec::Core::RakeTask.new(:spec)
5+
task test: :spec
6+
7+
task default: :test

SECURITY.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Security Policy
2+
3+
## Reporting a vulnerability
4+
5+
If you find a vulnerability in `sql_matchers`, please contact Keygen via
6+
[email](mailto:[email protected]).
7+
8+
You will be given public credit for your disclosure.

bin/console

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require "bundler/setup"
5+
require "sql_matchers"
6+
7+
# You can add fixtures and/or initialization code here to make experimenting
8+
# with your gem easier. You can also use a different console, if you like.
9+
10+
require "irb"
11+
IRB.start(__FILE__)

bin/setup

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
set -vx
5+
6+
bundle install
7+
8+
# Do any other automated setup that you need to do here

lib/sql_matchers.rb

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
require 'active_record'
4+
5+
require_relative 'sql_matchers/methods'
6+
require_relative 'sql_matchers/version'
7+
8+
module SqlMatchers; end

lib/sql_matchers/loggers.rb

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
module SqlMatchers
4+
module Loggers; end
5+
end

lib/sql_matchers/loggers/query.rb

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
module SqlMatchers
4+
module Loggers
5+
class Query
6+
IGNORED_STATEMENTS = %w[CACHE SCHEMA]
7+
IGNORED_STATEMENT_RE = %r{^(?:ROLLBACK|BEGIN|COMMIT|SAVEPOINT|RELEASE)}
8+
IGNORED_COMMENT_RE = %r{
9+
/\*(\w+='\w+',?)+\*/ # query log tags
10+
}x
11+
12+
def initialize
13+
@queries = []
14+
end
15+
16+
def self.subscribe(&) = new.subscribe(&)
17+
def subscribe(&block)
18+
ActiveSupport::Notifications.subscribed(
19+
logger_proc,
20+
'sql.active_record',
21+
&proc {
22+
result = block.call
23+
result.load if result in ActiveRecord::Relation # autoload relations
24+
}
25+
)
26+
27+
@queries
28+
end
29+
30+
private
31+
32+
def logger_proc = proc(&method(:logger))
33+
def logger(event)
34+
unless IGNORED_STATEMENTS.include?(event.payload[:name]) || IGNORED_STATEMENT_RE.match(event.payload[:sql])
35+
query = event.payload[:sql].gsub(IGNORED_COMMENT_RE, '')
36+
.squish
37+
38+
@queries << query
39+
end
40+
end
41+
end
42+
end
43+
end

lib/sql_matchers/matchers.rb

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'matchers/query'
4+
require_relative 'matchers/sql'
5+
6+
module SqlMatchers
7+
module Matchers
8+
def match_query(...) = Query.new(...)
9+
def match_queries(...) = match_query(...)
10+
def match_sql(...) = Sql.new(...)
11+
end
12+
end

lib/sql_matchers/matchers/query.rb

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../loggers/query'
4+
5+
module SqlMatchers
6+
module Matchers
7+
class Query
8+
def initialize(count: nil, &block)
9+
@count = count
10+
@block = block
11+
end
12+
13+
def supports_block_expectations? = true
14+
def supports_value_expectations? = true
15+
16+
def matches?(block)
17+
@queries = Loggers::Query.subscribe(&block)
18+
19+
(@count.nil? || @queries.size == @count) && (
20+
@block.nil? || @block.call(@queries)
21+
)
22+
end
23+
24+
def failure_message
25+
"expected to match #{@count} queries but got #{@queries.size}"
26+
end
27+
28+
def failure_message_when_negated
29+
"expected to not match #{@count} queries"
30+
end
31+
end
32+
end
33+
end

0 commit comments

Comments
 (0)