awesome_explain 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/mongodb.yml +53 -0
- data/.github/workflows/postgres.yml +56 -0
- data/.gitignore +11 -0
- data/Appraisals +11 -0
- data/Gemfile.lock +209 -49
- data/LICENSE.txt +4 -20
- data/README.md +155 -7
- data/Rakefile +35 -1
- data/app/models/awesome_explain/application_record.rb +5 -0
- data/app/models/awesome_explain/controller.rb +20 -0
- data/app/models/awesome_explain/delayed_job.rb +7 -0
- data/app/models/awesome_explain/explain.rb +23 -0
- data/app/models/awesome_explain/log.rb +7 -0
- data/app/models/awesome_explain/pg_dml_stat.rb +4 -0
- data/app/models/awesome_explain/pg_seq_scan.rb +4 -0
- data/app/models/awesome_explain/plan_node.rb +52 -0
- data/app/models/awesome_explain/plan_tree.rb +66 -0
- data/app/models/awesome_explain/sidekiq_worker.rb +7 -0
- data/app/models/awesome_explain/sql_explain.rb +14 -0
- data/app/models/awesome_explain/sql_plan_node.rb +73 -0
- data/app/models/awesome_explain/sql_plan_stats.rb +34 -0
- data/app/models/awesome_explain/sql_plan_tree.rb +133 -0
- data/app/models/awesome_explain/sql_query.rb +7 -0
- data/app/models/awesome_explain/stacktrace.rb +11 -0
- data/awesome_explain.gemspec +16 -5
- data/bin/rails +14 -0
- data/data/mongodb/customers.bson +0 -0
- data/data/mongodb/customers.metadata.json +1 -0
- data/data/mongodb/line_items.bson +0 -0
- data/data/mongodb/line_items.metadata.json +1 -0
- data/data/mongodb/orders.bson +0 -0
- data/data/mongodb/orders.metadata.json +1 -0
- data/data/mongodb/products.bson +0 -0
- data/data/mongodb/products.metadata.json +1 -0
- data/data/postgresql/dvdrental.tar +0 -0
- data/db/migrate/20200507214801_stacktraces.rb +12 -0
- data/db/migrate/20200507214949_controllers.rb +16 -0
- data/db/migrate/20200507215205_logs.rb +22 -0
- data/db/migrate/20200507215243_explains.rb +27 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +208 -0
- data/gemfiles/rails_5.gemfile +7 -0
- data/gemfiles/rails_5.gemfile.lock +209 -0
- data/gemfiles/rails_6.gemfile +7 -0
- data/gemfiles/rails_6.gemfile.lock +233 -0
- data/images/universe.png +0 -0
- data/lib/awesome_explain.rb +79 -2
- data/lib/awesome_explain/config.rb +196 -0
- data/lib/awesome_explain/engine.rb +5 -0
- data/lib/awesome_explain/insights/active_record_insights.rb +137 -0
- data/lib/awesome_explain/insights/base.rb +18 -0
- data/lib/awesome_explain/insights/mongoid_insights.rb +44 -0
- data/lib/awesome_explain/insights/sql_plans_insights.rb +64 -0
- data/lib/awesome_explain/kernel.rb +17 -0
- data/lib/awesome_explain/mongodb/base.rb +4 -0
- data/lib/awesome_explain/mongodb/command_start.rb +84 -0
- data/lib/awesome_explain/mongodb/command_success.rb +58 -0
- data/lib/awesome_explain/mongodb/formatter.rb +62 -0
- data/lib/awesome_explain/mongodb/helpers.rb +71 -0
- data/lib/awesome_explain/queue/command.rb +17 -0
- data/lib/awesome_explain/queue/simple_queue.rb +88 -0
- data/lib/awesome_explain/renderers/active_record.rb +114 -0
- data/lib/awesome_explain/renderers/base.rb +2 -0
- data/lib/awesome_explain/renderers/mongoid.rb +20 -33
- data/lib/awesome_explain/sidekiq_middleware.rb +17 -0
- data/lib/awesome_explain/stats/postgresql.rb +54 -0
- data/lib/awesome_explain/subscribers/active_record_passive_subscriber.rb +82 -0
- data/lib/awesome_explain/subscribers/active_record_subscriber.rb +187 -0
- data/lib/awesome_explain/subscribers/base.rb +3 -0
- data/lib/awesome_explain/subscribers/command_subscriber.rb +53 -0
- data/lib/awesome_explain/tasks/db.rb +325 -0
- data/lib/awesome_explain/utils/color.rb +16 -0
- data/lib/awesome_explain/version.rb +1 -1
- data/lib/tasks/ae.rake +28 -0
- data/lib/tasks/awesome_explain_tasks.rake +4 -0
- metadata +242 -25
- data/.travis.yml +0 -19
@@ -0,0 +1,73 @@
|
|
1
|
+
class AwesomeExplain::SqlPlanNode
|
2
|
+
attr_accessor :id,
|
3
|
+
:parent,
|
4
|
+
:children,
|
5
|
+
:label,
|
6
|
+
:type,
|
7
|
+
:relation_name,
|
8
|
+
:join_type,
|
9
|
+
:startup_cost,
|
10
|
+
:total_cost,
|
11
|
+
:rows,
|
12
|
+
:width,
|
13
|
+
:actual_startup_time,
|
14
|
+
:actual_total_time,
|
15
|
+
:actual_rows,
|
16
|
+
:actual_loops,
|
17
|
+
:recheck_condition,
|
18
|
+
:index_name,
|
19
|
+
:index_condition,
|
20
|
+
:seq_scan,
|
21
|
+
:total_rows,
|
22
|
+
:total_loops
|
23
|
+
|
24
|
+
alias :seq_scan? :seq_scan
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@total_rows = 0
|
28
|
+
@total_loops = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.build(data, parent = nil)
|
32
|
+
instance = self.new
|
33
|
+
instance.label = data.dig('Node Type')
|
34
|
+
instance.type = data.dig('Node Type')
|
35
|
+
instance.relation_name = data.dig('Relation Name')
|
36
|
+
instance.startup_cost = data.dig('Startup Cost')
|
37
|
+
instance.total_cost = data.dig('Total Cost')
|
38
|
+
instance.rows = data.dig('Plan Rows')
|
39
|
+
instance.width = data.dig('Plan Width')
|
40
|
+
instance.actual_startup_time = data.dig('Actual Startup Time')
|
41
|
+
instance.actual_total_time = data.dig('Actual Total Time')
|
42
|
+
instance.actual_rows = data.dig('Actual Rows')
|
43
|
+
instance.actual_loops = data.dig('Actual Loops')
|
44
|
+
instance.recheck_condition = data.dig('Recheck Cond')
|
45
|
+
instance.index_name = data.dig('Index Name')
|
46
|
+
instance.index_condition = data.dig('Index Cond')
|
47
|
+
instance.seq_scan = data.dig('Node Type') == 'Seq Scan'
|
48
|
+
instance.parent = parent
|
49
|
+
instance.children = []
|
50
|
+
instance
|
51
|
+
end
|
52
|
+
|
53
|
+
def meta_data_str
|
54
|
+
meta_data.join('<hr />')
|
55
|
+
end
|
56
|
+
|
57
|
+
def meta_data
|
58
|
+
data = []
|
59
|
+
data << "<strong>Join Type:</strong> #{join_type}" if join_type.present?
|
60
|
+
data << "<strong>Rows:</strong> #{rows}" if rows.present?
|
61
|
+
data << "<strong>Width:</strong> #{width}" if width.present?
|
62
|
+
data << "<span #{seq_scan? ? 'class="bg-red-200 text-red-900 px-1 py-1 rounded-r"' : ''}><strong>Seq Scan:</strong> #{seq_scan?}</span>"
|
63
|
+
data << "<strong>Index Name</strong> #{index_name}" if index_name.present?
|
64
|
+
data << "<strong>Index Condition</strong> #{index_condition}" if index_condition.present?
|
65
|
+
data << "<strong>Actual Rows</strong> #{actual_rows}" if actual_rows.present?
|
66
|
+
data << "<strong>Actual Loops</strong> #{actual_loops}" if actual_loops.present?
|
67
|
+
data << "<strong>Startup Cost</strong> #{startup_cost}" if startup_cost.present?
|
68
|
+
data << "<strong>Total Cost</strong> #{total_cost}" if total_cost.present?
|
69
|
+
data << "<strong>Actual Startup Time</strong> #{actual_startup_time}" if actual_startup_time.present?
|
70
|
+
data << "<strong>Actual Total Time</strong> #{actual_total_time}" if actual_total_time.present?
|
71
|
+
data
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class AwesomeExplain::SqlPlanStats
|
2
|
+
attr_accessor :table_stats,
|
3
|
+
:node_type_stats,
|
4
|
+
:index_stats,
|
5
|
+
:total_rows_planned,
|
6
|
+
:total_rows,
|
7
|
+
:total_loops,
|
8
|
+
:actual_total_time,
|
9
|
+
:seq_scans
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@table_stats = {}
|
13
|
+
@node_type_stats = {}
|
14
|
+
@index_stats = {}
|
15
|
+
@total_rows_planned = 0
|
16
|
+
@total_rows = 0
|
17
|
+
@total_loops = 0
|
18
|
+
@actual_total_time = 0
|
19
|
+
@seq_scans = 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def indexes?
|
23
|
+
!@index_stats.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
{
|
28
|
+
table_stats: @table_stats,
|
29
|
+
node_type_stats: @node_type_stats,
|
30
|
+
index_stats: @index_stats,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
alias :to_h :to_hash
|
34
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class AwesomeExplain::SqlPlanTree
|
2
|
+
attr_accessor :root,
|
3
|
+
:ids,
|
4
|
+
:plans_count,
|
5
|
+
:seq_scan,
|
6
|
+
:seq_scans,
|
7
|
+
:startup_cost,
|
8
|
+
:total_cost,
|
9
|
+
:rows,
|
10
|
+
:width,
|
11
|
+
:actual_startup_time,
|
12
|
+
:actual_total_time,
|
13
|
+
:actual_rows,
|
14
|
+
:actual_loops,
|
15
|
+
:plan_stats
|
16
|
+
|
17
|
+
alias :seq_scan? :seq_scan
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@startup_cost = 0
|
21
|
+
@total_cost = 0
|
22
|
+
@rows = 0
|
23
|
+
@width = 0
|
24
|
+
@actual_startup_time = 0
|
25
|
+
@actual_total_time = 0
|
26
|
+
@actual_rows = 0
|
27
|
+
@actual_loops = 0
|
28
|
+
@seq_scans = 0
|
29
|
+
@plan_stats = ::AwesomeExplain::SqlPlanStats.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.build(plan)
|
33
|
+
tree = self.new
|
34
|
+
tree.ids = (2..500).to_a # Ugh!!!
|
35
|
+
root = ::AwesomeExplain::SqlPlanNode.build(plan.first.dig('Plan'))
|
36
|
+
tree.root = root
|
37
|
+
tree.update_tree_stats(root)
|
38
|
+
root.id = 1
|
39
|
+
tree.plans_count = 1
|
40
|
+
build_recursive(plan.first.dig('Plan', 'Plans'), root, tree)
|
41
|
+
tree
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.build_recursive(data, parent, tree)
|
45
|
+
return unless data.present?
|
46
|
+
|
47
|
+
if data.is_a?(Array)
|
48
|
+
data.each do |plan|
|
49
|
+
build_recursive(plan, parent, tree)
|
50
|
+
end
|
51
|
+
elsif data.is_a?(Hash) && data.dig('Plans').present?
|
52
|
+
node = ::AwesomeExplain::SqlPlanNode.build(data, parent)
|
53
|
+
node.id = tree.ids.shift
|
54
|
+
parent.children << node
|
55
|
+
tree.plans_count += 1
|
56
|
+
tree.seq_scans += 1 if node.seq_scan?
|
57
|
+
tree.update_tree_stats(node)
|
58
|
+
build_recursive(data.dig('Plans'), node, tree)
|
59
|
+
elsif data.is_a?(Hash) && data.dig('Plans').nil?
|
60
|
+
node = ::AwesomeExplain::SqlPlanNode.build(data, parent)
|
61
|
+
tree.update_tree_stats(node)
|
62
|
+
node.id = tree.ids.shift
|
63
|
+
tree.plans_count += 1
|
64
|
+
tree.seq_scans += 1 if node.seq_scan?
|
65
|
+
parent.children << node
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def treeviz
|
70
|
+
return unless root.present?
|
71
|
+
output = []
|
72
|
+
queue = [root]
|
73
|
+
while(!queue.empty?) do
|
74
|
+
node = queue.shift
|
75
|
+
output << node.treeviz
|
76
|
+
node.children.each do |child|
|
77
|
+
queue << child
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
output
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_tree_stats(node)
|
85
|
+
self.startup_cost += node.startup_cost
|
86
|
+
self.total_cost += node.total_cost
|
87
|
+
self.rows += node.rows
|
88
|
+
self.width += node.width
|
89
|
+
self.actual_startup_time += node.actual_startup_time
|
90
|
+
self.actual_total_time += node.actual_total_time
|
91
|
+
self.actual_rows += node.actual_rows
|
92
|
+
self.actual_loops += node.actual_loops
|
93
|
+
|
94
|
+
# Plan Stats
|
95
|
+
plan_stats.total_rows_planned += node.rows
|
96
|
+
plan_stats.total_rows += node.actual_rows
|
97
|
+
plan_stats.total_loops += node.actual_loops
|
98
|
+
plan_stats.seq_scans += 1 if node.seq_scan?
|
99
|
+
|
100
|
+
relation_name = node.relation_name
|
101
|
+
if relation_name
|
102
|
+
if plan_stats.table_stats.dig(relation_name).nil?
|
103
|
+
plan_stats.table_stats[relation_name] = {
|
104
|
+
count: 0,
|
105
|
+
time: 0
|
106
|
+
}
|
107
|
+
end
|
108
|
+
plan_stats.table_stats[relation_name][:count] += 1
|
109
|
+
plan_stats.table_stats[relation_name][:time] += node.actual_total_time
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
node_type = node.type
|
114
|
+
if node_type
|
115
|
+
if plan_stats.node_type_stats.dig(node_type).nil?
|
116
|
+
plan_stats.node_type_stats[node_type] = {
|
117
|
+
count: 0
|
118
|
+
}
|
119
|
+
end
|
120
|
+
plan_stats.node_type_stats[node_type][:count] += 1
|
121
|
+
end
|
122
|
+
|
123
|
+
index_name = node.index_name
|
124
|
+
if index_name
|
125
|
+
if plan_stats.index_stats.dig(index_name).nil?
|
126
|
+
plan_stats.index_stats[index_name] = {
|
127
|
+
count: 0
|
128
|
+
}
|
129
|
+
plan_stats.index_stats[index_name][:count] += 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/awesome_explain.gemspec
CHANGED
@@ -22,11 +22,22 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.add_dependency 'awesome_print', '~> 1.0'
|
24
24
|
spec.add_dependency 'terminal-table', '~> 1.0'
|
25
|
+
spec.add_dependency 'sqlite3'
|
26
|
+
spec.add_dependency 'rails', '>= 4.2'
|
27
|
+
spec.add_dependency 'kaminari', '>= 1.0'
|
28
|
+
spec.add_dependency 'activerecord-import', '>= 0.25'
|
29
|
+
spec.add_dependency 'niceql'
|
30
|
+
spec.add_dependency 'pg'
|
25
31
|
|
26
|
-
spec.add_development_dependency 'bundler'
|
32
|
+
spec.add_development_dependency 'bundler'
|
33
|
+
spec.add_development_dependency 'appraisal'
|
34
|
+
spec.add_development_dependency 'wwtd'
|
35
|
+
spec.add_development_dependency 'binding_of_caller'
|
36
|
+
spec.add_development_dependency 'pry-byebug'
|
37
|
+
spec.add_development_dependency 'pry-rails'
|
27
38
|
spec.add_development_dependency 'mongoid', '>= 5'
|
28
|
-
spec.add_development_dependency 'rake', '
|
29
|
-
spec.add_development_dependency 'rspec', '
|
30
|
-
spec.add_development_dependency 'simplecov
|
31
|
-
spec.add_development_dependency 'simplecov', '
|
39
|
+
spec.add_development_dependency 'rake', '>= 10.0'
|
40
|
+
spec.add_development_dependency 'rspec', '>= 3.10'
|
41
|
+
spec.add_development_dependency 'simplecov', '>= 0.21.2'
|
42
|
+
spec.add_development_dependency 'simplecov-console', '>= 0.9.1'
|
32
43
|
end
|
data/bin/rails
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/awesome_explain/engine', __dir__)
|
7
|
+
APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
|
8
|
+
|
9
|
+
# Set up gems listed in the Gemfile.
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
11
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
12
|
+
|
13
|
+
require 'rails/all'
|
14
|
+
require 'rails/engine/commands'
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"OrderExample.customers"},{"v":2,"key":{"email":1.0},"name":"email_1","ns":"OrderExample.customers"}],"uuid":"290a27943cbe49c08136a6ef9602c089"}
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"OrderExample.line_items"},{"v":2,"key":{"orderId":1.0},"name":"orderId_1","ns":"OrderExample.line_items"},{"v":2,"key":{"prodId":1.0},"name":"prodId_1","ns":"OrderExample.line_items"}],"uuid":"77924a3990304623881e6b748abf20e7"}
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"OrderExample.orders"},{"v":2,"key":{"customerId":1.0},"name":"customerId_1","ns":"OrderExample.orders"}],"uuid":"e4d9c8ca50ae48888c16a8906d7d97de"}
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"OrderExample.products"}],"uuid":"4067ccb6647b4f32aeeb239fbd4f6183"}
|
Binary file
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Stacktraces < ActiveRecord::Migration[ActiveRecord.version.to_s.to_f]
|
2
|
+
def connection
|
3
|
+
ActiveRecord::Base.establish_connection(AwesomeExplain::Config.instance.db_config).connection
|
4
|
+
end
|
5
|
+
|
6
|
+
def change
|
7
|
+
create_table :stacktraces do |t|
|
8
|
+
t.column :stacktrace, :string
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Controllers < ActiveRecord::Migration[ActiveRecord.version.to_s.to_f]
|
2
|
+
def connection
|
3
|
+
ActiveRecord::Base.establish_connection(AwesomeExplain::Config.instance.db_config).connection
|
4
|
+
end
|
5
|
+
|
6
|
+
def change
|
7
|
+
create_table :controllers do |t|
|
8
|
+
t.column :name, :string
|
9
|
+
t.column :action, :string
|
10
|
+
t.column :path, :string
|
11
|
+
t.column :params, :string
|
12
|
+
t.column :session_id, :string
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Logs < ActiveRecord::Migration[ActiveRecord.version.to_s.to_f]
|
2
|
+
def connection
|
3
|
+
ActiveRecord::Base.establish_connection(AwesomeExplain::Config.instance.db_config).connection
|
4
|
+
end
|
5
|
+
|
6
|
+
def change
|
7
|
+
create_table :logs do |t|
|
8
|
+
t.column :collection, :string
|
9
|
+
t.column :source_name, :string
|
10
|
+
t.column :operation, :string
|
11
|
+
t.column :collscan, :integer
|
12
|
+
t.column :command, :string
|
13
|
+
t.column :duration, :double
|
14
|
+
t.column :session_id, :string
|
15
|
+
t.column :lsid, :string
|
16
|
+
t.column :stacktrace_id, :integer
|
17
|
+
t.column :explain_id, :integer
|
18
|
+
t.column :controller_id, :integer
|
19
|
+
t.timestamps
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Explains < ActiveRecord::Migration[ActiveRecord.version.to_s.to_f]
|
2
|
+
def connection
|
3
|
+
ActiveRecord::Base.establish_connection(AwesomeExplain::Config.instance.db_config).connection
|
4
|
+
end
|
5
|
+
|
6
|
+
def change
|
7
|
+
create_table :explains do |t|
|
8
|
+
t.column :collection, :string
|
9
|
+
t.column :source_name, :string
|
10
|
+
t.column :command, :string
|
11
|
+
t.column :collscan, :integer
|
12
|
+
t.column :winning_plan, :string
|
13
|
+
t.column :winning_plan_raw, :string
|
14
|
+
t.column :used_indexes, :string
|
15
|
+
t.column :duration, :double
|
16
|
+
t.column :documents_returned, :integer
|
17
|
+
t.column :documents_examined, :integer
|
18
|
+
t.column :keys_examined, :integer
|
19
|
+
t.column :rejected_plans, :integer
|
20
|
+
t.column :session_id, :string
|
21
|
+
t.column :lsid, :string
|
22
|
+
t.column :stacktrace_id, :integer
|
23
|
+
t.column :controller_id, :integer
|
24
|
+
t.timestamps
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
awesome_explain (1.0.0)
|
5
|
+
activerecord-import (>= 0.25)
|
6
|
+
awesome_print (~> 1.0)
|
7
|
+
kaminari (>= 1.0)
|
8
|
+
niceql
|
9
|
+
pg
|
10
|
+
rails (>= 4.2, <= 6.1)
|
11
|
+
sqlite3
|
12
|
+
terminal-table (~> 1.0)
|
13
|
+
|
14
|
+
GEM
|
15
|
+
remote: https://rubygems.org/
|
16
|
+
specs:
|
17
|
+
actionmailer (4.2.0)
|
18
|
+
actionpack (= 4.2.0)
|
19
|
+
actionview (= 4.2.0)
|
20
|
+
activejob (= 4.2.0)
|
21
|
+
mail (~> 2.5, >= 2.5.4)
|
22
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
23
|
+
actionpack (4.2.0)
|
24
|
+
actionview (= 4.2.0)
|
25
|
+
activesupport (= 4.2.0)
|
26
|
+
rack (~> 1.6.0)
|
27
|
+
rack-test (~> 0.6.2)
|
28
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
29
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
30
|
+
actionview (4.2.0)
|
31
|
+
activesupport (= 4.2.0)
|
32
|
+
builder (~> 3.1)
|
33
|
+
erubis (~> 2.7.0)
|
34
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
35
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
36
|
+
activejob (4.2.0)
|
37
|
+
activesupport (= 4.2.0)
|
38
|
+
globalid (>= 0.3.0)
|
39
|
+
activemodel (4.2.0)
|
40
|
+
activesupport (= 4.2.0)
|
41
|
+
builder (~> 3.1)
|
42
|
+
activerecord (4.2.0)
|
43
|
+
activemodel (= 4.2.0)
|
44
|
+
activesupport (= 4.2.0)
|
45
|
+
arel (~> 6.0)
|
46
|
+
activerecord-import (1.0.8)
|
47
|
+
activerecord (>= 3.2)
|
48
|
+
activesupport (4.2.0)
|
49
|
+
i18n (~> 0.7)
|
50
|
+
json (~> 1.7, >= 1.7.7)
|
51
|
+
minitest (~> 5.1)
|
52
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
53
|
+
tzinfo (~> 1.1)
|
54
|
+
ansi (1.5.0)
|
55
|
+
appraisal (2.4.0)
|
56
|
+
bundler
|
57
|
+
rake
|
58
|
+
thor (>= 0.14.0)
|
59
|
+
arel (6.0.4)
|
60
|
+
awesome_print (1.9.2)
|
61
|
+
binding_of_caller (1.0.0)
|
62
|
+
debug_inspector (>= 0.0.1)
|
63
|
+
bson (4.12.0)
|
64
|
+
builder (3.2.4)
|
65
|
+
byebug (11.1.3)
|
66
|
+
coderay (1.1.3)
|
67
|
+
concurrent-ruby (1.1.8)
|
68
|
+
crass (1.0.6)
|
69
|
+
debug_inspector (1.0.0)
|
70
|
+
diff-lcs (1.4.4)
|
71
|
+
docile (1.3.5)
|
72
|
+
erubis (2.7.0)
|
73
|
+
globalid (0.4.2)
|
74
|
+
activesupport (>= 4.2.0)
|
75
|
+
i18n (0.9.5)
|
76
|
+
concurrent-ruby (~> 1.0)
|
77
|
+
json (1.8.6)
|
78
|
+
kaminari (1.2.1)
|
79
|
+
activesupport (>= 4.1.0)
|
80
|
+
kaminari-actionview (= 1.2.1)
|
81
|
+
kaminari-activerecord (= 1.2.1)
|
82
|
+
kaminari-core (= 1.2.1)
|
83
|
+
kaminari-actionview (1.2.1)
|
84
|
+
actionview
|
85
|
+
kaminari-core (= 1.2.1)
|
86
|
+
kaminari-activerecord (1.2.1)
|
87
|
+
activerecord
|
88
|
+
kaminari-core (= 1.2.1)
|
89
|
+
kaminari-core (1.2.1)
|
90
|
+
loofah (2.9.0)
|
91
|
+
crass (~> 1.0.2)
|
92
|
+
nokogiri (>= 1.5.9)
|
93
|
+
mail (2.7.1)
|
94
|
+
mini_mime (>= 0.1.1)
|
95
|
+
method_source (1.0.0)
|
96
|
+
mini_mime (1.0.2)
|
97
|
+
mini_portile2 (2.5.0)
|
98
|
+
minitest (5.14.4)
|
99
|
+
mongo (2.14.0)
|
100
|
+
bson (>= 4.8.2, < 5.0.0)
|
101
|
+
mongoid (5.4.1)
|
102
|
+
activemodel (~> 4.0)
|
103
|
+
mongo (>= 2.5.1, < 3.0.0)
|
104
|
+
origin (~> 2.3)
|
105
|
+
tzinfo (>= 0.3.37)
|
106
|
+
niceql (0.1.25)
|
107
|
+
nokogiri (1.11.2)
|
108
|
+
mini_portile2 (~> 2.5.0)
|
109
|
+
racc (~> 1.4)
|
110
|
+
origin (2.3.1)
|
111
|
+
pg (1.2.3)
|
112
|
+
pry (0.13.1)
|
113
|
+
coderay (~> 1.1)
|
114
|
+
method_source (~> 1.0)
|
115
|
+
pry-byebug (3.9.0)
|
116
|
+
byebug (~> 11.0)
|
117
|
+
pry (~> 0.13.0)
|
118
|
+
pry-rails (0.3.9)
|
119
|
+
pry (>= 0.10.4)
|
120
|
+
racc (1.5.2)
|
121
|
+
rack (1.6.13)
|
122
|
+
rack-test (0.6.3)
|
123
|
+
rack (>= 1.0)
|
124
|
+
rails (4.2.0)
|
125
|
+
actionmailer (= 4.2.0)
|
126
|
+
actionpack (= 4.2.0)
|
127
|
+
actionview (= 4.2.0)
|
128
|
+
activejob (= 4.2.0)
|
129
|
+
activemodel (= 4.2.0)
|
130
|
+
activerecord (= 4.2.0)
|
131
|
+
activesupport (= 4.2.0)
|
132
|
+
bundler (>= 1.3.0, < 2.0)
|
133
|
+
railties (= 4.2.0)
|
134
|
+
sprockets-rails
|
135
|
+
rails-deprecated_sanitizer (1.0.4)
|
136
|
+
activesupport (>= 4.2.0.alpha)
|
137
|
+
rails-dom-testing (1.0.9)
|
138
|
+
activesupport (>= 4.2.0, < 5.0)
|
139
|
+
nokogiri (~> 1.6)
|
140
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
141
|
+
rails-html-sanitizer (1.3.0)
|
142
|
+
loofah (~> 2.3)
|
143
|
+
railties (4.2.0)
|
144
|
+
actionpack (= 4.2.0)
|
145
|
+
activesupport (= 4.2.0)
|
146
|
+
rake (>= 0.8.7)
|
147
|
+
thor (>= 0.18.1, < 2.0)
|
148
|
+
rake (13.0.3)
|
149
|
+
rspec (3.10.0)
|
150
|
+
rspec-core (~> 3.10.0)
|
151
|
+
rspec-expectations (~> 3.10.0)
|
152
|
+
rspec-mocks (~> 3.10.0)
|
153
|
+
rspec-core (3.10.1)
|
154
|
+
rspec-support (~> 3.10.0)
|
155
|
+
rspec-expectations (3.10.1)
|
156
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
157
|
+
rspec-support (~> 3.10.0)
|
158
|
+
rspec-mocks (3.10.2)
|
159
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
160
|
+
rspec-support (~> 3.10.0)
|
161
|
+
rspec-support (3.10.2)
|
162
|
+
simplecov (0.21.2)
|
163
|
+
docile (~> 1.1)
|
164
|
+
simplecov-html (~> 0.11)
|
165
|
+
simplecov_json_formatter (~> 0.1)
|
166
|
+
simplecov-console (0.9.1)
|
167
|
+
ansi
|
168
|
+
simplecov
|
169
|
+
terminal-table
|
170
|
+
simplecov-html (0.12.3)
|
171
|
+
simplecov_json_formatter (0.1.2)
|
172
|
+
sprockets (4.0.2)
|
173
|
+
concurrent-ruby (~> 1.0)
|
174
|
+
rack (> 1, < 3)
|
175
|
+
sprockets-rails (3.2.2)
|
176
|
+
actionpack (>= 4.0)
|
177
|
+
activesupport (>= 4.0)
|
178
|
+
sprockets (>= 3.0.0)
|
179
|
+
sqlite3 (1.4.2)
|
180
|
+
terminal-table (1.8.0)
|
181
|
+
unicode-display_width (~> 1.1, >= 1.1.1)
|
182
|
+
thor (1.1.0)
|
183
|
+
thread_safe (0.3.6)
|
184
|
+
tzinfo (1.2.9)
|
185
|
+
thread_safe (~> 0.1)
|
186
|
+
unicode-display_width (1.7.0)
|
187
|
+
wwtd (1.4.1)
|
188
|
+
|
189
|
+
PLATFORMS
|
190
|
+
ruby
|
191
|
+
|
192
|
+
DEPENDENCIES
|
193
|
+
appraisal
|
194
|
+
awesome_explain!
|
195
|
+
binding_of_caller
|
196
|
+
bundler
|
197
|
+
mongoid (>= 5)
|
198
|
+
pry-byebug
|
199
|
+
pry-rails
|
200
|
+
rails (= 4.2)
|
201
|
+
rake (>= 10.0)
|
202
|
+
rspec (>= 3.10)
|
203
|
+
simplecov (>= 0.21.2)
|
204
|
+
simplecov-console (>= 0.9.1)
|
205
|
+
wwtd
|
206
|
+
|
207
|
+
BUNDLED WITH
|
208
|
+
1.17.3
|