awesome_explain 0.3.0 → 1.0.0

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.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/mongodb.yml +53 -0
  3. data/.github/workflows/postgres.yml +56 -0
  4. data/.gitignore +11 -0
  5. data/Appraisals +11 -0
  6. data/Gemfile.lock +209 -49
  7. data/LICENSE.txt +4 -20
  8. data/README.md +155 -7
  9. data/Rakefile +35 -1
  10. data/app/models/awesome_explain/application_record.rb +5 -0
  11. data/app/models/awesome_explain/controller.rb +20 -0
  12. data/app/models/awesome_explain/delayed_job.rb +7 -0
  13. data/app/models/awesome_explain/explain.rb +23 -0
  14. data/app/models/awesome_explain/log.rb +7 -0
  15. data/app/models/awesome_explain/pg_dml_stat.rb +4 -0
  16. data/app/models/awesome_explain/pg_seq_scan.rb +4 -0
  17. data/app/models/awesome_explain/plan_node.rb +52 -0
  18. data/app/models/awesome_explain/plan_tree.rb +66 -0
  19. data/app/models/awesome_explain/sidekiq_worker.rb +7 -0
  20. data/app/models/awesome_explain/sql_explain.rb +14 -0
  21. data/app/models/awesome_explain/sql_plan_node.rb +73 -0
  22. data/app/models/awesome_explain/sql_plan_stats.rb +34 -0
  23. data/app/models/awesome_explain/sql_plan_tree.rb +133 -0
  24. data/app/models/awesome_explain/sql_query.rb +7 -0
  25. data/app/models/awesome_explain/stacktrace.rb +11 -0
  26. data/awesome_explain.gemspec +16 -5
  27. data/bin/rails +14 -0
  28. data/data/mongodb/customers.bson +0 -0
  29. data/data/mongodb/customers.metadata.json +1 -0
  30. data/data/mongodb/line_items.bson +0 -0
  31. data/data/mongodb/line_items.metadata.json +1 -0
  32. data/data/mongodb/orders.bson +0 -0
  33. data/data/mongodb/orders.metadata.json +1 -0
  34. data/data/mongodb/products.bson +0 -0
  35. data/data/mongodb/products.metadata.json +1 -0
  36. data/data/postgresql/dvdrental.tar +0 -0
  37. data/db/migrate/20200507214801_stacktraces.rb +12 -0
  38. data/db/migrate/20200507214949_controllers.rb +16 -0
  39. data/db/migrate/20200507215205_logs.rb +22 -0
  40. data/db/migrate/20200507215243_explains.rb +27 -0
  41. data/gemfiles/rails_4.gemfile +7 -0
  42. data/gemfiles/rails_4.gemfile.lock +208 -0
  43. data/gemfiles/rails_5.gemfile +7 -0
  44. data/gemfiles/rails_5.gemfile.lock +209 -0
  45. data/gemfiles/rails_6.gemfile +7 -0
  46. data/gemfiles/rails_6.gemfile.lock +233 -0
  47. data/images/universe.png +0 -0
  48. data/lib/awesome_explain.rb +79 -2
  49. data/lib/awesome_explain/config.rb +196 -0
  50. data/lib/awesome_explain/engine.rb +5 -0
  51. data/lib/awesome_explain/insights/active_record_insights.rb +137 -0
  52. data/lib/awesome_explain/insights/base.rb +18 -0
  53. data/lib/awesome_explain/insights/mongoid_insights.rb +44 -0
  54. data/lib/awesome_explain/insights/sql_plans_insights.rb +64 -0
  55. data/lib/awesome_explain/kernel.rb +17 -0
  56. data/lib/awesome_explain/mongodb/base.rb +4 -0
  57. data/lib/awesome_explain/mongodb/command_start.rb +84 -0
  58. data/lib/awesome_explain/mongodb/command_success.rb +58 -0
  59. data/lib/awesome_explain/mongodb/formatter.rb +62 -0
  60. data/lib/awesome_explain/mongodb/helpers.rb +71 -0
  61. data/lib/awesome_explain/queue/command.rb +17 -0
  62. data/lib/awesome_explain/queue/simple_queue.rb +88 -0
  63. data/lib/awesome_explain/renderers/active_record.rb +114 -0
  64. data/lib/awesome_explain/renderers/base.rb +2 -0
  65. data/lib/awesome_explain/renderers/mongoid.rb +20 -33
  66. data/lib/awesome_explain/sidekiq_middleware.rb +17 -0
  67. data/lib/awesome_explain/stats/postgresql.rb +54 -0
  68. data/lib/awesome_explain/subscribers/active_record_passive_subscriber.rb +82 -0
  69. data/lib/awesome_explain/subscribers/active_record_subscriber.rb +187 -0
  70. data/lib/awesome_explain/subscribers/base.rb +3 -0
  71. data/lib/awesome_explain/subscribers/command_subscriber.rb +53 -0
  72. data/lib/awesome_explain/tasks/db.rb +325 -0
  73. data/lib/awesome_explain/utils/color.rb +16 -0
  74. data/lib/awesome_explain/version.rb +1 -1
  75. data/lib/tasks/ae.rake +28 -0
  76. data/lib/tasks/awesome_explain_tasks.rake +4 -0
  77. metadata +242 -25
  78. 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
@@ -0,0 +1,7 @@
1
+ class AwesomeExplain::SqlQuery < ActiveRecord::Base
2
+ establish_connection AwesomeExplain::Config.instance.db_config
3
+ self.table_name = 'sql_queries'
4
+
5
+ belongs_to :stacktrace
6
+ belongs_to :sql_explain
7
+ end
@@ -0,0 +1,11 @@
1
+ class AwesomeExplain::Stacktrace < ActiveRecord::Base
2
+ establish_connection AwesomeExplain::Config.instance.db_config
3
+ self.table_name = 'stacktraces'
4
+
5
+ has_many :logs
6
+ has_many :explains
7
+
8
+ def stacktrace
9
+ JSON.parse self['stacktrace']
10
+ end
11
+ end
@@ -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', '~> 1.16'
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', '~> 10.0'
29
- spec.add_development_dependency 'rspec', '~> 3.0'
30
- spec.add_development_dependency 'simplecov-console', '~> 0.4.2'
31
- spec.add_development_dependency 'simplecov', '~> 0.16.1'
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,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "4.2"
6
+
7
+ gemspec path: "../"
@@ -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