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.
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