rack-queries 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.babelrc +13 -0
- data/.eslintignore +2 -0
- data/.eslintrc.json +8 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +18 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +57 -0
- data/LICENSE +21 -0
- data/README.md +86 -0
- data/Rakefile +12 -0
- data/bin/console +8 -0
- data/bin/example +168 -0
- data/bin/setup +6 -0
- data/lib/rack/queries.rb +17 -0
- data/lib/rack/queries/app.rb +95 -0
- data/lib/rack/queries/cache.rb +45 -0
- data/lib/rack/queries/static/app.css +137 -0
- data/lib/rack/queries/static/app.js +31 -0
- data/lib/rack/queries/static/favicon.ico +0 -0
- data/lib/rack/queries/static/index.html +15 -0
- data/lib/rack/queries/version.rb +7 -0
- data/package.json +49 -0
- data/rack-queries.gemspec +34 -0
- data/setupTests.js +2 -0
- data/webpack.config.js +14 -0
- data/yarn.lock +6110 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6b814a164ac9b628c813e68cc1ee655d866591bdfda12d4133cf5cf0928ea33b
|
4
|
+
data.tar.gz: ac90a00122cbc69bb2fd41b459dafa815975953d4adc4d62d972e06745ca8aca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2f9e59e66e9b4aeb541751e79081219631cfa1858764979407a1e5487c2881accdcec62df67a98a4be9f3fc08a20df357db1d11e86645c4ecac8d2b0f472d91
|
7
|
+
data.tar.gz: 7b17913fb90f412d78c89bd7b1aa17cda8f0d800409d8ec77c83fc112b9ff6cd190d403ca3b0c68cf93ad2d209d9236ab0f51ae56dd7b35f9cc0a34612dade2c
|
data/.babelrc
ADDED
data/.eslintignore
ADDED
data/.eslintrc.json
ADDED
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm: 2.6.1
|
3
|
+
branches:
|
4
|
+
only: master
|
5
|
+
before_install:
|
6
|
+
- gem update --system
|
7
|
+
- gem install bundler
|
8
|
+
install:
|
9
|
+
- bundle install --jobs=3 --retry=3 --deployment
|
10
|
+
- yarn install
|
11
|
+
script:
|
12
|
+
- bundle exec rake
|
13
|
+
- bundle exec rubocop
|
14
|
+
- yarn test
|
15
|
+
- yarn lint
|
16
|
+
cache:
|
17
|
+
- bundler
|
18
|
+
- yarn
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rack-queries (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.0)
|
10
|
+
docile (1.3.1)
|
11
|
+
jaro_winkler (1.5.2)
|
12
|
+
json (2.2.0)
|
13
|
+
minitest (5.11.3)
|
14
|
+
parallel (1.14.0)
|
15
|
+
parser (2.6.0.0)
|
16
|
+
ast (~> 2.4.0)
|
17
|
+
powerpack (0.1.2)
|
18
|
+
psych (3.1.0)
|
19
|
+
rack (2.0.6)
|
20
|
+
rack-test (1.1.0)
|
21
|
+
rack (>= 1.0, < 3)
|
22
|
+
rainbow (3.0.0)
|
23
|
+
rake (12.3.2)
|
24
|
+
rubocop (0.65.0)
|
25
|
+
jaro_winkler (~> 1.5.1)
|
26
|
+
parallel (~> 1.10)
|
27
|
+
parser (>= 2.5, != 2.5.1.1)
|
28
|
+
powerpack (~> 0.1)
|
29
|
+
psych (>= 3.1.0)
|
30
|
+
rainbow (>= 2.2.2, < 4.0)
|
31
|
+
ruby-progressbar (~> 1.7)
|
32
|
+
unicode-display_width (~> 1.4.0)
|
33
|
+
ruby-progressbar (1.10.0)
|
34
|
+
simplecov (0.16.1)
|
35
|
+
docile (~> 1.1)
|
36
|
+
json (>= 1.8, < 3)
|
37
|
+
simplecov-html (~> 0.10.0)
|
38
|
+
simplecov-html (0.10.2)
|
39
|
+
sqlite3 (1.4.0)
|
40
|
+
unicode-display_width (1.4.1)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
bundler (~> 2.0)
|
47
|
+
minitest (~> 5.0)
|
48
|
+
rack (~> 2.0)
|
49
|
+
rack-queries!
|
50
|
+
rack-test (~> 1.1)
|
51
|
+
rake (~> 12.0)
|
52
|
+
rubocop (~> 0.65)
|
53
|
+
simplecov (~> 0.16)
|
54
|
+
sqlite3 (~> 1.4)
|
55
|
+
|
56
|
+
BUNDLED WITH
|
57
|
+
2.0.1
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Kevin Deisz
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Rack::Queries
|
2
|
+
|
3
|
+
This gem provides a page in your rack-based (e.g., `Rails`, `Sinatra`) application that allows quick execution of pre-built queries. The goal is to allow quick insights into the state of your application without needing to update the main UI. Consider it a backdoor admin page that you can use before you decide to truly expose query results.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
First, add `rack-queries` to your Gemfile and `bundle install`. Then, mount the `Rack::Queries::App` application within your app.
|
8
|
+
|
9
|
+
Within Rails, that will look like (within `config/routes.rb`):
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Rails.application.routes.draw do
|
13
|
+
mount Rack::Queries::App, at: '/queries'
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
Then, you can go to `/queries` within your application to view the empty queries page.
|
18
|
+
|
19
|
+
Second, define the queries that you want included on your query page. Queries are classes that respond to `run(opts)`. The `run` method should return either a single value or an array of arrays (in the UI it will either display one value or a table). The following example returns an overall count:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class UserCountQuery
|
23
|
+
def run(_opts)
|
24
|
+
User.count
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
If you want your queries to have arguments (say for instance that users can be scoped to organizations), you define public instance methods on your query class:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class UserPerOrgCountQuery
|
33
|
+
def org
|
34
|
+
Org.order(:name).pluck(:name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def run(opts)
|
38
|
+
Org.where(name: opts['name']).users.count
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Each public instance method is expected to return an array of options (they get transformed into `select` tags in the UI). They are then given to the `run` method through the `opts` hash which contains the value within a string key corresponding to the method name.
|
44
|
+
|
45
|
+
Finally, inform `rack-queries` that you want to include the query on your query page by adding it to the list:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
Rack::Queries.add(
|
49
|
+
UserCountQuery,
|
50
|
+
UserPerOrgCountQuery
|
51
|
+
)
|
52
|
+
```
|
53
|
+
|
54
|
+
### Middleware
|
55
|
+
|
56
|
+
Since `Rack::Queries` is a rack application, you can add whatever middleware you like into its stack before the request hits the application. For instance, to integrate HTTP basic auth around it to protect the query results, you can use the `Rack::Queries::App::use` method as in:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
Rack::Queries::App.use(Rack::Auth::Basic) do |username, password|
|
60
|
+
compare = lambda { |left, right|
|
61
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
62
|
+
::Digest::SHA256.hexdigest(left),
|
63
|
+
::Digest::SHA256.hexdigest(right)
|
64
|
+
)
|
65
|
+
}
|
66
|
+
|
67
|
+
credentials = Rails.application.credentials
|
68
|
+
|
69
|
+
compare[username, credentials.rack_queries_username] &
|
70
|
+
compare[password, credentials.rack_queries_password]
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
## Development
|
75
|
+
|
76
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
77
|
+
|
78
|
+
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).
|
79
|
+
|
80
|
+
## Contributing
|
81
|
+
|
82
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/CultureHQ/rack-queries.
|
83
|
+
|
84
|
+
## License
|
85
|
+
|
86
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/example
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'rack'
|
7
|
+
require 'sqlite3'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift(File.expand_path(File.join('..', 'lib', 'rack'), __dir__))
|
10
|
+
require 'queries'
|
11
|
+
|
12
|
+
class Database
|
13
|
+
class IncomprehensibleReturnValueError < ArgumentError
|
14
|
+
def initialize(*)
|
15
|
+
super('SQLite3 return value not understood')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :db
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@db = SQLite3::Database.new(':memory:')
|
23
|
+
|
24
|
+
DATA.read.split(';')[0...-1].each do |query|
|
25
|
+
db.execute(query)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def value_from(query, binds = [])
|
30
|
+
results = execute(query, binds)
|
31
|
+
|
32
|
+
case results
|
33
|
+
when Array
|
34
|
+
results[0][0]
|
35
|
+
when SQLite3::ResultSet
|
36
|
+
results.next[0]
|
37
|
+
else
|
38
|
+
raise IncomprehensibleReturnValueError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def values_from(query, binds = [])
|
43
|
+
execute(query, binds).map(&:first)
|
44
|
+
end
|
45
|
+
|
46
|
+
def rows_from(query, binds = [])
|
47
|
+
execute(query, binds)
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def instance
|
52
|
+
@instance ||= new
|
53
|
+
end
|
54
|
+
|
55
|
+
extend Forwardable
|
56
|
+
def_delegators :instance, :value_from, :values_from, :rows_from
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def execute(query, binds)
|
62
|
+
return db.execute(query) if binds.empty?
|
63
|
+
|
64
|
+
stmt = db.prepare(query)
|
65
|
+
stmt.execute(binds)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module Queries
|
70
|
+
class OrgCountQuery
|
71
|
+
def run(_opts)
|
72
|
+
Database.value_from('SELECT COUNT(*) FROM orgs')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class OrgUsersCountQuery
|
77
|
+
def org_name
|
78
|
+
Database.values_from('SELECT name FROM orgs')
|
79
|
+
end
|
80
|
+
|
81
|
+
def run(opts)
|
82
|
+
query = <<~SQL
|
83
|
+
SELECT COUNT(*) FROM users
|
84
|
+
WHERE org_id = (SELECT id FROM orgs WHERE name = :org_name)
|
85
|
+
SQL
|
86
|
+
|
87
|
+
Database.value_from(query, opts)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class OrgActiveUsersCountQuery
|
92
|
+
def org_name
|
93
|
+
Database.values_from('SELECT name FROM orgs')
|
94
|
+
end
|
95
|
+
|
96
|
+
def run(opts)
|
97
|
+
query = <<~SQL
|
98
|
+
SELECT COUNT(*) FROM users
|
99
|
+
WHERE org_id = (SELECT id FROM orgs WHERE name = :org_name)
|
100
|
+
AND active = 1
|
101
|
+
SQL
|
102
|
+
|
103
|
+
Database.value_from(query, opts)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class UserCountQuery
|
108
|
+
def run(_opts)
|
109
|
+
Database.value_from('SELECT COUNT(*) FROM users')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class UsersQuery
|
114
|
+
def run(_opts)
|
115
|
+
[%w[id org_id active name]] +
|
116
|
+
Database.rows_from('SELECT id, org_id, active, name FROM users')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
Rack::Queries.add(
|
122
|
+
Queries::OrgCountQuery,
|
123
|
+
Queries::OrgUsersCountQuery,
|
124
|
+
Queries::OrgActiveUsersCountQuery,
|
125
|
+
Queries::UserCountQuery,
|
126
|
+
Queries::UsersQuery
|
127
|
+
)
|
128
|
+
|
129
|
+
Rack::Server.start(app: Rack::Queries::App, server: 'webrick')
|
130
|
+
|
131
|
+
__END__
|
132
|
+
CREATE TABLE orgs (
|
133
|
+
id INTEGER,
|
134
|
+
name VARCHAR(255),
|
135
|
+
PRIMARY KEY(id)
|
136
|
+
);
|
137
|
+
|
138
|
+
CREATE TABLE users (
|
139
|
+
id INTEGER,
|
140
|
+
org_id INTEGER,
|
141
|
+
active BOOLEAN NOT NULL,
|
142
|
+
name VARCHAR(255),
|
143
|
+
PRIMARY KEY(id)
|
144
|
+
);
|
145
|
+
|
146
|
+
INSERT INTO orgs(name) VALUES
|
147
|
+
("Parks and Recreation"),
|
148
|
+
("Dunder Mifflin");
|
149
|
+
|
150
|
+
INSERT INTO users(org_id, active, name) VALUES
|
151
|
+
(1, 1, "Leslie Knope"),
|
152
|
+
(1, 1, "Ron Swanson"),
|
153
|
+
(1, 1, "April Ludgate"),
|
154
|
+
(1, 1, "Andy Dwyer"),
|
155
|
+
(1, 1, "Ben Wyatt"),
|
156
|
+
(1, 1, "Tom Haverford"),
|
157
|
+
(1, 1, "Donna Meagle"),
|
158
|
+
(1, 0, "Jerry Gergich"),
|
159
|
+
(2, 1, "Michael Scott"),
|
160
|
+
(2, 1, "Pam Beesley"),
|
161
|
+
(2, 1, "Jim Halpert"),
|
162
|
+
(2, 1, "Dwight Schrute"),
|
163
|
+
(2, 1, "Angela Martin"),
|
164
|
+
(2, 1, "Andy Bernard"),
|
165
|
+
(2, 1, "Kevin Malone"),
|
166
|
+
(2, 0, "Roy Anderson"),
|
167
|
+
(2, 1, "Kelly Kapoor"),
|
168
|
+
(2, 1, "Ryan Howard");
|