Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
pcai committed Mar 22, 2023
1 parent ddf5fd2 commit ee1fe02
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
{
"name": "Ruby",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/ruby:0-3.1-bullseye"

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "ruby --version",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spec/test.db
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SafeQuery changelog

## Unreleased
- Add your PR changelog line here

## 0.1.0 (2023-03-22)
- Initial release
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gemspec
52 changes: 52 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
PATH
remote: .
specs:
safe_query (0.1.0)
activerecord (>= 5.0)
activesupport (>= 5.0)

GEM
remote: https://rubygems.org/
specs:
activemodel (7.0.4.3)
activesupport (= 7.0.4.3)
activerecord (7.0.4.3)
activemodel (= 7.0.4.3)
activesupport (= 7.0.4.3)
activesupport (7.0.4.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
concurrent-ruby (1.2.2)
diff-lcs (1.5.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
minitest (5.18.0)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
rspec-mocks (~> 3.12.0)
rspec-core (3.12.1)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.4)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-support (3.12.0)
sqlite3 (1.6.1-x86_64-linux)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)

PLATFORMS
x86_64-linux

DEPENDENCIES
rspec (>= 3.12)
safe_query!
sqlite3 (>= 1.6.1)

BUNDLED WITH
2.3.26
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) Peter Cai

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
47 changes: 47 additions & 0 deletions lib/safe_query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module ActiveRecord
class UnsafeQueryError < StandardError
# skip ourselves in the backtrace so it ends in the user code that generated the issue
def backtrace
return @lines if @lines
@lines = super
@lines.shift if @lines.present?
@lines
end
end

class Relation
module SafeQuery
def each
QueryRegistry.reset
super

query_to_check = QueryRegistry.queries.first.to_s

unless query_to_check.blank? || query_to_check.upcase.include?("LIMIT ") || query_to_check.upcase.include?("IN ")
raise UnsafeQueryError, "Detected a potentially dangerous #each iterator on an unpaginated query. " +
"Perhaps you need to add pagination, a limit clause, or use the ActiveRecord::Batches methods. \n\n" +
"To ignore this problem, or if it is a false positive, convert it to an array with ActiveRecord::Relation#to_a before iterating.\n\n" ++
"Potentially unpaginated query: \n\n #{query_to_check}"
end
end

ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
QueryRegistry.queries << payload[:sql]
end

module QueryRegistry
extend self

def queries
ActiveSupport::IsolatedExecutionState[:active_record_query_registry] ||= []
end

def reset
queries.clear
end
end
end
end
end

ActiveRecord::Relation.prepend ActiveRecord::Relation::SafeQuery
29 changes: 29 additions & 0 deletions safe_query.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- encoding : utf-8 -*-
lib = File.expand_path("../lib", __FILE__)
$:.unshift lib unless $:.include? lib

Gem::Specification.new do |s|
s.name = "safe_query"
s.version = "0.1.0"
s.authors = "Peter Cai"
s.email = "[email protected]"
s.homepage = "https://github.com/pcai/safe_query"
s.summary = "Safely query stuff in ActiveRecord"
s.description = <<-EOF
Helps developers avoid unsafe queries in ActiveRecord. This gem will raise an error
when iterating over a relation that is potentially unpaginated.
EOF
s.required_ruby_version = '>= 2.6.0'

s.license = 'MIT'

s.add_dependency "activerecord", ">= 5.0", "< 8.0"
s.add_dependency "activesupport", ">= 5.0", "< 8.0"

s.add_development_dependency "rspec", "~> 3.12"
s.add_development_dependency "sqlite3", "~> 1.6.1"

s.files = Dir['CHANGELOG.md', 'LICENSE', 'README.md', 'lib/**/*.rb']

s.require_path = "lib"
end
19 changes: 19 additions & 0 deletions spec/activerecord/safe_query_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require "spec_helper"

RSpec.describe ActiveRecord::Relation::SafeQuery do
it "raises an error when iterating over a relation without a limit" do
expect { User.all.each {} }.to raise_error(ActiveRecord::UnsafeQueryError)
end

it "does not raise an error when iterating over a relation with a limit" do
expect { User.limit(1).each {} }.to_not raise_error
end

it "does not raise an error when iterating over a relation with an in clause" do
expect { User.where(id: [1, 2, 3]).each {} }.to_not raise_error
end

it "does not raise an error when iterating over a relation with an in clause and a limit" do
expect { User.where(id: [1, 2, 3]).limit(1).each {} }.to_not raise_error
end
end
2 changes: 2 additions & 0 deletions spec/database.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
adapter: 'sqlite3'
database: 'spec/test.db'
3 changes: 3 additions & 0 deletions spec/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class User < ActiveRecord::Base

end
11 changes: 11 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "bundler"
Bundler.setup(:default, :development)

require "active_support/all"
require "active_record"
require "safe_query"

ActiveRecord::Base.establish_connection YAML::load(File.open('spec/database.yml'))
ActiveRecord::Base.connection.execute "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT, created_at DATETIME, updated_at DATETIME)"

require "models/user"

0 comments on commit ee1fe02

Please sign in to comment.