rails_info 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +58 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/rails_info/application.js +6 -0
  5. data/app/assets/javascripts/rails_info/base.js.coffee +6 -0
  6. data/app/assets/javascripts/rails_info/bootstrap.js.coffee +4 -0
  7. data/app/assets/stylesheets/rails_info/application.css +14 -0
  8. data/app/assets/stylesheets/rails_info/base.css.scss +22 -0
  9. data/app/assets/stylesheets/rails_info/bootstrap_and_overrides.css.less +63 -0
  10. data/app/assets/stylesheets/rails_info/exception.css +17 -0
  11. data/app/assets/stylesheets/rails_info/exception/base.css.scss +31 -0
  12. data/app/assets/stylesheets/rails_info/exception/pygments.css.erb +5 -0
  13. data/app/controllers/rails_info/data_controller.rb +9 -0
  14. data/app/controllers/rails_info/logs/server_controller.rb +10 -0
  15. data/app/controllers/rails_info/logs/test/rspec_controller.rb +16 -0
  16. data/app/controllers/rails_info/model_controller.rb +5 -0
  17. data/app/controllers/rails_info/properties_controller.rb +4 -0
  18. data/app/controllers/rails_info/routes_controller.rb +4 -0
  19. data/app/controllers/rails_info/stack_traces_controller.rb +11 -0
  20. data/app/controllers/rails_info_controller.rb +16 -0
  21. data/app/helpers/rails_info/resources_helper.rb +194 -0
  22. data/app/presenters/rails_info/code_presenter.rb +63 -0
  23. data/app/presenters/rails_info/data/object_presenter.rb +34 -0
  24. data/app/presenters/rails_info/data/row_set_presenter.rb +38 -0
  25. data/app/presenters/rails_info/data_presenter.rb +21 -0
  26. data/app/presenters/rails_info/logs/server/action_presenter.rb +109 -0
  27. data/app/presenters/rails_info/logs/server_presenter.rb +57 -0
  28. data/app/presenters/rails_info/logs/test/rspec/file_presenter.rb +87 -0
  29. data/app/presenters/rails_info/logs/test/rspec_presenter.rb +32 -0
  30. data/app/presenters/rails_info/model_presenter.rb +68 -0
  31. data/app/presenters/rails_info/presenter.rb +19 -0
  32. data/app/presenters/rails_info/stack_trace_presenter.rb +98 -0
  33. data/app/views/layouts/_layout.html.erb +19 -0
  34. data/app/views/layouts/rails_info.html.erb +1 -0
  35. data/app/views/layouts/rails_info/exception.html.erb +1 -0
  36. data/app/views/layouts/shared/_navigation.html.erb +7 -0
  37. data/app/views/rails_info/data/index.html.erb +23 -0
  38. data/app/views/rails_info/logs/server/_table.html.erb +16 -0
  39. data/app/views/rails_info/logs/server/new.html.erb +19 -0
  40. data/app/views/rails_info/logs/test/rspec/new.html.erb +10 -0
  41. data/app/views/rails_info/model/index.html.erb +14 -0
  42. data/app/views/rails_info/properties/index.html.erb +1 -0
  43. data/app/views/rails_info/routes/index.html.erb +18 -0
  44. data/app/views/rails_info/stack_traces/_accordion.html.erb +1 -0
  45. data/app/views/rails_info/stack_traces/_form.html.erb +11 -0
  46. data/app/views/rails_info/stack_traces/_with_request_and_response.html.erb +16 -0
  47. data/app/views/rails_info/stack_traces/new.html.erb +10 -0
  48. data/config/navigation.rb +20 -0
  49. data/config/routes.rb +20 -0
  50. data/lib/rails_info.rb +19 -0
  51. data/lib/rails_info/controller.rb +2 -0
  52. data/lib/rails_info/controller/exception_diagnostics.rb +28 -0
  53. data/lib/rails_info/data.rb +55 -0
  54. data/lib/rails_info/engine.rb +9 -0
  55. data/lib/rails_info/logs.rb +2 -0
  56. data/lib/rails_info/logs/server.rb +230 -0
  57. data/lib/rails_info/logs/test.rb +2 -0
  58. data/lib/rails_info/logs/test/rspec.rb +160 -0
  59. data/lib/rails_info/model.rb +221 -0
  60. data/lib/rails_info/stack_trace.rb +155 -0
  61. data/lib/rails_info/version.rb +3 -0
  62. data/lib/tasks/rails_info_tasks.rake +4 -0
  63. data/spec/dummy/README.rdoc +261 -0
  64. data/spec/dummy/Rakefile +7 -0
  65. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  66. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  67. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  68. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  69. data/spec/dummy/app/models/user.rb +3 -0
  70. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  71. data/spec/dummy/config.ru +4 -0
  72. data/spec/dummy/config/application.rb +59 -0
  73. data/spec/dummy/config/boot.rb +10 -0
  74. data/spec/dummy/config/database.yml +42 -0
  75. data/spec/dummy/config/environment.rb +5 -0
  76. data/spec/dummy/config/environments/development.rb +39 -0
  77. data/spec/dummy/config/environments/production.rb +69 -0
  78. data/spec/dummy/config/environments/test.rb +39 -0
  79. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  80. data/spec/dummy/config/initializers/inflections.rb +15 -0
  81. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  82. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  83. data/spec/dummy/config/initializers/session_store.rb +8 -0
  84. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  85. data/spec/dummy/config/locales/en.yml +5 -0
  86. data/spec/dummy/config/routes.rb +2 -0
  87. data/spec/dummy/log/development.log +4876 -0
  88. data/spec/dummy/public/404.html +26 -0
  89. data/spec/dummy/public/422.html +26 -0
  90. data/spec/dummy/public/500.html +25 -0
  91. data/spec/dummy/public/favicon.ico +0 -0
  92. data/spec/dummy/script/rails +6 -0
  93. metadata +274 -0
@@ -0,0 +1,2 @@
1
+ module RailsInfo::Controller
2
+ end
@@ -0,0 +1,28 @@
1
+ module RailsInfo::Controller::ExceptionDiagnostics
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ rescue_from Exception, with: :custom_stack_trace
6
+ end
7
+
8
+ private
9
+
10
+ def custom_stack_trace(exception)
11
+ =begin
12
+ wrapper = ActionDispatch::ExceptionWrapper.new(env, exception)
13
+ trace = {
14
+ exception: wrapper.exception,
15
+ :application_trace => wrapper.application_trace,
16
+ :framework_trace => wrapper.framework_trace,
17
+ :full_trace => wrapper.full_trace
18
+ }
19
+ =end
20
+ @stack_trace = RailsInfo::StackTracePresenter.new(
21
+ view_context, stack_trace: {
22
+ body: exception.backtrace, exception: exception , request: request
23
+ }
24
+ )
25
+
26
+ render 'rails_info/stack_traces/new', layout: 'rails_info/exception'
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ class RailsInfo::Data
2
+ class << self
3
+ def update_multiple(data)
4
+ collection = {}
5
+
6
+ data.each do |element|
7
+ klass, id = element.split(';')
8
+ collection[klass] ||= []
9
+ collection[klass] << id
10
+ end
11
+
12
+ collection.each do |klass,ids|
13
+ klass.constantize.delete(ids)
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize
19
+ @rails_info_model = ::RailsInfo::Model.new
20
+ end
21
+
22
+ def last_objects
23
+ klasses = @rails_info_model.classes
24
+ objects = {}
25
+
26
+ klasses.each do |klass|
27
+ sort_field = [:updated_at, :created_at].select{|v| klass.columns.map{|c|c.name.to_sym}.include?(v)}.first
28
+
29
+ raise NotImplementedError.new("No known sort fields found in attribute keys: #{object.attributes.keys.inspect}") if sort_field.blank?
30
+
31
+ klass.limit(10).each do |object|
32
+ objects[object.send(sort_field).to_i] = object.attributes
33
+ objects[object.send(sort_field).to_i][:class] = object.class
34
+ end
35
+ end
36
+
37
+ row_sets = []
38
+ last_class = ""
39
+ last_objects = []
40
+
41
+ objects.keys.sort {|x,y| y <=> x }.each do |sort_value|
42
+ if last_class != "" && objects[sort_value][:class] != last_class
43
+ row_sets << last_objects
44
+ last_objects = []
45
+ end
46
+
47
+ last_objects << objects[sort_value]
48
+ last_class = objects[sort_value][:class]
49
+ end
50
+
51
+ row_sets << last_objects
52
+
53
+ row_sets
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ module RailsInfo
2
+ class Engine < ::Rails::Engine
3
+ config.before_initialize do |app|
4
+ if Rails.env.development?
5
+ SimpleNavigation.config_file_paths << "#{File.expand_path(File.dirname(__FILE__))}/../../config"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,2 @@
1
+ module RailsInfo::Logs
2
+ end
@@ -0,0 +1,230 @@
1
+ class RailsInfo::Logs::Server
2
+ def initialize(options = {log: {}, debug: false})
3
+ options ||= {log: {}, debug: false}
4
+ options[:log] ||= {}
5
+
6
+ @debug = options[:debug]
7
+ path = options[:log][:path] || "#{Rails.root}/log/"
8
+ env = options[:log][:env] || Rails.env
9
+ @body = options[:log][:body]
10
+
11
+ unless @body
12
+ file_path = "#{path}#{env}.log"
13
+
14
+ @body = File.new(file_path, 'r').read if File.exist?(file_path)
15
+ end
16
+
17
+ @log ||= {}
18
+ @writes = {}
19
+
20
+ process if @body.present?
21
+ end
22
+
23
+ def hash
24
+ @log
25
+ end
26
+
27
+ def writes
28
+ @writes
29
+ end
30
+
31
+ private
32
+
33
+ def process
34
+ @start_after_last_shutdown = true
35
+ @body = @body.split("\n").map(&:strip)
36
+
37
+ reset_log
38
+
39
+ group_by_action = false
40
+
41
+ @body.each do |line|
42
+ if line.match('Processing') && line.match('#') && line.match(/\[/) && line.match(/\]/)
43
+ # rails 2
44
+ group_by_action = true
45
+ elsif line.match('Started') && line.match(' for ') && line.match(' at ') && line.split(' ').length == 9
46
+ # rails 3
47
+ group_by_action = true
48
+ elsif line.match('Processing by') && line.match('#') && line.match(' as ') && line.split(' ').length == 5
49
+ # rails 3
50
+ group_by_action = true
51
+ end
52
+ end
53
+
54
+ unless group_by_action
55
+ @action = 'no_action_grouping'
56
+ @log[@action] ||= { 'Parameters' => '', 'WRITE' => {}, 'READ' => [], 'views' => [], 'misc' => [], 'errors' => []}
57
+ end
58
+
59
+ @body.each do |line|
60
+ process_line(line)
61
+ end
62
+
63
+ @log.each do |action,tabs|
64
+ tabs.each do |tab,content|
65
+ @log[action].delete(tab) if @log[action][tab].blank?
66
+ end
67
+ end
68
+
69
+ @log.each do |action,tabs|
70
+ tabs.each do |tab,content|
71
+ next unless tab == 'WRITE'
72
+
73
+ # define WHERE column as last one
74
+ content.each do |table_name,data|
75
+ @log[action][tab][table_name]['columns'] << 'WHERE'
76
+ end
77
+
78
+ break
79
+ end
80
+ end
81
+
82
+ @writes.each do |table_name,data|
83
+ @writes[table_name]['columns'] << 'WHERE'
84
+ end
85
+ end
86
+
87
+ def reset_log
88
+ @log, @action_indexes, @action, @last_action = {}, {}, "", ""
89
+ end
90
+
91
+ # TODO: process test.log which don't have action grouping, too
92
+ # TODO: look if newrelic & Co. already does that but are only doing it for the last action
93
+ # TODO: parse stack traces by grouping the lines by file and than method, generate a pseudo web sequence diagram code for files as participants and their methods
94
+ # TODO: integrate a code snippet for each line of the stack trace like brakeman & co. do
95
+ # TODO: extend rails stack trace output by code snippets
96
+ def process_line(line)
97
+ if line.match('Ctrl-C to shutdown server') && @start_after_last_shutdown
98
+ reset_log
99
+ elsif line.match('Processing') && line.match('#') && line.match(/\[/) && line.match(/\]/)
100
+ # rails 2 start of action
101
+ # Processing Clickworker::DashboardsController#show (for 127.0.0.1 at 2011-08-26 12:22:58) [GET]
102
+ @action = line.split(' ')[1]
103
+
104
+ init_log_action
105
+ #elsif line.match('Started') && line.match(' for ') && line.match(' at ') && line.split(' ').length == 9
106
+ # rails 3 start of action
107
+ # Started GET "/orders/815?truncate_length=1000" for 127.0.0.1 at 2011-10-04 19:58:44 +0200
108
+ elsif line.match('Processing by') && line.match('#') && line.match(' as ') && line.split(' ').length == 5
109
+ # rails 3 start of action
110
+ # Processing by OrdersController#show as HTML
111
+ @action = line.split(' ')[2]
112
+
113
+ init_log_action
114
+ elsif @action.blank?
115
+ elsif line.match('Parameters:') && line.match('{') && line.match('}')
116
+ line = line.split(' ')
117
+ line.pop
118
+ @log[@action]['Parameters'] = line.join(' ').strip
119
+ elsif line.match('INSERT INTO') || line.match('UPDATE')
120
+ table_name = table_name_from_line(line)
121
+
122
+ data = line.match('INSERT INTO') ? process_insert(line) : process_update(table_name, line)
123
+
124
+ # TaskTemplate Create (0.2ms) INSERT INTO `task_templates` (`slug`, `name`, `created_at`, `product_id`, `updated_at`, `customer_id`, `state`) VALUES('the code', 'a customer name', '2011-08-26 10:22:54', 2, '2011-08-26 10:22:54', 1002, 'draft')
125
+ #
126
+ # InputDataItem Update (0.3ms) UPDATE `data_items` SET `input` = '<opt>\n <input>\n <__dynamic_form__>\n <df_create>\n <the_input></the_input>\n <the_output>Output field 1</the_output>\n </df_create>\n </__dynamic_form__>\n </input>\n</opt>\n', `updated_at` = '2011-08-26 10:22:55' WHERE `id` = 5485
127
+
128
+
129
+ @log[@action]['WRITE'][table_name] ||= { 'columns' => ['id'], 'rows' => [] }
130
+ @writes[table_name] ||= { 'columns' => ['id'], 'rows' => [] }
131
+
132
+ @log[@action]['WRITE'][table_name]['columns'] = @log[@action]['WRITE'][table_name]['columns'].concat(data.keys).uniq
133
+ @writes[table_name]['columns'] = @writes[table_name]['columns'].concat(data.keys).uniq
134
+ @log[@action]['WRITE'][table_name]['rows'] << data
135
+ @writes[table_name]['rows'] << data
136
+ elsif (line.match('Load') && line.match('SELECT')) || (line.match('CACHE') && line.match('\(') && line.match('ms\)'))
137
+ line = line.split(')')
138
+ line.shift
139
+ @log[@action]['READ'] ||= []
140
+ @log[@action]['READ'] << line.join(')').strip
141
+ elsif line.match('Rendered') && line.match('\(') && line.match('ms\)')
142
+ @log[@action]['Views'] ||= []
143
+ line = line.split('Rendered')
144
+ line.shift
145
+ @log[@action]['Views'] << line.join('Rendered').split('(').first.strip
146
+ elsif line.match('Rendering')
147
+ @log[@action]['Views'] ||= []
148
+ line = line.split('Rendering')
149
+ line.shift
150
+ @log[@action]['Views'] << line.join('').strip
151
+ elsif (
152
+ line.match('SHOW FIELDS FROM') || (line.match('SQL \(') && line.match('ms\)'))
153
+ )
154
+ else
155
+ @log[@action]['misc'] << line
156
+ end
157
+
158
+ @last_action = @action
159
+ end
160
+
161
+ def table_name_from_line(line)
162
+ line = line.split(')')
163
+ line.shift
164
+ line = line.join(')')
165
+
166
+ line = line.split('`')
167
+ line[1]
168
+ end
169
+
170
+ def process_insert(line)
171
+ data = {}
172
+
173
+ columns = line.match(/\(`(.)+\)(( |)VALUES)/)[0].split('VALUES').first.strip.gsub('`', '')[1..-2].split(',').map(&:strip)
174
+
175
+ cells = nil
176
+
177
+ #begin
178
+ cells = line.match(/(VALUES(| )\()(.)+\)/)[0].split('VALUES').last.strip.
179
+ gsub(/\('/, '(').
180
+ gsub(/,(| )'/, ",").
181
+ gsub(/',(| )'/, ",").
182
+ gsub(/',(| )/, ",").
183
+ strip[1..-2].split(',').map(&:strip)
184
+ #rescue
185
+ # raise [line, line.match(/(VALUES(| )\()(.)+\)/)].inspect
186
+ #end
187
+
188
+ columns.each_index {|column_index| data[columns[column_index]] = cells[column_index]}
189
+
190
+ data
191
+ end
192
+
193
+ def process_update(table_name, line)
194
+ data = {}
195
+
196
+ # SET `input` = '<opt>\n <input>\n <__dynamic_form__>\n <df_create>\n <the_input></the_input>\n <the_output>Output field 1</the_output>\n </df_create>\n </__dynamic_form__>\n </input>\n</opt>\n', `updated_at` = '2011-08-26 10:22:55' WHERE `id` = 5485
197
+ line = line.split('WHERE')
198
+ data_string = line.first
199
+ conditions = line.last
200
+ data_string = data_string.split(' '); data_string.shift; data_string = data_string.join(' ')
201
+ data_string = data_string.gsub("', `", "||||").gsub(", `", '||||').gsub("` = '", "=").split('||||')
202
+
203
+ data_string.each do |data_element|
204
+ data_element = data_element.split('=')
205
+ data[data_element.shift.gsub('`', '').strip] = data_element.join('=')
206
+ end
207
+
208
+ if conditions.match("`#{table_name}`.`id` = ([0-9]+)")
209
+ data['id'] = conditions.match("`#{table_name}`.`id` = ([0-9]+)")[0].split('=').last.strip
210
+ elsif conditions.match(/`id` = ([0-9]+)/)
211
+ data['id'] = conditions.match(/`id` = ([0-9]+)/)[0].split('=').last.strip
212
+ end
213
+
214
+ data['WHERE'] = conditions
215
+
216
+ data
217
+ end
218
+
219
+ def init_log_action
220
+ if @action_indexes.has_key?(@action)
221
+ @action_indexes[@action] = @action_indexes[@action] + 1
222
+ else
223
+ @action_indexes[@action] = 1
224
+ end
225
+
226
+ @action = "#{@action} ##{@action_indexes[@action]}"
227
+
228
+ @log[@action] ||= { 'Parameters' => '', 'WRITE' => {}, 'READ' => [], 'views' => [], 'misc' => [], 'errors' => []}
229
+ end
230
+ end
@@ -0,0 +1,2 @@
1
+ module RailsInfo::Logs::Test
2
+ end
@@ -0,0 +1,160 @@
1
+ class RailsInfo::Logs::Test::Rspec
2
+ FILE_NAME_REGEXP = /[a-zA-Z]|[0-9]|\/|\.|_|-/
3
+
4
+ def initialize(options = {log: {}, debug: false})
5
+ options ||= {log: {}, debug: false}
6
+ options[:log] ||= {}
7
+
8
+ @rails_root = options[:log][:rails_root] || Rails.root.to_s
9
+ @body = options[:log][:body]
10
+ @show_all = options[:log][:show_all] || false
11
+ @debug = options[:debug]
12
+
13
+ unless @body
14
+ file_path = "#{@rails_root}/log/rspec.log"
15
+
16
+ @body = File.new(file_path, 'r').read if File.exist?(file_path)
17
+ end
18
+
19
+ @log ||= {}
20
+
21
+ process if @body.present?
22
+ end
23
+
24
+ def summary
25
+ @summary
26
+ end
27
+
28
+ def hash
29
+ @log
30
+ end
31
+
32
+ def rails_root
33
+ @rails_root
34
+ end
35
+
36
+ private
37
+
38
+ def process
39
+ @body = @body.split("\n").map(&:strip)
40
+
41
+ failures_found, line_index = false, -1
42
+ example, failure_code, exception_class, exception_message, stack_trace = nil, nil, nil, nil, []
43
+ after_stack_trace_entry = nil
44
+
45
+ @body.each do |line|
46
+ line_index += 1
47
+
48
+ if line.match('Failures:')
49
+ failures_found = true
50
+
51
+ next
52
+ elsif line.match(/Finished in/) && @body[line_index + 1].match(/examples|failures|pending/)
53
+ add_entry(example, failure_code, exception_class, exception_message, stack_trace, after_stack_trace_entry)
54
+
55
+ @summary = @body[line_index + 1]
56
+
57
+ break
58
+ elsif line.blank?
59
+ next
60
+ end
61
+
62
+ if failures_found && line.match(/^[0-9]+\)( ){1}([a-zA-Z]){1}/)
63
+ if example.present?
64
+ add_entry(example, failure_code, exception_class, exception_message, stack_trace, after_stack_trace_entry)
65
+ end
66
+
67
+ after_stack_trace_entry, stack_trace = nil, []
68
+
69
+ # 1) Community::CronJobs::Statistics.total_feedbacks_one_hour_ago principally works
70
+
71
+ =begin
72
+ line = line.split(')').second.strip.split(' ')
73
+ line.shift
74
+ example = line.join(' ') # principally works
75
+ =end
76
+
77
+ example = line.split(')').second.strip
78
+
79
+ #Failure/Error: Feedback.make!(community: @community, user: @user)
80
+ failure_code = @body[line_index + 1]
81
+ #oMethodError:
82
+ exception_class = @body[line_index + 2].split(':').first.strip
83
+
84
+ if exception_class == 'expected' || @body[line_index + 3].split(':').first.strip == 'expected'
85
+ #Reaction it should behave like objects that are 'hidable'#text_or_reason_for_hiding should return a notice if the entry has been hidden
86
+ #Failure/Error: subject.content_or_reason_for_hiding.should == "Dieser Beitrag wurde am 2000-01-01 21:15:01 +0100 von Johann Wolfgang von Goethe gelöscht. Der Grund war: This entry sucks!"
87
+ #expected: "Dieser Beitrag wurde am 2000-01-01 21:15:01 +0100 von Johann Wolfgang von Goethe gelöscht. Der Grund war: This entry sucks!"
88
+ #got: "Dieser Beitrag wurde am 2000-01-01 20:15:01 +0000 von Johann Wolfgang von Goethe gelöscht. Der Grund war: This entry sucks!" (using ==)
89
+ #Shared Example Group: "objects that are 'hidable'" called from ./spec/models/shared/hidable_trait_spec.rb:113
90
+ ## ./spec/models/shared/hidable_trait_spec.rb:102:in `block (3 levels) in <top (required)>'
91
+ exception_class, exception_message, after_stack_trace_entry = alternative_exception_message(line_index, 2..4)
92
+ else
93
+ #undefined method `moderators' for nil:NilClass
94
+ exception_message = @body[line_index + 3]
95
+ end
96
+ elsif failures_found && line.match("^#( ){1}\.\/(#{FILE_NAME_REGEXP}){1,}:[0-9]{1,}:in( ){1}`((.){1,})'")
97
+ ## ./app/models/notification/email.rb:38:in `block in receivers'
98
+ stack_trace << line
99
+ end
100
+ end
101
+ end
102
+
103
+ def add_entry(example, failure_code, exception_class, exception_message, stack_trace, after_stack_trace_entry = nil)
104
+ stack_trace << after_stack_trace_entry if after_stack_trace_entry
105
+
106
+ return unless stack_trace.any?
107
+
108
+ # from spec line of code down to application line of code
109
+ stack_trace.reverse!
110
+ file_name = stack_trace.first.match("^#( ){1}(\.\/(#{FILE_NAME_REGEXP}){1,})")[2]
111
+
112
+ @log[file_name] ||= {}
113
+
114
+ if example.match(/\./) && example.split('.').length == 2 && !example.split('.').first.match(/ |'/)
115
+ #Community::CronJobs::Statistics.total_feedbacks_one_hour_ago principally works
116
+ example = ".#{example.split('.').second.strip}"
117
+ elsif example.match(/#/) && example.split('#').length == 2 && !example.split('#').first.match(/ |'/)
118
+ #Community::CronJobs::Statistics#total_feedbacks_one_hour_ago principally works
119
+ example = "##{example.split('#').second.strip}"
120
+ elsif example.split(' ').length == 1
121
+ #Reaction
122
+ #Failure/Error: it {should have_one(:spam_flag)}
123
+
124
+ # make example unique by setting it to the failure code
125
+ example = failure_code
126
+ elsif example.split(' -').length == 2
127
+ #Feedback -validations
128
+ #Failure/Error: it { should belong_to(:community) }
129
+
130
+ # make example unique by including failure code
131
+ example += " #{failure_code}"
132
+ end
133
+
134
+ if @log[file_name].has_key?(example)
135
+ raise NotImplementedError.new(
136
+ "RSpec file #{file_name} not expected to have more than 1 example named #{example.inspect}"
137
+ )
138
+ end
139
+
140
+ @log[file_name][example] = {
141
+ failure_code: failure_code, exception_class: exception_class, exception_message: exception_message,
142
+ stack_trace: stack_trace.join('\n')
143
+ }
144
+ end
145
+
146
+ def alternative_exception_message(line_index, span)
147
+ exception_class = '', after_stack_trace_entry = nil
148
+
149
+ exception_message = span.to_a.map {|i| @body[line_index + i] }
150
+
151
+ if exception_message.last.match("^(.){1,}( ){1}called from( ){1}\.(#{FILE_NAME_REGEXP}){1,}:([0-9]){1,}$")
152
+ stack_trace_entry = exception_message.last.split('called from').last.strip
153
+ after_stack_trace_entry = "# #{stack_trace_entry}:in `SORRY_BUT_NOT_PASSED_BY_RSPEC'"
154
+ end
155
+
156
+ exception_message = exception_message.join('\n')
157
+
158
+ [exception_class, exception_message, after_stack_trace_entry]
159
+ end
160
+ end