arel_toolkit 0.4.0 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/coverage.yml +48 -0
- data/.github/workflows/test.yml +65 -0
- data/.gitignore +6 -1
- data/Appraisals +4 -0
- data/CHANGELOG.md +87 -7
- data/Gemfile.lock +50 -39
- data/Guardfile +4 -0
- data/README.md +25 -11
- data/Rakefile +11 -1
- data/arel_toolkit.gemspec +10 -5
- data/benchmark.rb +54 -0
- data/ext/pg_result_init/extconf.rb +52 -0
- data/ext/pg_result_init/pg_result_init.c +138 -0
- data/ext/pg_result_init/pg_result_init.h +6 -0
- data/gemfiles/active_record_6.gemfile +7 -0
- data/gemfiles/active_record_6.gemfile.lock +210 -0
- data/gemfiles/arel_gems.gemfile.lock +28 -18
- data/gemfiles/default.gemfile.lock +30 -20
- data/lib/arel/enhance.rb +1 -0
- data/lib/arel/enhance/context_enhancer/arel_table.rb +18 -1
- data/lib/arel/enhance/node.rb +71 -28
- data/lib/arel/enhance/query.rb +2 -0
- data/lib/arel/enhance/query_methods.rb +23 -0
- data/lib/arel/enhance/visitor.rb +19 -3
- data/lib/arel/extensions.rb +8 -2
- data/lib/arel/extensions/active_model_attribute_with_cast_value.rb +22 -0
- data/lib/arel/extensions/active_record_relation_query_attribute.rb +22 -0
- data/lib/arel/extensions/active_record_type_caster_connection.rb +7 -0
- data/lib/arel/extensions/attributes_attribute.rb +47 -0
- data/lib/arel/extensions/bind_param.rb +15 -0
- data/lib/arel/extensions/coalesce.rb +17 -3
- data/lib/arel/extensions/delete_statement.rb +20 -15
- data/lib/arel/extensions/exists.rb +59 -0
- data/lib/arel/extensions/function.rb +3 -2
- data/lib/arel/extensions/greatest.rb +17 -3
- data/lib/arel/extensions/infer.rb +1 -1
- data/lib/arel/extensions/insert_statement.rb +3 -3
- data/lib/arel/extensions/least.rb +17 -3
- data/lib/arel/extensions/node.rb +10 -0
- data/lib/arel/extensions/range_function.rb +10 -2
- data/lib/arel/extensions/select_core.rb +22 -7
- data/lib/arel/extensions/top.rb +8 -0
- data/lib/arel/extensions/tree_manager.rb +5 -0
- data/lib/arel/extensions/update_statement.rb +9 -23
- data/lib/arel/middleware.rb +5 -1
- data/lib/arel/middleware/active_record_extension.rb +13 -0
- data/lib/arel/middleware/cache_accessor.rb +35 -0
- data/lib/arel/middleware/chain.rb +110 -31
- data/lib/arel/middleware/database_executor.rb +77 -0
- data/lib/arel/middleware/no_op_cache.rb +9 -0
- data/lib/arel/middleware/postgresql_adapter.rb +41 -5
- data/lib/arel/middleware/railtie.rb +6 -2
- data/lib/arel/middleware/result.rb +170 -0
- data/lib/arel/middleware/to_sql_executor.rb +15 -0
- data/lib/arel/middleware/to_sql_middleware.rb +33 -0
- data/lib/arel/sql_to_arel/pg_query_visitor.rb +34 -33
- data/lib/arel/sql_to_arel/result.rb +19 -2
- data/lib/arel/transformer.rb +2 -1
- data/lib/arel/transformer/prefix_schema_name.rb +183 -0
- data/lib/arel/transformer/remove_active_record_info.rb +2 -4
- data/lib/arel/transformer/replace_table_with_subquery.rb +31 -0
- data/lib/arel_toolkit.rb +7 -1
- data/lib/arel_toolkit/version.rb +1 -1
- metadata +101 -37
- data/.travis.yml +0 -34
- data/lib/arel/extensions/generate_series.rb +0 -9
- data/lib/arel/extensions/rank.rb +0 -9
- data/lib/arel/transformer/add_schema_to_table.rb +0 -26
data/Guardfile
CHANGED
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
- [](https://github.com/mvgijssel/arel_toolkit/actions)
|
|
6
6
|
- [](https://codeclimate.com/github/mvgijssel/arel_toolkit/maintainability)
|
|
7
7
|
- [](https://codeclimate.com/github/mvgijssel/arel_toolkit/test_coverage)
|
|
8
8
|
- [](https://badge.fury.io/rb/arel_toolkit)
|
|
@@ -65,7 +65,7 @@ enhanced_arel.child_at_path(['ast', 'cores', 0, 'projections', 1]).object
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
##### Replace or remove nodes without modifying the original arel
|
|
68
|
-
`remove` and `replace` allow for modifications to the Arel AST. The changes are aplied to a new copy of the AST, making sure the original AST is not touched.
|
|
68
|
+
`remove` and `replace` allow for modifications to the Arel AST. The changes are aplied to a new copy of the AST, making sure the original AST is not touched. The `replace` method accepts any Arel node or a precomputed enhanced node for improved performance.
|
|
69
69
|
|
|
70
70
|
```ruby
|
|
71
71
|
enhanced_arel.child_at_path(['ast', 'cores', 0, 'projections', 1]).replace(Post.arel_table[:content])
|
|
@@ -84,33 +84,36 @@ Middleware sits between ActiveRecord and the database, it allows you to alter th
|
|
|
84
84
|
The middleware works out of the box in combination with Rails. If using ActiveRecord standalone you need to run the following **after** setting up the database connection:
|
|
85
85
|
|
|
86
86
|
```ruby
|
|
87
|
-
Arel::Middleware::Railtie.
|
|
87
|
+
Arel::Middleware::Railtie.insert
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
### Example
|
|
91
91
|
|
|
92
|
-
Create
|
|
92
|
+
Create middleware which can be any Ruby object as long as it responds to `call`. Middleware accepts 2 or 3 arguments, context is optional. Calling `.call` on `next_middleware` invokes the next middleware in the chain, returning the response from the database.
|
|
93
|
+
|
|
94
|
+
In this example, we're creating a middleware that will reorder any query. Next to reordering, we're adding an additional middleware that prints out the result of the reorder middleware.
|
|
93
95
|
|
|
94
96
|
```ruby
|
|
95
97
|
class ReorderMiddleware
|
|
96
|
-
def self.call(arel,
|
|
98
|
+
def self.call(arel, next_middleware)
|
|
97
99
|
enhanced_arel = Arel.enhance(arel)
|
|
98
100
|
enhanced_arel.query(class: Arel::Nodes::SelectStatement).each do |node|
|
|
99
101
|
arel_table = node.child_at_path(['cores', 0, 'source', 'left']).object
|
|
100
102
|
node['orders'].replace([arel_table[:id].asc])
|
|
101
103
|
end
|
|
102
104
|
|
|
103
|
-
arel.order(Post.arel_table[:id].asc)
|
|
105
|
+
new_arel = arel.order(Post.arel_table[:id].asc)
|
|
106
|
+
next_middleware.call(new_arel)
|
|
104
107
|
end
|
|
105
108
|
end
|
|
106
109
|
|
|
107
110
|
class LoggingMiddleware
|
|
108
|
-
def self.call(arel, context)
|
|
111
|
+
def self.call(arel, next_middleware, context)
|
|
109
112
|
puts "User executing query: `#{context[:current_user_id]}`"
|
|
110
113
|
puts "Original SQL: `#{context[:original_sql]}`"
|
|
111
114
|
puts "Modified SQL: `#{arel.to_sql}`"
|
|
112
|
-
|
|
113
|
-
arel
|
|
115
|
+
|
|
116
|
+
next_middleware.call(arel)
|
|
114
117
|
end
|
|
115
118
|
end
|
|
116
119
|
```
|
|
@@ -140,9 +143,20 @@ This gem aims to have full support for PostgreSQL's SQL. In order to do so, it n
|
|
|
140
143
|
|
|
141
144
|
## Development
|
|
142
145
|
|
|
143
|
-
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.
|
|
146
|
+
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. To install this gem onto your local machine, run `bundle exec rake install`.
|
|
147
|
+
|
|
148
|
+
## Releasing
|
|
144
149
|
|
|
145
|
-
|
|
150
|
+
1. Update version in `version.rb`
|
|
151
|
+
1. Create a new branch `v<<VERSION_HERE>>`
|
|
152
|
+
1. Run `bundle install`
|
|
153
|
+
1. Run `bundle exec appraisal install`
|
|
154
|
+
1. Run `bundle exec rake changelog`
|
|
155
|
+
1. Commit the changes
|
|
156
|
+
1. Open a PR with name `Version <<VERSION_HERE>>` ([example](https://github.com/mvgijssel/arel_toolkit/pull/172))
|
|
157
|
+
1. Merge the PR
|
|
158
|
+
1. Checkout the master branch and pull latest changes
|
|
159
|
+
1. Run `bundle exec rake release`
|
|
146
160
|
|
|
147
161
|
## Contributing
|
|
148
162
|
|
data/Rakefile
CHANGED
|
@@ -10,5 +10,15 @@ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
|
|
10
10
|
config.user = 'mvgijssel'
|
|
11
11
|
config.project = 'arel_toolkit'
|
|
12
12
|
config.future_release = "v#{ArelToolkit::VERSION}"
|
|
13
|
-
config.
|
|
13
|
+
config.add_pr_wo_labels = false
|
|
14
|
+
config.enhancement_labels = %w[enhancement dependencies]
|
|
15
|
+
config.exclude_tags = ['v0.1.0']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require 'rake/extensiontask'
|
|
19
|
+
|
|
20
|
+
task build: :compile
|
|
21
|
+
|
|
22
|
+
Rake::ExtensionTask.new('pg_result_init') do |ext|
|
|
23
|
+
ext.lib_dir = 'lib/arel_toolkit'
|
|
14
24
|
end
|
data/arel_toolkit.gemspec
CHANGED
|
@@ -23,16 +23,17 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
spec.bindir = 'exe'
|
|
24
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
25
25
|
spec.require_paths = ['lib']
|
|
26
|
+
spec.extensions = ['ext/pg_result_init/extconf.rb']
|
|
26
27
|
|
|
27
|
-
spec.add_dependency '
|
|
28
|
-
spec.add_dependency 'activerecord', '~> 5.2.0'
|
|
28
|
+
spec.add_dependency 'activerecord'
|
|
29
29
|
spec.add_dependency 'pg', '~> 1.1.4'
|
|
30
|
-
spec.add_dependency 'pg_query', '~> 1.
|
|
30
|
+
spec.add_dependency 'pg_query', '~> 1.3'
|
|
31
31
|
|
|
32
32
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
33
33
|
spec.add_development_dependency 'dpl', '~> 1.10.11'
|
|
34
|
-
spec.add_development_dependency 'github_changelog_generator', '~> 1.
|
|
35
|
-
spec.add_development_dependency 'rake', '~>
|
|
34
|
+
spec.add_development_dependency 'github_changelog_generator', '~> 1.15'
|
|
35
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
36
|
+
spec.add_development_dependency 'rake-compiler', '~> 1.0'
|
|
36
37
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
|
37
38
|
spec.add_development_dependency 'approvals', '~> 0.0.24'
|
|
38
39
|
spec.add_development_dependency 'appraisal', '~> 2.2.0'
|
|
@@ -45,6 +46,10 @@ Gem::Specification.new do |spec|
|
|
|
45
46
|
spec.add_development_dependency 'guard', '~> 2.15'
|
|
46
47
|
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
|
47
48
|
spec.add_development_dependency 'guard-rubocop', '~> 1.3.0'
|
|
49
|
+
spec.add_development_dependency 'guard-rake', '~> 1.0.0'
|
|
50
|
+
|
|
51
|
+
spec.add_development_dependency 'stackprof', '~> 0.2'
|
|
52
|
+
spec.add_development_dependency 'memory_profiler', '~> 0.9'
|
|
48
53
|
|
|
49
54
|
spec.add_development_dependency 'pry'
|
|
50
55
|
spec.add_development_dependency 'pry-nav'
|
data/benchmark.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
GC.disable
|
|
2
|
+
|
|
3
|
+
require 'arel'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require 'memory_profiler'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'stackprof'
|
|
8
|
+
|
|
9
|
+
require './lib/arel/enhance'
|
|
10
|
+
require './lib/arel/extensions'
|
|
11
|
+
require './lib/arel/sql_to_arel'
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Base.establish_connection(
|
|
14
|
+
adapter: 'postgresql',
|
|
15
|
+
host: 'localhost',
|
|
16
|
+
databse: 'arel_toolkit_test',
|
|
17
|
+
username: 'postgres',
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# rubocop:disable Metrics/LineLength
|
|
21
|
+
SQL = %{
|
|
22
|
+
SELECT COUNT("hacktivity_items"."id") FROM (SELECT "hacktivity_items".* FROM (SELECT "hacktivity_items".* FROM (SELECT "hacktivity_items".*, (NULLIF(current_setting('secure.requester_id'), ''))::integer AS "_requester_id" FROM (SELECT "outer_hacktivity_items".*, EXISTS (SELECT 1 FROM "public"."reports" INNER JOIN "public"."teams" ON "teams"."id" = "reports"."team_id" WHERE "teams"."state" = 5 AND "reports"."disclosed_at" IS NOT NULL AND "reports"."public_view" = 'full' AND "reports"."id" = "outer_hacktivity_items"."report_id") AS "is_someone_visiting_a_public_report", EXISTS (SELECT 1 FROM "public"."reports" INNER JOIN "public"."teams" ON "teams"."id" = "reports"."team_id" INNER JOIN "public"."whitelisted_reporters" ON "whitelisted_reporters"."team_id" = "teams"."id" WHERE "teams" ."state" = 4 AND "teams"."allows_private_disclosure" = 't'::bool AND (("whitelisted_reporters"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "whitelisted_reporters"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "reports"."disclosed_at" IS NOT NULL AND "reports"."public_view" = 'full' AND "reports"."id" = "outer_hacktivity_items"."report_id") AS "is_someone_visiting_a_report_privately_disclosed_to_them", EXISTS (SELECT 1 FROM "public"."reports" WHERE "reports"."hacker_published" = 't'::bool AND "reports"."disclosed_at" IS NOT NULL AND "reports"."id" = "outer_hacktivity_items"."report_id") AS "is_hacker_published", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."team_members" ON "team_members"."team_id" = "teams"."id" WHERE (("team_members"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "team_members"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "team_members"."user_id" IS NOT NULL AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_team_member", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 5 AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_public", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."whitelisted_reporters" ON "whitelisted_reporters"."team_id" = "teams"."id" INNER JOIN "public"."users" ON "users"."id" = "whitelisted_reporters"."user_id" WHERE (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_whitelisted_reporter", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 4 AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_private", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."offers_bounties" = 't'::bool AND "teams"."hide_bounty_amounts" = 'f'::bool AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_shows_bounty_amounts" FROM "public"."hacktivity_items" "outer_hacktivity_items") AS "hacktivity_items" WHERE "hacktivity_items"."is_team_public" = 't'::bool OR ("hacktivity_items"."is_team_private" = 't'::bool AND ("hacktivity_items"."is_team_team_member" = 't'::bool OR "hacktivity_items"."is_team_whitelisted_reporter" = 't'::bool)) OR "hacktivity_items"."is_hacker_published" = 't'::bool) hacktivity_items) AS "hacktivity_items" LEFT OUTER JOIN (SELECT "reports".*, "new_j0"."disclosed_at" AS "__new_filterable_disclosed_at" FROM (SELECT "reports".*, (NULLIF(current_setting('secure.requester_id'), ''))::integer AS "_requester_id" FROM (SELECT "outer_reports".*, EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."team_members" ON "team_members"."team_id" = "teams"."id" INNER JOIN "public"."users" ON "users"."id" = "team_members"."user_id" WHERE (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_reports"."team_id") AS "is_team_member", (("outer_reports"."reporter_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "outer_reports"."reporter_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "outer_reports"."reporter_id" IS NOT NULL AS "is_reporter", EXISTS (SELECT 1 FROM "public"."reports_users" INNER JOIN "public"."users" ON "users"."id" = "reports_users"."user_id" WHERE (("reports_users"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "reports_users"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "reports_users"."user_id" IS NOT NULL AND "reports_users"."report_id" = "outer_reports"."id") AS "is_external_user", EXISTS (SELECT 1 FROM "public"."report_retests" INNER JOIN "public"."report_retest_users" ON "report_retest_users"."report_retest_id" = "report_retests"."id" WHERE (EXISTS (SELECT "invitations".* FROM "public"."invitations" WHERE ("accepted_at" IS NOT NULL OR "expires_at" >= now() OR "expires_at" IS NULL) AND "invitations"."cancelled_at" IS NULL AND "invitations"."rejected_at" IS NULL AND "invitations"."id" = "report_retest_users"."invitation_id") OR (NOT (EXISTS (SELECT "invitations".* FROM "public"."invitations" WHERE "invitations"."id" = "report_retest_users"."invitation_id")))) AND (("report_retest_users"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "report_retest_users"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "report_retest_users"."user_id" IS NOT NULL AND "report_retests"."report_id" = "outer_reports"."id") AS "is_retester", EXISTS (SELECT 1 FROM "public"."teams" LEFT OUTER JOIN "public"."users" ON "users"."anc_triager" WHERE "outer_reports"."pre_submission_review_state" IS NOT NULL AND "teams"."state" IN (4, 5) AND "teams"."anc_enabled" = 't'::bool AND (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_reports"."team_id") AS "is_anc_triager", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."team_members" ON "team_members"."team_id" = "teams"."id" INNER JOIN "public"."users" ON "users"."id" = "team_members"."user_id" WHERE (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."hackerone_triager" = 't'::bool AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_reports"."team_id") AS "is_hackerone_triager", EXISTS (SELECT 1 FROM "public"."report_collaborators" WHERE (("report_collaborators"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "report_collaborators"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "report_collaborators"."user_id" IS NOT NULL AND "report_collaborators"."report_id" = "outer_reports"."id") AS "is_report_collaborator", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 5 AND "outer_reports"."disclosed_at" IS NOT NULL AND "outer_reports"."public_view" = 'full' AND "teams"."id" = "outer_reports"."team_id") AS "is_someone_visiting_a_public_report", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 5 AND "outer_reports"."disclosed_at" IS NOT NULL AND "outer_reports"."public_view" = 'no-content' AND "teams"."id" = "outer_reports"."team_id") AS "is_someone_visiting_a_public_limited_report", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."whitelisted_reporters" ON "whitelisted_reporters"."team_id" = "teams"."id" WHERE "teams"."state" = 4 AND "teams"."allows_private_disclosure" = 't'::bool AND (("whitelisted_reporters"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "whitelisted_reporters"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "outer_reports"."disclosed_at" IS NOT NULL AND "outer_reports"."public_view" = 'full' AND "teams"."id" = "outer_reports"."team_id") AS "is_someone_visiting_a_report_privately_disclosed_to_them", "outer_reports"."hacker_published" = 't'::bool AND "outer_reports"."disclosed_at" IS NOT NULL AS "is_hacker_published" FROM "public"."reports" "outer_reports") AS "reports" WHERE "reports"."is_reporter" = 't'::bool OR "reports"."is_team_member" = 't'::bool OR "reports"."is_external_user" = 't'::bool OR "reports"."is_anc_triager" = 't'::bool OR "reports"."is_report_collaborator" = 't'::bool OR "reports"."is_someone_visiting_a_public_report" = 't'::bool OR "reports"."is_someone_visiting_a_public_limited_report" = 't'::bool OR "reports"."is_someone_visiting_a_report_privately_disclosed_to_them" = 't'::bool OR "reports"."is_hacker_published" = 't'::bool OR "reports"."is_retester" = 't'::bool) reports LEFT OUTER JOIN "public"."reports" "new_j0" ON "reports"."id" = "new_j0"."id" AND ("reports"."is_reporter" = 't'::bool OR "reports"."is_team_member" = 't'::bool OR "reports"."is_external_user" = 't'::bool OR "reports"."is_anc_triager" = 't'::bool OR "reports"."is_report_collaborator" = 't'::bool OR "reports"."is_someone_visiting_a_public_report" = 't'::bool OR "reports"."is_someone_visiting_a_public_limited_report" = 't'::bool OR "reports"."is_someone_visiting_a_report_privately_disclosed_to_them" = 't'::bool OR "reports"."is_hacker_published" = 't'::bool)) AS "reports" ON "reports"."id" = "hacktivity_items"."report_id" WHERE "reports"."__new_filterable_disclosed_at" IS NOT NULL) AS "hacktivity_items" LEFT OUTER JOIN "hacktivity_scores" ON "hacktivity_scores"."hacktivity_item_id" = "hacktivity_items"."id"
|
|
23
|
+
}.freeze
|
|
24
|
+
# rubocop:enable Metrics/LineLength
|
|
25
|
+
|
|
26
|
+
def do_it
|
|
27
|
+
arel = Arel.sql_to_arel SQL
|
|
28
|
+
enhanced_arel = Arel.enhance(arel)
|
|
29
|
+
|
|
30
|
+
enhanced_arel.query(class: Arel::Attributes::Attribute).each do |node|
|
|
31
|
+
node.replace node
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
enhanced_arel
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Warm up
|
|
38
|
+
do_it
|
|
39
|
+
|
|
40
|
+
profile = StackProf.run(mode: :wall, raw: true) { do_it }
|
|
41
|
+
File.write('stackprof.json', JSON.generate(profile))
|
|
42
|
+
|
|
43
|
+
report = MemoryProfiler.report { do_it }
|
|
44
|
+
puts report.pretty_print
|
|
45
|
+
|
|
46
|
+
n = 100
|
|
47
|
+
|
|
48
|
+
Benchmark.bm do |benchmark|
|
|
49
|
+
benchmark.report do
|
|
50
|
+
n.times do
|
|
51
|
+
do_it
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'mkmf'
|
|
2
|
+
require 'pg'
|
|
3
|
+
|
|
4
|
+
CONFIG['debugflags'] = '-ggdb3'
|
|
5
|
+
CONFIG['optflags'] = '-O0'
|
|
6
|
+
|
|
7
|
+
# https://github.com/jeremyevans/sequel_pg/blob/master/ext/sequel_pg/extconf.rb
|
|
8
|
+
|
|
9
|
+
pg_include_dir = ENV['POSTGRES_INCLUDE'] ||
|
|
10
|
+
(begin
|
|
11
|
+
IO.popen('pg_config --includedir').readline.chomp
|
|
12
|
+
rescue StandardError
|
|
13
|
+
nil
|
|
14
|
+
end)
|
|
15
|
+
pg_lib_dir = ENV['POSTGRES_LIB'] ||
|
|
16
|
+
(begin
|
|
17
|
+
IO.popen('pg_config --libdir').readline.chomp
|
|
18
|
+
rescue StandardError
|
|
19
|
+
nil
|
|
20
|
+
end)
|
|
21
|
+
|
|
22
|
+
dir_config(
|
|
23
|
+
'pg',
|
|
24
|
+
pg_include_dir,
|
|
25
|
+
pg_lib_dir,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
pg_ext = Gem.loaded_specs.fetch('pg')
|
|
29
|
+
pg_ext_inlude_dir = File.join(pg_ext.full_gem_path, 'ext')
|
|
30
|
+
pg_ext_lib_dir = pg_ext.extension_dir
|
|
31
|
+
|
|
32
|
+
dir_config(
|
|
33
|
+
'pg_ext',
|
|
34
|
+
pg_ext_inlude_dir,
|
|
35
|
+
pg_ext_lib_dir,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if (
|
|
39
|
+
have_library('pq') ||
|
|
40
|
+
have_library('libpq') ||
|
|
41
|
+
have_library('ms/libpq')
|
|
42
|
+
) &&
|
|
43
|
+
have_header('libpq-fe.h') &&
|
|
44
|
+
have_header('pg.h') &&
|
|
45
|
+
have_func('PQcopyResult') &&
|
|
46
|
+
have_func('PQsetResultAttrs') &&
|
|
47
|
+
have_func('PQsetvalue')
|
|
48
|
+
|
|
49
|
+
create_makefile('arel_toolkit/pg_result_init')
|
|
50
|
+
else
|
|
51
|
+
abort 'Could not find PostgreSQL build environment (libraries & headers): Makefile not created'
|
|
52
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#include <libpq-fe.h>
|
|
2
|
+
#include <pg.h>
|
|
3
|
+
#include "pg_result_init.h"
|
|
4
|
+
|
|
5
|
+
VALUE rb_mPgResultInit;
|
|
6
|
+
|
|
7
|
+
static VALUE
|
|
8
|
+
pg_result_init_create(VALUE self, VALUE rb_pgconn, VALUE rb_result, VALUE rb_columns, VALUE rb_rows) {
|
|
9
|
+
Check_Type(rb_columns, T_ARRAY);
|
|
10
|
+
Check_Type(rb_rows, T_ARRAY);
|
|
11
|
+
|
|
12
|
+
if (!rb_obj_is_kind_of(rb_result, rb_cPGresult)) {
|
|
13
|
+
rb_raise(
|
|
14
|
+
rb_eTypeError,
|
|
15
|
+
"wrong argument type %s (expected kind of PG::Result)",
|
|
16
|
+
rb_obj_classname(rb_result)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!rb_obj_is_kind_of(rb_pgconn, rb_cPGconn)) {
|
|
21
|
+
rb_raise(
|
|
22
|
+
rb_eTypeError,
|
|
23
|
+
"wrong argument type %s (expected kind of PG::Connection)",
|
|
24
|
+
rb_obj_classname(rb_pgconn)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
PGresult *result = pgresult_get(rb_result);
|
|
29
|
+
PGresult *result_copy = PQcopyResult(result, PG_COPYRES_EVENTS | PG_COPYRES_NOTICEHOOKS);
|
|
30
|
+
|
|
31
|
+
int num_columns = RARRAY_LEN(rb_columns);
|
|
32
|
+
PGresAttDesc *attDescs = malloc(num_columns * sizeof(PGresAttDesc));
|
|
33
|
+
|
|
34
|
+
if (attDescs == NULL)
|
|
35
|
+
rb_raise(rb_eRuntimeError, "Cannot allocate memory");
|
|
36
|
+
|
|
37
|
+
int index;
|
|
38
|
+
VALUE rb_column;
|
|
39
|
+
VALUE rb_column_name;
|
|
40
|
+
VALUE rb_table_id;
|
|
41
|
+
VALUE rb_column_id;
|
|
42
|
+
VALUE rb_format;
|
|
43
|
+
VALUE rb_typ_id;
|
|
44
|
+
VALUE rb_typ_len;
|
|
45
|
+
VALUE rb_att_typemod;
|
|
46
|
+
|
|
47
|
+
char *column_name;
|
|
48
|
+
|
|
49
|
+
for(index = 0; index < num_columns; index++) {
|
|
50
|
+
rb_column = rb_ary_entry(rb_columns, index);
|
|
51
|
+
Check_Type(rb_column, T_HASH);
|
|
52
|
+
|
|
53
|
+
rb_column_name = rb_funcall(rb_column, rb_intern("fetch"), 1, ID2SYM(rb_intern("name")));
|
|
54
|
+
|
|
55
|
+
// 0 means unknown tableid
|
|
56
|
+
rb_table_id = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("tableid")), INT2NUM(0));
|
|
57
|
+
|
|
58
|
+
// 0 means unknown columnid
|
|
59
|
+
rb_column_id = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("columnid")), INT2NUM(0));
|
|
60
|
+
|
|
61
|
+
// 0 is text, 1 is binary https://www.postgresql.org/docs/10/libpq-exec.html
|
|
62
|
+
rb_format = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("format")), INT2NUM(0));
|
|
63
|
+
|
|
64
|
+
rb_typ_id = rb_funcall(rb_column, rb_intern("fetch"), 1, ID2SYM(rb_intern("typid")));
|
|
65
|
+
rb_typ_len = rb_funcall(rb_column, rb_intern("fetch"), 1, ID2SYM(rb_intern("typlen")));
|
|
66
|
+
|
|
67
|
+
// -1 means that there is no type modifier
|
|
68
|
+
rb_att_typemod = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("atttypmod")), INT2NUM(-1));
|
|
69
|
+
|
|
70
|
+
// Using StringValueCStr, if column contains null bytes it should raise Argument error
|
|
71
|
+
// postgres does not handle null bytes.
|
|
72
|
+
column_name = StringValueCStr(rb_column_name);
|
|
73
|
+
|
|
74
|
+
// postgres/src/interfaces/libpq/libpq-fe.h:235
|
|
75
|
+
attDescs[index].name = column_name;
|
|
76
|
+
attDescs[index].tableid = NUM2INT(rb_table_id);
|
|
77
|
+
attDescs[index].columnid = NUM2INT(rb_column_id);
|
|
78
|
+
attDescs[index].format = NUM2INT(rb_format);
|
|
79
|
+
attDescs[index].typid = NUM2INT(rb_typ_id);
|
|
80
|
+
attDescs[index].typlen = NUM2INT(rb_typ_len);
|
|
81
|
+
attDescs[index].atttypmod = NUM2INT(rb_att_typemod);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
int success;
|
|
85
|
+
|
|
86
|
+
success = PQsetResultAttrs(result_copy, num_columns, attDescs);
|
|
87
|
+
|
|
88
|
+
if (success == 0)
|
|
89
|
+
rb_raise(rb_eRuntimeError, "PQsetResultAttrs failed: %d", success);
|
|
90
|
+
|
|
91
|
+
free(attDescs);
|
|
92
|
+
|
|
93
|
+
int num_rows = RARRAY_LEN(rb_rows);
|
|
94
|
+
int row_index;
|
|
95
|
+
int column_index;
|
|
96
|
+
VALUE rb_row;
|
|
97
|
+
VALUE rb_value;
|
|
98
|
+
char *value;
|
|
99
|
+
int value_len;
|
|
100
|
+
|
|
101
|
+
for(row_index = 0; row_index < num_rows; row_index++) {
|
|
102
|
+
for(column_index = 0; column_index < num_columns; column_index++) {
|
|
103
|
+
rb_row = rb_ary_entry(rb_rows, row_index);
|
|
104
|
+
rb_value = rb_ary_entry(rb_row, column_index);
|
|
105
|
+
|
|
106
|
+
if (NIL_P(rb_value)) {
|
|
107
|
+
success = PQsetvalue(result_copy, row_index, column_index, NULL, -1);
|
|
108
|
+
|
|
109
|
+
if (success == 0)
|
|
110
|
+
rb_raise(rb_eRuntimeError, "PQsetvalue failed: %d", success);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
rb_value = rb_funcall(rb_value, rb_intern("to_s"), 0);
|
|
114
|
+
|
|
115
|
+
// Using StringValueCStr, if column contains null bytes it should raise Argument error
|
|
116
|
+
// postgres does not handle null bytes.
|
|
117
|
+
value = StringValueCStr(rb_value);
|
|
118
|
+
value_len = RSTRING_LEN(rb_value);
|
|
119
|
+
|
|
120
|
+
success = PQsetvalue(result_copy, row_index, column_index, value, value_len);
|
|
121
|
+
|
|
122
|
+
if (success == 0)
|
|
123
|
+
rb_raise(rb_eRuntimeError, "PQsetvalue failed: %d", success);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
VALUE rb_pgresult = pg_new_result(result_copy, rb_pgconn);
|
|
129
|
+
return rb_pgresult;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void
|
|
133
|
+
Init_pg_result_init(void)
|
|
134
|
+
{
|
|
135
|
+
rb_mPgResultInit = rb_define_module("PgResultInit");
|
|
136
|
+
|
|
137
|
+
rb_define_module_function(rb_mPgResultInit, "create", pg_result_init_create, 4);
|
|
138
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ..
|
|
3
|
+
specs:
|
|
4
|
+
arel_toolkit (0.4.5)
|
|
5
|
+
activerecord
|
|
6
|
+
pg (~> 1.1.4)
|
|
7
|
+
pg_query (~> 1.3)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
activemodel (6.0.0)
|
|
13
|
+
activesupport (= 6.0.0)
|
|
14
|
+
activerecord (6.0.0)
|
|
15
|
+
activemodel (= 6.0.0)
|
|
16
|
+
activesupport (= 6.0.0)
|
|
17
|
+
activesupport (6.0.0)
|
|
18
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
19
|
+
i18n (>= 0.7, < 2)
|
|
20
|
+
minitest (~> 5.1)
|
|
21
|
+
tzinfo (~> 1.1)
|
|
22
|
+
zeitwerk (~> 2.1, >= 2.1.8)
|
|
23
|
+
addressable (2.7.0)
|
|
24
|
+
public_suffix (>= 2.0.2, < 5.0)
|
|
25
|
+
ansi (1.5.0)
|
|
26
|
+
appraisal (2.2.0)
|
|
27
|
+
bundler
|
|
28
|
+
rake
|
|
29
|
+
thor (>= 0.14.0)
|
|
30
|
+
approvals (0.0.24)
|
|
31
|
+
nokogiri (~> 1.6)
|
|
32
|
+
thor (~> 0.18)
|
|
33
|
+
ast (2.4.1)
|
|
34
|
+
binding_of_caller (0.8.0)
|
|
35
|
+
debug_inspector (>= 0.0.1)
|
|
36
|
+
coderay (1.1.3)
|
|
37
|
+
concurrent-ruby (1.1.7)
|
|
38
|
+
database_cleaner (1.7.0)
|
|
39
|
+
debug_inspector (0.0.3)
|
|
40
|
+
diff-lcs (1.4.4)
|
|
41
|
+
docile (1.3.2)
|
|
42
|
+
dpl (1.10.15)
|
|
43
|
+
faraday (1.1.0)
|
|
44
|
+
multipart-post (>= 1.2, < 3)
|
|
45
|
+
ruby2_keywords
|
|
46
|
+
faraday-http-cache (2.2.0)
|
|
47
|
+
faraday (>= 0.8)
|
|
48
|
+
ffi (1.13.1)
|
|
49
|
+
formatador (0.2.5)
|
|
50
|
+
github_changelog_generator (1.15.2)
|
|
51
|
+
activesupport
|
|
52
|
+
faraday-http-cache
|
|
53
|
+
multi_json
|
|
54
|
+
octokit (~> 4.6)
|
|
55
|
+
rainbow (>= 2.2.1)
|
|
56
|
+
rake (>= 10.0)
|
|
57
|
+
retriable (~> 3.0)
|
|
58
|
+
guard (2.16.2)
|
|
59
|
+
formatador (>= 0.2.4)
|
|
60
|
+
listen (>= 2.7, < 4.0)
|
|
61
|
+
lumberjack (>= 1.0.12, < 2.0)
|
|
62
|
+
nenv (~> 0.1)
|
|
63
|
+
notiffany (~> 0.0)
|
|
64
|
+
pry (>= 0.9.12)
|
|
65
|
+
shellany (~> 0.0)
|
|
66
|
+
thor (>= 0.18.1)
|
|
67
|
+
guard-compat (1.2.1)
|
|
68
|
+
guard-rake (1.0.0)
|
|
69
|
+
guard
|
|
70
|
+
rake
|
|
71
|
+
guard-rspec (4.7.3)
|
|
72
|
+
guard (~> 2.1)
|
|
73
|
+
guard-compat (~> 1.1)
|
|
74
|
+
rspec (>= 2.99.0, < 4.0)
|
|
75
|
+
guard-rubocop (1.3.0)
|
|
76
|
+
guard (~> 2.0)
|
|
77
|
+
rubocop (~> 0.20)
|
|
78
|
+
hirb (0.7.3)
|
|
79
|
+
i18n (1.8.5)
|
|
80
|
+
concurrent-ruby (~> 1.0)
|
|
81
|
+
interception (0.5)
|
|
82
|
+
jaro_winkler (1.5.4)
|
|
83
|
+
json (2.3.1)
|
|
84
|
+
listen (3.2.1)
|
|
85
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
86
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
|
87
|
+
lumberjack (1.2.8)
|
|
88
|
+
memory_profiler (0.9.14)
|
|
89
|
+
method_source (0.9.2)
|
|
90
|
+
mini_portile2 (2.4.0)
|
|
91
|
+
minitest (5.14.2)
|
|
92
|
+
multi_json (1.15.0)
|
|
93
|
+
multipart-post (2.1.1)
|
|
94
|
+
nenv (0.3.0)
|
|
95
|
+
nokogiri (1.10.10)
|
|
96
|
+
mini_portile2 (~> 2.4.0)
|
|
97
|
+
notiffany (0.1.3)
|
|
98
|
+
nenv (~> 0.1)
|
|
99
|
+
shellany (~> 0.0)
|
|
100
|
+
octokit (4.19.0)
|
|
101
|
+
faraday (>= 0.9)
|
|
102
|
+
sawyer (~> 0.8.0, >= 0.5.3)
|
|
103
|
+
parallel (1.19.2)
|
|
104
|
+
parser (2.7.2.0)
|
|
105
|
+
ast (~> 2.4.1)
|
|
106
|
+
pg (1.1.4)
|
|
107
|
+
pg_query (1.3.0)
|
|
108
|
+
pry (0.12.2)
|
|
109
|
+
coderay (~> 1.1.0)
|
|
110
|
+
method_source (~> 0.9.0)
|
|
111
|
+
pry-alias (0.0.1)
|
|
112
|
+
binding_of_caller
|
|
113
|
+
pry
|
|
114
|
+
pry-doc (1.1.0)
|
|
115
|
+
pry (~> 0.11)
|
|
116
|
+
yard (~> 0.9.11)
|
|
117
|
+
pry-nav (0.3.0)
|
|
118
|
+
pry (>= 0.9.10, < 0.13.0)
|
|
119
|
+
pry-rescue (1.5.2)
|
|
120
|
+
interception (>= 0.5)
|
|
121
|
+
pry (>= 0.12.0)
|
|
122
|
+
pry-stack_explorer (0.4.9.3)
|
|
123
|
+
binding_of_caller (>= 0.7)
|
|
124
|
+
pry (>= 0.9.11)
|
|
125
|
+
public_suffix (4.0.6)
|
|
126
|
+
rainbow (3.0.0)
|
|
127
|
+
rake (13.0.1)
|
|
128
|
+
rake-compiler (1.1.1)
|
|
129
|
+
rake
|
|
130
|
+
rb-fsevent (0.10.4)
|
|
131
|
+
rb-inotify (0.10.1)
|
|
132
|
+
ffi (~> 1.0)
|
|
133
|
+
retriable (3.1.2)
|
|
134
|
+
rspec (3.9.0)
|
|
135
|
+
rspec-core (~> 3.9.0)
|
|
136
|
+
rspec-expectations (~> 3.9.0)
|
|
137
|
+
rspec-mocks (~> 3.9.0)
|
|
138
|
+
rspec-core (3.9.3)
|
|
139
|
+
rspec-support (~> 3.9.3)
|
|
140
|
+
rspec-expectations (3.9.3)
|
|
141
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
142
|
+
rspec-support (~> 3.9.0)
|
|
143
|
+
rspec-mocks (3.9.1)
|
|
144
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
145
|
+
rspec-support (~> 3.9.0)
|
|
146
|
+
rspec-support (3.9.4)
|
|
147
|
+
rubocop (0.71.0)
|
|
148
|
+
jaro_winkler (~> 1.5.1)
|
|
149
|
+
parallel (~> 1.10)
|
|
150
|
+
parser (>= 2.6)
|
|
151
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
152
|
+
ruby-progressbar (~> 1.7)
|
|
153
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
|
154
|
+
ruby-progressbar (1.10.1)
|
|
155
|
+
ruby2_keywords (0.0.2)
|
|
156
|
+
sawyer (0.8.2)
|
|
157
|
+
addressable (>= 2.3.5)
|
|
158
|
+
faraday (> 0.8, < 2.0)
|
|
159
|
+
shellany (0.0.1)
|
|
160
|
+
simplecov (0.16.1)
|
|
161
|
+
docile (~> 1.1)
|
|
162
|
+
json (>= 1.8, < 3)
|
|
163
|
+
simplecov-html (~> 0.10.0)
|
|
164
|
+
simplecov-console (0.4.2)
|
|
165
|
+
ansi
|
|
166
|
+
hirb
|
|
167
|
+
simplecov
|
|
168
|
+
simplecov-html (0.10.2)
|
|
169
|
+
stackprof (0.2.16)
|
|
170
|
+
thor (0.20.3)
|
|
171
|
+
thread_safe (0.3.6)
|
|
172
|
+
tzinfo (1.2.7)
|
|
173
|
+
thread_safe (~> 0.1)
|
|
174
|
+
unicode-display_width (1.6.1)
|
|
175
|
+
yard (0.9.25)
|
|
176
|
+
zeitwerk (2.4.0)
|
|
177
|
+
|
|
178
|
+
PLATFORMS
|
|
179
|
+
ruby
|
|
180
|
+
|
|
181
|
+
DEPENDENCIES
|
|
182
|
+
activerecord (~> 6.0.0)
|
|
183
|
+
appraisal (~> 2.2.0)
|
|
184
|
+
approvals (~> 0.0.24)
|
|
185
|
+
arel_toolkit!
|
|
186
|
+
bundler (~> 2.0)
|
|
187
|
+
database_cleaner (~> 1.7.0)
|
|
188
|
+
dpl (~> 1.10.11)
|
|
189
|
+
github_changelog_generator (~> 1.15)
|
|
190
|
+
guard (~> 2.15)
|
|
191
|
+
guard-rake (~> 1.0.0)
|
|
192
|
+
guard-rspec (~> 4.7)
|
|
193
|
+
guard-rubocop (~> 1.3.0)
|
|
194
|
+
memory_profiler (~> 0.9)
|
|
195
|
+
pry
|
|
196
|
+
pry-alias
|
|
197
|
+
pry-doc
|
|
198
|
+
pry-nav
|
|
199
|
+
pry-rescue
|
|
200
|
+
pry-stack_explorer
|
|
201
|
+
rake (~> 13.0)
|
|
202
|
+
rake-compiler (~> 1.0)
|
|
203
|
+
rspec (~> 3.8)
|
|
204
|
+
rubocop (= 0.71.0)
|
|
205
|
+
simplecov (~> 0.16.1)
|
|
206
|
+
simplecov-console (~> 0.4.2)
|
|
207
|
+
stackprof (~> 0.2)
|
|
208
|
+
|
|
209
|
+
BUNDLED WITH
|
|
210
|
+
2.2.19
|