exception_notification 2.5.2 → 2.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,78 +1,89 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- exception_notification (2.4.1)
4
+ exception_notification (2.5.2)
5
5
  actionmailer (>= 3.0.4)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- abstract (1.0.0)
11
- actionmailer (3.0.9)
12
- actionpack (= 3.0.9)
13
- mail (~> 2.2.19)
14
- actionpack (3.0.9)
15
- activemodel (= 3.0.9)
16
- activesupport (= 3.0.9)
17
- builder (~> 2.1.2)
18
- erubis (~> 2.6.6)
19
- i18n (~> 0.5.0)
20
- rack (~> 1.2.1)
21
- rack-mount (~> 0.6.14)
22
- rack-test (~> 0.5.7)
23
- tzinfo (~> 0.3.23)
24
- activemodel (3.0.9)
25
- activesupport (= 3.0.9)
26
- builder (~> 2.1.2)
27
- i18n (~> 0.5.0)
28
- activerecord (3.0.9)
29
- activemodel (= 3.0.9)
30
- activesupport (= 3.0.9)
31
- arel (~> 2.0.10)
32
- tzinfo (~> 0.3.23)
33
- activeresource (3.0.9)
34
- activemodel (= 3.0.9)
35
- activesupport (= 3.0.9)
36
- activesupport (3.0.9)
37
- arel (2.0.10)
38
- builder (2.1.2)
39
- erubis (2.6.6)
40
- abstract (>= 1.0.0)
41
- i18n (0.5.0)
42
- mail (2.2.19)
43
- activesupport (>= 2.3.6)
10
+ actionmailer (3.2.3)
11
+ actionpack (= 3.2.3)
12
+ mail (~> 2.4.4)
13
+ actionpack (3.2.3)
14
+ activemodel (= 3.2.3)
15
+ activesupport (= 3.2.3)
16
+ builder (~> 3.0.0)
17
+ erubis (~> 2.7.0)
18
+ journey (~> 1.0.1)
19
+ rack (~> 1.4.0)
20
+ rack-cache (~> 1.2)
21
+ rack-test (~> 0.6.1)
22
+ sprockets (~> 2.1.2)
23
+ activemodel (3.2.3)
24
+ activesupport (= 3.2.3)
25
+ builder (~> 3.0.0)
26
+ activerecord (3.2.3)
27
+ activemodel (= 3.2.3)
28
+ activesupport (= 3.2.3)
29
+ arel (~> 3.0.2)
30
+ tzinfo (~> 0.3.29)
31
+ activeresource (3.2.3)
32
+ activemodel (= 3.2.3)
33
+ activesupport (= 3.2.3)
34
+ activesupport (3.2.3)
35
+ i18n (~> 0.6)
36
+ multi_json (~> 1.0)
37
+ arel (3.0.2)
38
+ builder (3.0.0)
39
+ erubis (2.7.0)
40
+ hike (1.2.1)
41
+ i18n (0.6.0)
42
+ journey (1.0.3)
43
+ json (1.6.6)
44
+ mail (2.4.4)
44
45
  i18n (>= 0.4.0)
45
46
  mime-types (~> 1.16)
46
47
  treetop (~> 1.4.8)
47
- mime-types (1.16)
48
- polyglot (0.3.2)
49
- rack (1.2.3)
50
- rack-mount (0.6.14)
51
- rack (>= 1.0.0)
52
- rack-test (0.5.7)
48
+ mime-types (1.18)
49
+ multi_json (1.2.0)
50
+ polyglot (0.3.3)
51
+ rack (1.4.1)
52
+ rack-cache (1.2)
53
+ rack (>= 0.4)
54
+ rack-ssl (1.3.2)
55
+ rack
56
+ rack-test (0.6.1)
53
57
  rack (>= 1.0)
54
- rails (3.0.9)
55
- actionmailer (= 3.0.9)
56
- actionpack (= 3.0.9)
57
- activerecord (= 3.0.9)
58
- activeresource (= 3.0.9)
59
- activesupport (= 3.0.9)
58
+ rails (3.2.3)
59
+ actionmailer (= 3.2.3)
60
+ actionpack (= 3.2.3)
61
+ activerecord (= 3.2.3)
62
+ activeresource (= 3.2.3)
63
+ activesupport (= 3.2.3)
60
64
  bundler (~> 1.0)
61
- railties (= 3.0.9)
62
- railties (3.0.9)
63
- actionpack (= 3.0.9)
64
- activesupport (= 3.0.9)
65
+ railties (= 3.2.3)
66
+ railties (3.2.3)
67
+ actionpack (= 3.2.3)
68
+ activesupport (= 3.2.3)
69
+ rack-ssl (~> 1.3.2)
65
70
  rake (>= 0.8.7)
66
71
  rdoc (~> 3.4)
67
- thor (~> 0.14.4)
68
- rake (0.9.2)
69
- rdoc (3.8)
70
- sqlite3 (1.3.4)
72
+ thor (~> 0.14.6)
73
+ rake (0.9.2.2)
74
+ rdoc (3.12)
75
+ json (~> 1.4)
76
+ sprockets (2.1.2)
77
+ hike (~> 1.2)
78
+ rack (~> 1.0)
79
+ tilt (~> 1.1, != 1.3.0)
80
+ sqlite3 (1.3.5)
71
81
  thor (0.14.6)
82
+ tilt (1.3.3)
72
83
  treetop (1.4.10)
73
84
  polyglot
74
85
  polyglot (>= 0.3.1)
75
- tzinfo (0.3.29)
86
+ tzinfo (0.3.32)
76
87
 
77
88
  PLATFORMS
78
89
  ruby
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- Exception Notifier Plugin for Rails
1
+ Exception Notifier Plugin for Rails ![project status](http://stillmaintained.com/smartinez87/exception_notification.png) [![Travis](http://travis-ci.org/smartinez87/exception_notification.png)](http://travis-ci.org/smartinez87/exception_notification)
2
2
  ====
3
3
 
4
4
  The Exception Notifier plugin provides a mailer object and a default set of
@@ -12,72 +12,178 @@ application. The plugin is configurable, allowing programmers to specify:
12
12
  The email includes information about the current request, session, and
13
13
  environment, and also gives a backtrace of the exception.
14
14
 
15
+ There's a great [Railscast about Exception Notifications](http://railscasts.com/episodes/104-exception-notifications-revised)
16
+ you can see that may help you getting started.
17
+
15
18
  Installation
16
19
  ---
17
20
 
18
21
  You can use the latest ExceptionNotification gem with Rails 3, by adding
19
22
  the following line in your Gemfile
20
23
 
21
- gem 'exception_notification'
24
+ ```ruby
25
+ gem 'exception_notification'
26
+ ```
22
27
 
23
28
  As of Rails 3 ExceptionNotification is used as a rack middleware, so you can
24
29
  configure its options on your config.ru file, or in the environment you
25
30
  want it to run. In most cases you would want ExceptionNotification to
26
31
  run on production. You can make it work by
27
32
 
28
- Whatever::Application.config.middleware.use ExceptionNotifier,
29
- :email_prefix => "[Whatever] ",
30
- :sender_address => %{"notifier" <notifier@example.com>},
31
- :exception_recipients => %w{exceptions@example.com}
33
+ ```ruby
34
+ Whatever::Application.config.middleware.use ExceptionNotifier,
35
+ :email_prefix => "[Whatever] ",
36
+ :sender_address => %{"notifier" <notifier@example.com>},
37
+ :exception_recipients => %w{exceptions@example.com}
38
+ ```
32
39
 
33
40
  Customization
34
41
  ---
35
42
 
43
+ ### Sections
44
+
36
45
  By default, the notification email includes four parts: request, session,
37
46
  environment, and backtrace (in that order). You can customize how each of those
38
47
  sections are rendered by placing a partial named for that part in your
39
48
  app/views/exception_notifier directory (e.g., _session.rhtml). Each partial has
40
49
  access to the following variables:
41
50
 
42
- @controller # the controller that caused the error
43
- @request # the current request object
44
- @exception # the exception that was raised
45
- @backtrace # a sanitized version of the exception's backtrace
46
- @data # a hash of optional data values that were passed to the notifier
47
- @sections # the array of sections to include in the email
51
+ ```ruby
52
+ @controller # the controller that caused the error
53
+ @request # the current request object
54
+ @exception # the exception that was raised
55
+ @backtrace # a sanitized version of the exception's backtrace
56
+ @data # a hash of optional data values that were passed to the notifier
57
+ @sections # the array of sections to include in the email
58
+ ```
59
+
60
+ Background views will not have access to @controller and @request.
48
61
 
49
62
  You can reorder the sections, or exclude sections completely, by altering the
50
63
  ExceptionNotifier.sections variable. You can even add new sections that
51
64
  describe application-specific data--just add the section's name to the list
52
- (wherever you'd like), and define the corresponding partial.
53
-
54
- #Example with two new added sections
55
- Whatever::Application.config.middleware.use ExceptionNotifier,
56
- :email_prefix => "[Whatever] ",
57
- :sender_address => %{"notifier" <notifier@example.com>},
58
- :exception_recipients => %w{exceptions@example.com},
59
- :sections => %w{my_section1 my_section2} + ExceptionNotifier::Notifier.default_sections
65
+ (wherever you'd like), and define the corresponding partial.
66
+
67
+ ```ruby
68
+ #Example with two new added sections
69
+ Whatever::Application.config.middleware.use ExceptionNotifier,
70
+ :email_prefix => "[Whatever] ",
71
+ :sender_address => %{"notifier" <notifier@example.com>},
72
+ :exception_recipients => %w{exceptions@example.com},
73
+ :sections => %w{my_section1 my_section2} + ExceptionNotifier::Notifier.default_sections
74
+ ```
60
75
 
61
76
  If your new section requires information that isn't available by default, make sure
62
77
  it is made available to the email using the exception_data macro:
63
78
 
64
- class ApplicationController < ActionController::Base
65
- before_filter :log_additional_data
66
- ...
67
- protected
68
- def log_additional_data
69
- request.env["exception_notifier.exception_data"] = {
70
- :document => @document,
71
- :person => @person
72
- }
73
- end
74
- ...
79
+ ```ruby
80
+ class ApplicationController < ActionController::Base
81
+ before_filter :log_additional_data
82
+ ...
83
+ protected
84
+ def log_additional_data
85
+ request.env["exception_notifier.exception_data"] = {
86
+ :document => @document,
87
+ :person => @person
88
+ }
75
89
  end
90
+ ...
91
+ end
92
+ ```
76
93
 
77
94
  In the above case, @document and @person would be made available to the email
78
95
  renderer, allowing your new section(s) to access and display them. See the
79
96
  existing sections defined by the plugin for examples of how to write your own.
80
97
 
98
+ You may want to include different sections for background notifications:
99
+
100
+ ```ruby
101
+ #Example with two new added sections
102
+ Whatever::Application.config.middleware.use ExceptionNotifier,
103
+ :email_prefix => "[Whatever] ",
104
+ :sender_address => %{"notifier" <notifier@example.com>},
105
+ :exception_recipients => %w{exceptions@example.com},
106
+ :background_sections => %w{my_section1 my_section2} + ExceptionNotifier::Notifier.default_background_sections
107
+ ```
108
+
109
+ By default, the backtrace and data sections are included in background
110
+ notifications.
111
+
112
+ ### Ignore Exceptions
113
+
114
+ You can choose to ignore certain exceptions, which will make
115
+ ExceptionNotifier avoid sending notifications for those specified.
116
+ There are three ways of specifying which exceptions to ignore:
117
+
118
+ - `:ignore_exceptions` - By exception class (i.e. ignore RecordNotFound ones)
119
+
120
+ - `:ignore_crawlers` - From crawler (i.e. ignore ones originated by Googlebot)
121
+
122
+ - `:ignore_if` - Custom (i.e. ignore exceptions that satisfy some condition)
123
+
124
+ ---
125
+
126
+ * _:ignore_exceptions_
127
+
128
+ Ignore specified exception types.
129
+ To achieve that, you should use the _:ignore_exceptions_ option, like this:
130
+
131
+ ```ruby
132
+ Whatever::Application.config.middleware.use ExceptionNotifier,
133
+ :email_prefix => "[Whatever] ",
134
+ :sender_address => %{"notifier" <notifier@example.com>},
135
+ :exception_recipients => %w{exceptions@example.com},
136
+ :ignore_exceptions => ['ActionView::TemplateError'] + ExceptionNotifier.default_ignore_exceptions
137
+ ```
138
+
139
+ The above will make ExceptionNotifier ignore a *TemplateError*
140
+ exception, plus the ones ignored by default.
141
+ By default, ExceptionNotifier ignores _ActiveRecord::RecordNotFound_,
142
+ _AbstractController::ActionNotFound_ and
143
+ _ActionController::RountingError_.
144
+
145
+ * _:ignore_crawlers_
146
+
147
+ In some cases you may want to avoid getting notifications from exceptions
148
+ made by crawlers. Using _:ignore_crawlers_ option like this,
149
+
150
+ ```ruby
151
+ Whatever::Application.config.middleware.use ExceptionNotifier,
152
+ :email_prefix => "[Whatever] ",
153
+ :sender_address => %{"notifier" <notifier@example.com>},
154
+ :exception_recipients => %w{exceptions@example.com},
155
+ :ignore_crawlers => %w{Googlebot bingbot}
156
+ ```
157
+
158
+ will prevent sending those unwanted notifications.
159
+
160
+ * _:ignore_if_
161
+
162
+ Last but not least, you can ignore exceptions based on a condition, by
163
+
164
+ ```ruby
165
+ Whatever::Application.config.middleware.use ExceptionNotifier,
166
+ :email_prefix => "[Whatever] ",
167
+ :sender_address => %{"notifier" <notifier@example.com>},
168
+ :exception_recipients => %w{exceptions@example.com},
169
+ :ignore_if => lambda { |env, e| e.message =~ /^Couldn't find Page with ID=/ }
170
+ ```
171
+
172
+ You can make use of both the environment and the exception inside the lambda to decide wether to
173
+ avoid or not sending the notification.
174
+
175
+ ### Verbose
176
+
177
+ You can also choose to exclude the exception message from the subject, which is included by default.
178
+ Use _:verbose_subject => false_ to exclude it.
179
+
180
+ ### Normalize subject
181
+
182
+ You can also choose to remove numbers from subject so they thread as a single one.
183
+ This is disabled by default.
184
+ Use _:normalize_subject => true_ to enable it.
185
+
186
+
81
187
  Background Notifications
82
188
  ---
83
189
 
@@ -85,11 +191,26 @@ If you want to send notifications from a background process like
85
191
  DelayedJob, you should use the background_exception_notification method
86
192
  like this:
87
193
 
88
- begin
89
- some code...
90
- rescue => e
91
- ExceptionNotifier::Notifier.background_exception_notification(e)
92
- end
194
+ ```ruby
195
+ begin
196
+ some code...
197
+ rescue => e
198
+ ExceptionNotifier::Notifier.background_exception_notification(e)
199
+ end
200
+ ```
201
+
202
+ You can include information about the background process that created
203
+ the error by including a data parameter:
204
+
205
+ ```ruby
206
+ begin
207
+ some code...
208
+ rescue => exception
209
+ ExceptionNotifier::Notifier.background_exception_notification(exception,
210
+ :data => {:worker => worker.to_s, :queue => queue, :payload => payload})
211
+ end
212
+ ```
213
+
93
214
 
94
215
  Manually notify of exception
95
216
  ---
@@ -97,13 +218,16 @@ Manually notify of exception
97
218
  If your controller action manually handles an error, the notifier will never be
98
219
  run. To manually notify of an error you can do something like the following:
99
220
 
100
- rescue_from Exception, :with => :server_error
221
+ ```ruby
222
+ rescue_from Exception, :with => :server_error
101
223
 
102
- def server_error(exception)
103
- # Whatever code that handles the exception
224
+ def server_error(exception)
225
+ # Whatever code that handles the exception
104
226
 
105
- ExceptionNotifier::Notifier.exception_notification(request.env, exception).deliver
106
- end
227
+ ExceptionNotifier::Notifier.exception_notification(request.env, exception,
228
+ :data => {:message => "was doing something wrong"}).deliver
229
+ end
230
+ ```
107
231
 
108
232
  Notification
109
233
  ---
@@ -111,14 +235,32 @@ Notification
111
235
  After an exception notification has been delivered the rack environment variable
112
236
  'exception_notifier.delivered' will be set to +true+.
113
237
 
114
- Rails 2.3 stable and earlier
238
+ Versions
115
239
  ---
116
240
 
117
- If you are running Rails 2.3 then see the branch for that:
118
-
241
+ NOTE: Master branch is currently set for v2.6.0
242
+
243
+ For v2.5.2, see this tag:
244
+
245
+ <a href="http://github.com/smartinez87/exception_notification/tree/v2.5.2">http://github.com/smartinez87/exception_notification/tree/v2.5.2</a>
246
+
247
+ For v2.5.0, see tag:
248
+
249
+ <a href="http://github.com/smartinez87/exception_notification/tree/v2.5.0">http://github.com/smartinez87/exception_notification/tree/v2.5.0</a>
250
+
251
+ For v2.4.1, see tag:
252
+
253
+ <a href="http://github.com/smartinez87/exception_notification/tree/v2.4.1">http://github.com/smartinez87/exception_notification/tree/v2.4.1</a>
254
+
255
+ For v2.4.0, see tag:
256
+
257
+ <a href="http://github.com/smartinez87/exception_notification/tree/v2.4.0">http://github.com/smartinez87/exception_notification/tree/v2.4.0</a>
258
+
259
+ If you are running Rails 2.3 then see the branch for that:
260
+
119
261
  <a href="http://github.com/smartinez87/exception_notification/tree/2-3-stable">http://github.com/smartinez87/exception_notification/tree/2-3-stable</a>
120
262
 
121
- If you are running pre-rack Rails then see this tag:
263
+ If you are running pre-rack Rails then see this tag:
122
264
 
123
265
  <a href="http://github.com/smartinez87/exception_notification/tree/pre-2-3">http://github.com/smartinez87/exception_notification/tree/pre-2-3</a>
124
266
 
@@ -127,4 +269,4 @@ Support and tickets
127
269
 
128
270
  <a href="https://github.com/smartinez87/exception_notification/issues">https://github.com/smartinez87/exception_notification/issues</a>
129
271
 
130
- Copyright (c) 2005 Jamis Buck, released under the MIT license
272
+ Copyright (c) 2005 Jamis Buck, released under the MIT license: <a href="http://www.opensource.org/licenses/MIT">http://www.opensource.org/licenses/MIT</a>
@@ -1,9 +1,10 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'exception_notification'
3
- s.version = '2.5.2'
3
+ s.version = '2.6.0.rc1'
4
4
  s.authors = ["Jamis Buck", "Josh Peek"]
5
- s.date = %q{2011-08-29}
5
+ s.date = %q{2012-04-05}
6
6
  s.summary = "Exception notification by email for Rails apps"
7
+ s.homepage = "https://github.com/smartinez87/exception_notification"
7
8
  s.email = "smartinez87@gmail.com"
8
9
 
9
10
  s.files = `git ls-files`.split("\n")
@@ -4,12 +4,16 @@ require 'exception_notifier/notifier'
4
4
  class ExceptionNotifier
5
5
  def self.default_ignore_exceptions
6
6
  [].tap do |exceptions|
7
- exceptions << ::ActiveRecord::RecordNotFound if defined? ::ActiveRecord::RecordNotFound
8
- exceptions << ::AbstractController::ActionNotFound if defined? ::AbstractController::ActionNotFound
9
- exceptions << ::ActionController::RoutingError if defined? ::ActionController::RoutingError
7
+ exceptions << 'ActiveRecord::RecordNotFound'
8
+ exceptions << 'AbstractController::ActionNotFound'
9
+ exceptions << 'ActionController::RoutingError'
10
10
  end
11
11
  end
12
12
 
13
+ def self.default_ignore_crawlers
14
+ []
15
+ end
16
+
13
17
  def initialize(app, options = {})
14
18
  @app, @options = app, options
15
19
 
@@ -17,8 +21,13 @@ class ExceptionNotifier
17
21
  Notifier.default_exception_recipients = @options[:exception_recipients]
18
22
  Notifier.default_email_prefix = @options[:email_prefix]
19
23
  Notifier.default_sections = @options[:sections]
24
+ Notifier.default_background_sections = @options[:background_sections]
25
+ Notifier.default_verbose_subject = @options[:verbose_subject]
26
+ Notifier.default_normalize_subject = @options[:normalize_subject]
20
27
 
21
28
  @options[:ignore_exceptions] ||= self.class.default_ignore_exceptions
29
+ @options[:ignore_crawlers] ||= self.class.default_ignore_crawlers
30
+ @options[:ignore_if] ||= lambda { |env, e| false }
22
31
  end
23
32
 
24
33
  def call(env)
@@ -27,11 +36,32 @@ class ExceptionNotifier
27
36
  options = (env['exception_notifier.options'] ||= Notifier.default_options)
28
37
  options.reverse_merge!(@options)
29
38
 
30
- unless Array.wrap(options[:ignore_exceptions]).include?(exception.class)
39
+ unless ignored_exception(options[:ignore_exceptions], exception) ||
40
+ from_crawler(options[:ignore_crawlers], env['HTTP_USER_AGENT']) ||
41
+ conditionally_ignored(options[:ignore_if], env, exception)
31
42
  Notifier.exception_notification(env, exception).deliver
32
43
  env['exception_notifier.delivered'] = true
33
44
  end
34
45
 
35
46
  raise exception
36
47
  end
48
+
49
+ private
50
+
51
+ def ignored_exception(ignore_array, exception)
52
+ Array.wrap(ignore_array).map(&:to_s).include?(exception.class.name)
53
+ end
54
+
55
+ def from_crawler(ignore_array, agent)
56
+ ignore_array.each do |crawler|
57
+ return true if (agent =~ Regexp.new(crawler))
58
+ end unless ignore_array.blank?
59
+ false
60
+ end
61
+
62
+ def conditionally_ignored(ignore_proc, env, exception)
63
+ ignore_proc.call(env, exception)
64
+ rescue Exception => ex
65
+ false
66
+ end
37
67
  end
@@ -6,7 +6,6 @@ class ExceptionNotifier
6
6
  self.mailer_name = 'exception_notifier'
7
7
 
8
8
  #Append application view path to the ExceptionNotifier lookup context.
9
- self.append_view_path Rails.root.nil? ? "app/views" : "#{Rails.root}/app/views" if defined?(Rails)
10
9
  self.append_view_path "#{File.dirname(__FILE__)}/views"
11
10
 
12
11
  class << self
@@ -14,6 +13,9 @@ class ExceptionNotifier
14
13
  attr_writer :default_exception_recipients
15
14
  attr_writer :default_email_prefix
16
15
  attr_writer :default_sections
16
+ attr_writer :default_background_sections
17
+ attr_writer :default_verbose_subject
18
+ attr_writer :default_normalize_subject
17
19
 
18
20
  def default_sender_address
19
21
  @default_sender_address || %("Exception Notifier" <exception.notifier@default.com>)
@@ -31,11 +33,30 @@ class ExceptionNotifier
31
33
  @default_sections || %w(request session environment backtrace)
32
34
  end
33
35
 
36
+ def default_background_sections
37
+ @default_background_sections || %w(backtrace data)
38
+ end
39
+
40
+ def default_verbose_subject
41
+ @default_verbose_subject.nil? || @default_verbose_subject
42
+ end
43
+
44
+ def default_normalize_subject
45
+ @default_normalize_prefix || false
46
+ end
47
+
34
48
  def default_options
35
49
  { :sender_address => default_sender_address,
36
50
  :exception_recipients => default_exception_recipients,
37
51
  :email_prefix => default_email_prefix,
38
- :sections => default_sections }
52
+ :sections => default_sections,
53
+ :background_sections => default_background_sections,
54
+ :verbose_subject => default_verbose_subject,
55
+ :normalize_subject => default_normalize_subject }
56
+ end
57
+
58
+ def normalize_digits(string)
59
+ string.gsub(/[0-9]+/, 'N')
39
60
  end
40
61
  end
41
62
 
@@ -44,37 +65,41 @@ class ExceptionNotifier
44
65
  end
45
66
  end
46
67
 
47
- def exception_notification(env, exception)
68
+ def exception_notification(env, exception, options={})
69
+ self.append_view_path Rails.root.nil? ? "app/views" : "#{Rails.root}/app/views" if defined?(Rails)
70
+
48
71
  @env = env
49
72
  @exception = exception
50
73
  @options = (env['exception_notifier.options'] || {}).reverse_merge(self.class.default_options)
51
74
  @kontroller = env['action_controller.instance'] || MissingController.new
52
75
  @request = ActionDispatch::Request.new(env)
53
- @backtrace = clean_backtrace(exception)
76
+ @backtrace = exception.backtrace ? clean_backtrace(exception) : []
54
77
  @sections = @options[:sections]
55
- data = env['exception_notifier.exception_data'] || {}
78
+ @data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
79
+ @sections = @sections + %w(data) unless @data.empty?
56
80
 
57
- data.each do |name, value|
81
+ @data.each do |name, value|
58
82
  instance_variable_set("@#{name}", value)
59
83
  end
60
-
61
- prefix = "#{@options[:email_prefix]}#{@kontroller.controller_name}##{@kontroller.action_name}"
62
- subject = "#{prefix} (#{@exception.class}) #{@exception.message.inspect}"
63
- subject = subject.length > 120 ? subject[0...120] + "..." : subject
84
+ subject = compose_subject(exception, @kontroller)
64
85
 
65
86
  mail(:to => @options[:exception_recipients], :from => @options[:sender_address], :subject => subject) do |format|
66
87
  format.text { render "#{mailer_name}/exception_notification" }
67
88
  end
68
89
  end
69
90
 
70
- def background_exception_notification(exception)
91
+ def background_exception_notification(exception, options={})
71
92
  if @notifier = Rails.application.config.middleware.detect{ |x| x.klass == ExceptionNotifier }
72
- @options = (@notifier.args.first || {}).reverse_merge(self.class.default_options)
73
- subject = "#{@options[:email_prefix]} (#{exception.class}) #{exception.message.inspect}"
74
-
93
+ @options = (@notifier.args.first || {}).reverse_merge(self.class.default_options)
75
94
  @exception = exception
76
95
  @backtrace = exception.backtrace || []
77
- @sections = %w{backtrace}
96
+ @sections = @options[:background_sections]
97
+ @data = options[:data] || {}
98
+
99
+ @data.each do |name, value|
100
+ instance_variable_set("@#{name}", value)
101
+ end
102
+ subject = compose_subject(exception)
78
103
 
79
104
  mail(:to => @options[:exception_recipients], :from => @options[:sender_address], :subject => subject) do |format|
80
105
  format.text { render "#{mailer_name}/background_exception_notification" }
@@ -84,6 +109,15 @@ class ExceptionNotifier
84
109
 
85
110
  private
86
111
 
112
+ def compose_subject(exception, kontroller=nil)
113
+ subject = "#{@options[:email_prefix]}"
114
+ subject << "#{kontroller.controller_name}##{kontroller.action_name}" if kontroller
115
+ subject << " (#{exception.class})"
116
+ subject << " #{exception.message.inspect}" if @options[:verbose_subject]
117
+ subject = normalize_digits(subject) if @options[:normalize_subject]
118
+ subject.length > 120 ? subject[0...120] + "..." : subject
119
+ end
120
+
87
121
  def clean_backtrace(exception)
88
122
  if Rails.respond_to?(:backtrace_cleaner)
89
123
  Rails.backtrace_cleaner.send(:filter, exception.backtrace)
@@ -0,0 +1 @@
1
+ * data: <%= raw PP.pp(@data, "") %>
@@ -1,8 +1,8 @@
1
1
  <% filtered_env = @request.filtered_env -%>
2
- <% max = filtered_env.keys.max { |a, b| a.length <=> b.length } -%>
3
- <% filtered_env.keys.sort.each do |key| -%>
2
+ <% max = filtered_env.keys.map(&:to_s).max { |a, b| a.length <=> b.length } -%>
3
+ <% filtered_env.keys.map(&:to_s).sort.each do |key| -%>
4
4
  * <%= raw("%-*s: %s" % [max.length, key, inspect_object(filtered_env[key])]) %>
5
5
  <% end -%>
6
6
 
7
7
  * Process: <%= raw $$ %>
8
- * Server : <%= raw `hostname -s`.chomp %>
8
+ * Server : <%= raw `hostname`.chomp %>
@@ -2,3 +2,4 @@
2
2
  * IP address: <%= raw @request.remote_ip %>
3
3
  * Parameters: <%= raw @request.filtered_parameters.inspect %>
4
4
  * Rails root: <%= raw Rails.root %>
5
+ * Timestamp : <%= raw Time.current %>
@@ -1,2 +1,2 @@
1
- * session id: <%= @request.ssl? ? "[FILTERED]" : (raw @request.session['session_id'].inspect.html_safe) %>
1
+ * session id: <%= @request.ssl? ? "[FILTERED]" : (raw (@request.session['session_id'] || @request.env["rack.session.options"][:id]).inspect.html_safe) %>
2
2
  * data: <%= raw PP.pp(@request.session, "") %>
@@ -1,4 +1,4 @@
1
- A <%= @exception.class %> occurred in background:
1
+ A <%= @exception.class %> occurred in background at <%= raw Time.current %> :
2
2
 
3
3
  <%= @exception.message %>
4
4
  <%= @backtrace.first %>
@@ -3,11 +3,22 @@ A <%= @exception.class %> occurred in <%= @kontroller.controller_name %>#<%= @ko
3
3
  <%= raw @exception.message %>
4
4
  <%= raw @backtrace.first %>
5
5
 
6
- <% sections = @sections.map do |section|
7
- summary = render(section).strip
8
- unless summary.blank?
6
+ <%
7
+ sections = @sections.map do |section|
8
+ begin
9
+ summary = render(section).strip
10
+ unless summary.blank?
11
+ title = render("title", :title => section).strip
12
+ "#{title}\n\n#{summary.gsub(/^/, " ")}\n\n"
13
+ end
14
+
15
+ rescue Exception => e
9
16
  title = render("title", :title => section).strip
10
- "#{title}\n\n#{summary.gsub(/^/, " ")}\n\n"
17
+ summary = ["ERROR: Failed to generate exception summary:", [e.class.to_s, e.message].join(": "), e.backtrace && e.backtrace.join("\n")].compact.join("\n\n")
18
+
19
+ [title, summary.gsub(/^/, " "), nil].join("\n\n")
11
20
  end
12
- end %>
21
+ end
22
+ %>
23
+
13
24
  <%= raw sections.join %>
@@ -6,7 +6,9 @@ class BackgroundExceptionNotificationTest < ActiveSupport::TestCase
6
6
  1/0
7
7
  rescue => e
8
8
  @exception = e
9
- @mail = ExceptionNotifier::Notifier.background_exception_notification(@exception)
9
+ @time = Time.current
10
+ @mail = ExceptionNotifier::Notifier.background_exception_notification(@exception,
11
+ :data => {:job => 'DivideWorkerJob', :payload => '1/0', :message => 'My Custom Message'})
10
12
  end
11
13
  end
12
14
 
@@ -27,17 +29,24 @@ class BackgroundExceptionNotificationTest < ActiveSupport::TestCase
27
29
  end
28
30
 
29
31
  test "mail should have a descriptive subject" do
30
- assert @mail.subject.include? "[Dummy ERROR] (ZeroDivisionError) \"divided by 0\""
32
+ assert @mail.subject == "[Dummy ERROR] (ZeroDivisionError) \"divided by 0\""
31
33
  end
32
34
 
33
- test "mail should say exception was raised in background" do
34
- assert @mail.body.include? "A ZeroDivisionError occurred in background"
35
+ test "mail should say exception was raised in background at show timestamp" do
36
+ assert @mail.body.include? "A ZeroDivisionError occurred in background at #{@time}"
35
37
  end
36
38
 
37
39
  test "mail should contain backtrace in body" do
38
40
  assert @mail.body.include? "test/background_exception_notification_test.rb:6"
39
41
  end
40
42
 
43
+ test "mail should contain data in body" do
44
+ assert @mail.body.include? '* data:'
45
+ assert @mail.body.include? ':payload=>"1/0"'
46
+ assert @mail.body.include? ':job=>"DivideWorkerJob"'
47
+ assert @mail.body.include? "My Custom Message"
48
+ end
49
+
41
50
  test "mail should not contain any attachments" do
42
51
  assert @mail.attachments == []
43
52
  end
@@ -47,7 +56,7 @@ class BackgroundExceptionNotificationTest < ActiveSupport::TestCase
47
56
  raise ActiveRecord::RecordNotFound
48
57
  rescue => e
49
58
  @ignored_exception = e
50
- unless ExceptionNotifier.default_ignore_exceptions.include?(@ignored_exception.class)
59
+ unless ExceptionNotifier.default_ignore_exceptions.include?(@ignored_exception.class.name)
51
60
  @ignored_mail = ExceptionNotifier::Notifier.background_exception_notification(@ignored_exception)
52
61
  end
53
62
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../../..
3
3
  specs:
4
- exception_notification (2.4.1)
4
+ exception_notification (2.5.2)
5
5
  actionmailer (>= 3.0.4)
6
6
 
7
7
  GEM
@@ -0,0 +1 @@
1
+ * New section for testing
@@ -4,7 +4,8 @@ require File.expand_path('../application', __FILE__)
4
4
  Dummy::Application.config.middleware.use ExceptionNotifier,
5
5
  :email_prefix => "[Dummy ERROR] ",
6
6
  :sender_address => %{"Dummy Notifier" <dummynotifier@example.com>},
7
- :exception_recipients => %w{dummyexceptions@example.com}
7
+ :exception_recipients => %w{dummyexceptions@example.com},
8
+ :sections => ['new_section', 'request', 'session', 'environment', 'backtrace']
8
9
 
9
10
  # Initialize the rails application
10
11
  Dummy::Application.initialize!
@@ -7,7 +7,7 @@ class PostsControllerTest < ActionController::TestCase
7
7
  post :create, :post => @post.attributes
8
8
  rescue => e
9
9
  @exception = e
10
- @mail = ExceptionNotifier::Notifier.exception_notification(request.env, @exception)
10
+ @mail = ExceptionNotifier::Notifier.exception_notification(request.env, @exception, {:data => {:message => 'My Custom Message'}})
11
11
  end
12
12
  end
13
13
 
@@ -28,13 +28,25 @@ class PostsControllerTest < ActionController::TestCase
28
28
  end
29
29
 
30
30
  test "mail should have a descriptive subject" do
31
- assert @mail.subject.include? "[Dummy ERROR] # (NoMethodError)"
31
+ assert @mail.subject.include? "[Dummy ERROR] # (NoMethodError) \"undefined method `nw'"
32
32
  end
33
33
 
34
34
  test "mail should contain backtrace in body" do
35
35
  assert @mail.body.include? "`method_missing'\n app/controllers/posts_controller.rb:17:in `create'\n"
36
36
  end
37
37
 
38
+ test "mail should contain timestamp of exception in body" do
39
+ assert @mail.body.include? "Timestamp : #{Time.current}"
40
+ end
41
+
42
+ test "mail should contain the newly defined section" do
43
+ assert @mail.body.include? "* New section for testing"
44
+ end
45
+
46
+ test "mail should contain the custom message" do
47
+ assert @mail.body.include? "My Custom Message"
48
+ end
49
+
38
50
  test "should filter sensible data" do
39
51
  assert @mail.body.include? "secret\"=>\"[FILTERED]"
40
52
  end
@@ -48,7 +60,7 @@ class PostsControllerTest < ActionController::TestCase
48
60
  get :show, :id => @post.to_param + "10"
49
61
  rescue => e
50
62
  @ignored_exception = e
51
- unless ExceptionNotifier.default_ignore_exceptions.include?(@ignored_exception.class)
63
+ unless ExceptionNotifier.default_ignore_exceptions.include?(@ignored_exception.class.name)
52
64
  @ignored_mail = ExceptionNotifier::Notifier.exception_notification(request.env, @ignored_exception)
53
65
  end
54
66
  end
@@ -69,4 +81,87 @@ class PostsControllerTest < ActionController::TestCase
69
81
  assert request.ssl?
70
82
  assert @secured_mail.body.include? "* session id: [FILTERED]\n *"
71
83
  end
84
+
85
+ test "should ignore exception if from unwanted cralwer" do
86
+ request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
87
+ begin
88
+ @post = posts(:one)
89
+ post :create, :post => @post.attributes
90
+ rescue => e
91
+ @exception = e
92
+ custom_env = request.env
93
+ custom_env['exception_notifier.options'] ||= {}
94
+ custom_env['exception_notifier.options'].merge!(:ignore_crawlers => %w(Googlebot))
95
+ ignore_array = custom_env['exception_notifier.options'][:ignore_crawlers]
96
+ unless ExceptionNotifier.new(Dummy::Application, custom_env['exception_notifier.options']).send(:from_crawler, ignore_array, custom_env['HTTP_USER_AGENT'])
97
+ @ignored_mail = ExceptionNotifier::Notifier.exception_notification(custom_env, @exception)
98
+ end
99
+ end
100
+
101
+ assert_nil @ignored_mail
102
+ end
103
+
104
+ test "should ignore exception if satisfies conditional ignore" do
105
+ request.env['IGNOREME'] = "IGNOREME"
106
+ begin
107
+ @post = posts(:one)
108
+ post :create, :post => @post.attributes
109
+ rescue => e
110
+ @exception = e
111
+ custom_env = request.env
112
+ custom_env['exception_notifier.options'] ||= {}
113
+ ignore_cond = {:ignore_if => lambda {|env, e| (env['IGNOREME'] == 'IGNOREME') && (e.message =~ /undefined method/)}}
114
+ custom_env['exception_notifier.options'].merge!(ignore_cond)
115
+ unless ExceptionNotifier.new(Dummy::Application, custom_env['exception_notifier.options']).send(:conditionally_ignored, ignore_cond[:ignore_if], custom_env, @exception)
116
+ @ignored_mail = ExceptionNotifier::Notifier.exception_notification(custom_env, @exception)
117
+ end
118
+ end
119
+
120
+ assert_nil @ignored_mail
121
+ end
122
+ end
123
+
124
+ class PostsControllerTestWithoutVerboseSubject < ActionController::TestCase
125
+ tests PostsController
126
+ setup do
127
+ ExceptionNotifier::Notifier.default_verbose_subject = false
128
+ begin
129
+ @post = posts(:one)
130
+ post :create, :post => @post.attributes
131
+ rescue => e
132
+ @exception = e
133
+ @mail = ExceptionNotifier::Notifier.exception_notification(request.env, @exception)
134
+ end
135
+ end
136
+
137
+ test "should not include exception message in subject" do
138
+ assert_equal "[ERROR] # (NoMethodError)", @mail.subject
139
+ end
140
+ end
141
+
142
+ class PostsControllerTestBadRequestData < ActionController::TestCase
143
+ tests PostsController
144
+ setup do
145
+ begin
146
+ # This might seem synthetic, but the point is that the data used by
147
+ # ExceptionNotification could be rendered "invalid" by e.g. a badly
148
+ # behaving middleware, and we want to test that ExceptionNotification
149
+ # still manages to send off an email in those cases.
150
+ #
151
+ # The trick here is to trigger an exception in the template used by
152
+ # ExceptionNotification. (The original test stuffed request.env with
153
+ # badly encoded strings, but that only works in Ruby 1.9+.)
154
+ request.send :instance_variable_set, :@env, {}
155
+
156
+ @post = posts(:one)
157
+ post :create, :post => @post.attributes
158
+ rescue => e
159
+ @exception = e
160
+ @mail = ExceptionNotifier::Notifier.exception_notification(request.env, @exception)
161
+ end
162
+ end
163
+
164
+ test "should include error message in body" do
165
+ assert_match /ERROR: Failed to generate exception summary/, @mail.body.to_s
166
+ end
72
167
  end
@@ -2,7 +2,7 @@ require 'test_helper'
2
2
 
3
3
  class ExceptionNotificationTest < ActiveSupport::TestCase
4
4
  test "should have default ignored exceptions" do
5
- assert ExceptionNotifier.default_ignore_exceptions == [ActiveRecord::RecordNotFound, AbstractController::ActionNotFound, ActionController::RoutingError]
5
+ assert ExceptionNotifier.default_ignore_exceptions == ['ActiveRecord::RecordNotFound', 'AbstractController::ActionNotFound', 'ActionController::RoutingError']
6
6
  end
7
7
 
8
8
  test "should have default sender address overriden" do
@@ -14,6 +14,29 @@ class ExceptionNotificationTest < ActiveSupport::TestCase
14
14
  end
15
15
 
16
16
  test "should have default sections" do
17
- assert ExceptionNotifier::Notifier.default_sections == %w(request session environment backtrace)
17
+ for section in %w(request session environment backtrace)
18
+ assert ExceptionNotifier::Notifier.default_sections.include? section
19
+ end
20
+ end
21
+
22
+ test "should have default background sections" do
23
+ assert ExceptionNotifier::Notifier.default_background_sections == %w(backtrace data)
24
+ end
25
+
26
+ test "should have verbose subject by default" do
27
+ assert ExceptionNotifier::Notifier.default_options[:verbose_subject] == true
28
+ end
29
+
30
+ test "should have ignored crawler by default" do
31
+ assert ExceptionNotifier.default_ignore_crawlers == []
32
+ end
33
+
34
+ test "should normalize multiple digits into one N" do
35
+ assert_equal 'N foo N bar N baz N',
36
+ ExceptionNotifier::Notifier.normalize_digits('1 foo 12 bar 123 baz 1234')
37
+ end
38
+
39
+ test "should have normalize_subject false by default" do
40
+ assert ExceptionNotifier::Notifier.default_options[:normalize_subject] == false
18
41
  end
19
42
  end
metadata CHANGED
@@ -1,81 +1,71 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: exception_notification
3
- version: !ruby/object:Gem::Version
4
- hash: 31
5
- prerelease:
6
- segments:
7
- - 2
8
- - 5
9
- - 2
10
- version: 2.5.2
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.6.0.rc1
5
+ prerelease: 6
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jamis Buck
14
9
  - Josh Peek
15
10
  autorequire:
16
11
  bindir: bin
17
12
  cert_chain: []
18
-
19
- date: 2011-08-29 00:00:00 -03:00
20
- default_executable:
21
- dependencies:
22
- - !ruby/object:Gem::Dependency
13
+ date: 2012-04-05 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
23
16
  name: actionmailer
24
- prerelease: false
25
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: !ruby/object:Gem::Requirement
26
18
  none: false
27
- requirements:
28
- - - ">="
29
- - !ruby/object:Gem::Version
30
- hash: 15
31
- segments:
32
- - 3
33
- - 0
34
- - 4
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
35
22
  version: 3.0.4
36
23
  type: :runtime
37
- version_requirements: *id001
38
- - !ruby/object:Gem::Dependency
39
- name: rails
40
24
  prerelease: false
41
- requirement: &id002 !ruby/object:Gem::Requirement
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: 3.0.4
31
+ - !ruby/object:Gem::Dependency
32
+ name: rails
33
+ requirement: !ruby/object:Gem::Requirement
42
34
  none: false
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- hash: 15
47
- segments:
48
- - 3
49
- - 0
50
- - 4
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
51
38
  version: 3.0.4
52
39
  type: :development
53
- version_requirements: *id002
54
- - !ruby/object:Gem::Dependency
55
- name: sqlite3
56
40
  prerelease: false
57
- requirement: &id003 !ruby/object:Gem::Requirement
41
+ version_requirements: !ruby/object:Gem::Requirement
58
42
  none: false
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- hash: 19
63
- segments:
64
- - 1
65
- - 3
66
- - 4
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 3.0.4
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
67
54
  version: 1.3.4
68
55
  type: :development
69
- version_requirements: *id003
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: 1.3.4
70
63
  description:
71
64
  email: smartinez87@gmail.com
72
65
  executables: []
73
-
74
66
  extensions: []
75
-
76
67
  extra_rdoc_files: []
77
-
78
- files:
68
+ files:
79
69
  - .gemtest
80
70
  - Gemfile
81
71
  - Gemfile.lock
@@ -86,6 +76,7 @@ files:
86
76
  - lib/exception_notifier.rb
87
77
  - lib/exception_notifier/notifier.rb
88
78
  - lib/exception_notifier/views/exception_notifier/_backtrace.text.erb
79
+ - lib/exception_notifier/views/exception_notifier/_data.text.erb
89
80
  - lib/exception_notifier/views/exception_notifier/_environment.text.erb
90
81
  - lib/exception_notifier/views/exception_notifier/_request.text.erb
91
82
  - lib/exception_notifier/views/exception_notifier/_session.text.erb
@@ -102,6 +93,7 @@ files:
102
93
  - test/dummy/app/helpers/application_helper.rb
103
94
  - test/dummy/app/helpers/posts_helper.rb
104
95
  - test/dummy/app/models/post.rb
96
+ - test/dummy/app/views/exception_notifier/_new_section.text.erb
105
97
  - test/dummy/app/views/layouts/application.html.erb
106
98
  - test/dummy/app/views/posts/_form.html.erb
107
99
  - test/dummy/app/views/posts/new.html.erb
@@ -146,41 +138,31 @@ files:
146
138
  - test/dummy/test/test_helper.rb
147
139
  - test/exception_notification_test.rb
148
140
  - test/test_helper.rb
149
- has_rdoc: true
150
- homepage:
141
+ homepage: https://github.com/smartinez87/exception_notification
151
142
  licenses: []
152
-
153
143
  post_install_message:
154
144
  rdoc_options: []
155
-
156
- require_paths:
145
+ require_paths:
157
146
  - lib
158
- required_ruby_version: !ruby/object:Gem::Requirement
147
+ required_ruby_version: !ruby/object:Gem::Requirement
159
148
  none: false
160
- requirements:
161
- - - ">="
162
- - !ruby/object:Gem::Version
163
- hash: 3
164
- segments:
165
- - 0
166
- version: "0"
167
- required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ! '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
154
  none: false
169
- requirements:
170
- - - ">="
171
- - !ruby/object:Gem::Version
172
- hash: 3
173
- segments:
174
- - 0
175
- version: "0"
155
+ requirements:
156
+ - - ! '>'
157
+ - !ruby/object:Gem::Version
158
+ version: 1.3.1
176
159
  requirements: []
177
-
178
160
  rubyforge_project:
179
- rubygems_version: 1.5.2
161
+ rubygems_version: 1.8.21
180
162
  signing_key:
181
163
  specification_version: 3
182
164
  summary: Exception notification by email for Rails apps
183
- test_files:
165
+ test_files:
184
166
  - test/background_exception_notification_test.rb
185
167
  - test/dummy/.gitignore
186
168
  - test/dummy/Gemfile
@@ -191,6 +173,7 @@ test_files:
191
173
  - test/dummy/app/helpers/application_helper.rb
192
174
  - test/dummy/app/helpers/posts_helper.rb
193
175
  - test/dummy/app/models/post.rb
176
+ - test/dummy/app/views/exception_notifier/_new_section.text.erb
194
177
  - test/dummy/app/views/layouts/application.html.erb
195
178
  - test/dummy/app/views/posts/_form.html.erb
196
179
  - test/dummy/app/views/posts/new.html.erb