wrangler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ # Copyright (c) 2009 Umamibud, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,247 @@
1
+ = Wrangler
2
+
3
+ == NOTE/DISCLAIMER
4
+ This gem is almost completely inspired by/ripped off the exception_notification
5
+ plugin/gem, but had to hack too much to get things to work with delayed_job
6
+ that I just decided to start from scratch. You'll see that much has been
7
+ borrowed however, so I owe a huge debt to the originals (exception_notification
8
+ [http://github.com/rails/exception_notification] and
9
+ super_exception_notification [http://github.com/pboling/exception_notification])
10
+ to help me recreate the Rails hacking.
11
+
12
+ If you don't really care about using delayed_job for your emailing, consider
13
+ going back to the originals, as they're likely better maintained.... ;)
14
+
15
+ == Overview:
16
+ A gem for handling exceptions in a Rails application/environment.
17
+
18
+ Some highlights:
19
+ * Allows for rendering error pages and sending email notifications in case of
20
+ errors.
21
+ * Allows for lots of configuring of which pages to show, whether to email or not.
22
+ * Allows for asynchronous emailing through delayed_job if available, but works
23
+ fine even if delayed_job not installed (email will be synchronous however)
24
+ * Will honor filter_parameters set in the controller including the Wrangler
25
+ module (except if the parameters are in the URL for GETs) when logging and
26
+ emailing application state at the time of the exception
27
+ * Allows email notification on exceptions thrown totally outside Controller
28
+ context, e.g. in a script run (with rails environment) in a cronjob or
29
+ delayed_job
30
+
31
+ == Quickstart
32
+ === Bare Minimum
33
+ There are a lot of defaults set, so getting started should be pretty easy. In
34
+ fact, if you don't want any email notifications, you basically just need to
35
+ include the Wrangler module in your controller:
36
+ # application_controller.rb
37
+ class ApplicationController < ActionController::Base
38
+ include Wrangler
39
+
40
+ ...
41
+ end
42
+
43
+ === Enabling email notifications
44
+ Email notifications are configured to be sent with the default configuration,
45
+ but you'll need to specify from and to addresses for emails to actually be sent.
46
+ So that brings us to the configuration of Wrangler. Recommended: just create a
47
+ new initializer file (create a wrangler.rb (or whatever you want to name the file)
48
+ in RAILS_ROOT/config/initializers/wrangler.rb). In it, add the following:
49
+
50
+ Wrangler::ExceptionNotifier.configure do |notifier_config|
51
+ notifier_config.merge! :from_address => 'monitor@umamibud.com',
52
+ :recipient_addresses => ['u-ops-monitor@umamibud.com']
53
+ end
54
+
55
+ And, if you haven't already configured ActionMailer to send emails, you'll need
56
+ to do that (e.g. setting ActionMailer::Base.smtp_settings), and even after you
57
+ have, you may want to change some settings in order to send from a different
58
+ account from the one you may use to email your users (e.g. change the :user_name
59
+ for smtp_settings):
60
+
61
+ Wrangler::ExceptionNotifier.smtp_settings.merge! :user_name => 'notifier@mydomain.com'
62
+
63
+ (Recommend just putting that in the same wrangler.rb initializer you created above)
64
+
65
+ For more info on smtp_settings, see ActionMailer (http://am.rubyonrails.org/)
66
+
67
+ == Configuration
68
+ There are two different classes that receive configuration, ExceptionHandler and
69
+ ExceptionNotifier.
70
+
71
+ ExceptionHandler stores configurations about what to do about exceptions (whether to
72
+ handle them, email them, which error templates to render for each exception...).
73
+
74
+ ExceptionNotifier handles the sending of emails when exceptions occur, so
75
+ stores configurations about where to send the emails.
76
+
77
+ You override defaults on each using the same syntax (as seen above) by calling the
78
+ configure() method on the class and using the config hash that is yielded to
79
+ set your configurations. See the method documentation for the configure() methods
80
+ themselves, but here's the basic idea:
81
+
82
+ Wrangler::ExceptionHandler.configure do |handler_config|
83
+ handler_config[:key1] = value1
84
+ handler_config[:key2] = value2
85
+ handler_config[:key_for_a_hash].merge! :subkey => value
86
+ handler_config[:key_for_an_array] << another_value
87
+ end
88
+
89
+ OR
90
+
91
+ Wrangler::ExceptionHandler.configure do |handler_config|
92
+ handler_config.merge! :key1 => value1,
93
+ :key2 => value2,
94
+ handler_config[:key_for_a_hash].merge! :subkey => value
95
+ handler_config[:key_for_an_array] << another_value
96
+ end
97
+
98
+ (same with Wrangler::ExceptionNotifier, except different classname)
99
+
100
+ Most configurations are single values (e.g. nums or strings),
101
+ but some are hashes or arrays. You can either overwrite the hashes/arrays, or
102
+ selectively delete, or just append. Recommend just appending to the defaults
103
+ in most cases, but if you know what you're doing, you can do whatever you like!
104
+
105
+ Here is the full set of configuration values for both classes, as well as their
106
+ default values (pasted in from the classes, so you can check the code directly
107
+ to make sure you've got the latest! :) ):
108
+
109
+ ####################
110
+ # ExceptionHandler:
111
+ ####################
112
+
113
+ :app_name => '',
114
+ :handle_local_errors => false,
115
+ :handle_public_errors => true,
116
+ # send email for local reqeusts. ignored if :handle_local_errors false
117
+ :notify_on_local_error => false,
118
+ # send email for public requests. ignored if :handle_public_errors false
119
+ :notify_on_public_error => true,
120
+ # send email for exceptions caught outside of a controller context
121
+ :notify_on_background_error => true,
122
+ # configure whether to send emails synchronously or asynchronously
123
+ # using delayed_job (these can be true even if delayed job is not
124
+ # installed, in which case, will just send emails synchronously anyway)
125
+ :delayed_job_for_controller_errors => false,
126
+ :delayed_job_for_non_controller_errors => false,
127
+ # mappings from exception classes to http status codes (see above)
128
+ # add/remove from this list as desired in environment configuration
129
+ :error_class_status_codes => Wrangler::codes_for_exception_classes,
130
+ # explicitly indicate which exceptions to send email notifications for
131
+ :notify_exception_classes => %w(),
132
+ # indicate which http status codes should result in email notification
133
+ :notify_status_codes => %w( 405 500 503 ),
134
+ # where to look for app-specific error page templates (ones you create
135
+ # yourself, for example...there are some defaults in this gem you can
136
+ # use as well...and that are configured already by default)
137
+ :error_template_dir => File.join(RAILS_ROOT, 'app', 'views', 'error'),
138
+ # excplicit mappings from exception class to arbitrary error page
139
+ # templates, different set for html and js responses (Wrangler determines
140
+ # which to use automatically, so you can have an entry in both
141
+ # hashes for the same error class)
142
+ :error_class_html_templates => {},
143
+ :error_class_js_templates => {},
144
+ # you can specify a fallback failsafe error template to render if
145
+ # no appropriate template is found in the usual places (you shouldn't
146
+ # rely on this, and error messages will be logged if this template is
147
+ # used). note: there's an even more failsafe template included in the
148
+ # gem (absolute_last_resort...) below, but DON'T CHANGE IT!!!
149
+ :default_error_template => '',
150
+ # these filter out any HTTP params that are undesired
151
+ :request_env_to_skip => [ /^rack\./,
152
+ "action_controller.rescue.request",
153
+ "action_controller.rescue.response" ],
154
+ # mapping from exception classes to templates (if desired), express
155
+ # in absolute paths. use wildcards like on cmd line (glob-like), NOT
156
+ # regexp-style
157
+
158
+ # just DON'T change this! this is the error template of last resort!
159
+ :absolute_last_resort_default_error_template =>
160
+ File.join(WRANGLER_ROOT,'rails','app','views','wrangler','500.html')
161
+
162
+ #####################
163
+ # ExceptionNotifier:
164
+ #####################
165
+
166
+ # who the emails will be coming from. if nil or missing or empty string,
167
+ # effectively disables email notification
168
+ :from_address => '',
169
+ # array of addresses that the emails will be sent to. if nil or missing
170
+ # or empty array, effectively disables email notification.
171
+ :recipient_addresses => [],
172
+ # what will show up at the beginning of the subject line for each email
173
+ # sent note: will be preceded by "[<app_name (if any)>...", where app_name
174
+ # is the :app_name config value from ExceptionHandler (or explicit
175
+ # proc_name given to notify_on_error() method)
176
+ :subject_prefix => "#{(defined?(Rails) ? Rails.env : RAILS_ENV).capitalize} ERROR",
177
+ # can use this to define app-specific mail views using the same data
178
+ # made available in exception_notification()
179
+ :mailer_template_root => File.join(WRANGLER_ROOT, 'views')
180
+
181
+ == Search algorithm for error templates (given an exception and a status_code):
182
+ When trying to find an appropriate error page template to render, Wrangler
183
+ goes through several different attempts to locate an appropriate template,
184
+ beginning with templates you've explicitly associated with the exception class
185
+ or status code that has arisen...and on through to assuming default file naming
186
+ conventions and finally failsafe default templates.
187
+
188
+ # if there is an explicit mapping from the exception to an error page in
189
+ :error_class_xxx_templates, use that
190
+ # if there is a mapping in :error_class_templates for which the exception
191
+ returns true to an is_a? call, use that
192
+ # if there is a file/template corresponding to the exception name
193
+ (underscorified) in one of the following locations, use that:
194
+ ** config[:error_template_dir]/
195
+ ** RAILS_ROOT/public/
196
+ ** WRANGLER_ROOT/rails/app/views/wrangler/
197
+ # if there is a file/template corresponding to the status code
198
+ (e.g. named ###.html.erb where ### is the status code) in one of the following
199
+ locations, use that:
200
+ ** config[:error_template_dir]/
201
+ ** RAILS_ROOT/public/
202
+ ** WRANGLER_ROOT/rails/app/views/wrangler/
203
+ # if there is a file/template corresponding to a parent class name of the
204
+ exception (underscorified) one of the following locations, use that:
205
+ ** config[:error_template_dir]/
206
+ ** RAILS_ROOT/public/
207
+ ** WRANGLER_ROOT/rails/app/views/wrangler/
208
+ # :default_error_template
209
+ # :absolute_last_resort_default_error_template
210
+
211
+ == Using outside a Controller:
212
+ You can still use Wrangler outside the context of a Controller class. If you'll
213
+ be running within the context of an object instance, you can just include Wrangler
214
+ in the object's class. If you'll be running 'static' code, you can refer to
215
+ relevant methods via the Wrangler module. Note that in both cases, you'll be
216
+ calling the notify_on_error() method. Also note that the notify_on_error()
217
+ method will re-raise the exception that occurred in the block, so you may want
218
+ to begin/rescue/end around the notify_on_error() method call
219
+
220
+ using in an object instance:
221
+
222
+ class MyClass
223
+ include Wrangler
224
+
225
+ def my_error_method; raise "error!"; end
226
+
227
+ def call_a_method
228
+ notify_on_error { my_error_method }
229
+ rescue => e
230
+ exit
231
+ end
232
+ end
233
+
234
+ using 'statically':
235
+
236
+ Wrangler::notify_on_error { run_some_method_that_might_raise_exceptions }
237
+
238
+ == Maintaining the Wrangler gem
239
+ Should be pretty straightforward. Note that we're using jeweler, so the .gemspec
240
+ isn't included in the git repos; it gets generated dynamically from the settings
241
+ in Rakefile.
242
+
243
+ To build:
244
+
245
+ cd .../wrangler
246
+ rake gemspec
247
+ rake build
@@ -0,0 +1,280 @@
1
+ module Wrangler
2
+
3
+ # a utility method that should only be used internally. don't call this; it
4
+ # should only be called once by the Config class and you can get/set it there.
5
+ # returns a mapping from exception classes to http status codes
6
+ #-----------------------------------------------------------------------------
7
+ def self.codes_for_exception_classes
8
+ classes = {
9
+ # These are standard errors in rails / ruby
10
+ NameError => "503",
11
+ TypeError => "503",
12
+ RuntimeError => "500",
13
+ ArgumentError => "500",
14
+ # the default mapping for an unrecognized exception class
15
+ :default => "500"
16
+ }
17
+
18
+ # from exception_notification gem:
19
+ # Highly dependent on the verison of rails, so we're very protective about these'
20
+ classes.merge!({ ActionView::TemplateError => "500"}) if defined?(ActionView) && ActionView.const_defined?(:TemplateError)
21
+ classes.merge!({ ActiveRecord::RecordNotFound => "400" }) if defined?(ActiveRecord) && ActiveRecord.const_defined?(:RecordNotFound)
22
+ classes.merge!({ ActiveResource::ResourceNotFound => "404" }) if defined?(ActiveResource) && ActiveResource.const_defined?(:ResourceNotFound)
23
+
24
+ # from exception_notification gem:
25
+ if defined?(ActionController)
26
+ classes.merge!({ ActionController::UnknownController => "404" }) if ActionController.const_defined?(:UnknownController)
27
+ classes.merge!({ ActionController::MissingTemplate => "404" }) if ActionController.const_defined?(:MissingTemplate)
28
+ classes.merge!({ ActionController::MethodNotAllowed => "405" }) if ActionController.const_defined?(:MethodNotAllowed)
29
+ classes.merge!({ ActionController::UnknownAction => "501" }) if ActionController.const_defined?(:UnknownAction)
30
+ classes.merge!({ ActionController::RoutingError => "404" }) if ActionController.const_defined?(:RoutingError)
31
+ classes.merge!({ ActionController::InvalidAuthenticityToken => "405" }) if ActionController.const_defined?(:InvalidAuthenticityToken)
32
+ end
33
+
34
+ return classes
35
+ end
36
+
37
+ # class that holds configuration for the exception handling logic. may also
38
+ # include a helper method or two, but the main interaction with
39
+ # ExceptionHandler is setting and getting config, e.g.
40
+ #
41
+ # Wrangler::ExceptionHandler.configure do |handler_config|
42
+ # handler_config.merge! :key => value
43
+ # end
44
+ #-----------------------------------------------------------------------------
45
+ class ExceptionHandler
46
+
47
+ # the default configuration
48
+ @@config ||= {
49
+ :app_name => '',
50
+ :handle_local_errors => false,
51
+ :handle_public_errors => true,
52
+ # send email for local reqeusts. ignored if :handle_local_errors false
53
+ :notify_on_local_error => false,
54
+ # send email for public requests. ignored if :handle_public_errors false
55
+ :notify_on_public_error => true,
56
+ # send email for exceptions caught outside of a controller context
57
+ :notify_on_background_error => true,
58
+ # configure whether to send emails synchronously or asynchronously
59
+ # using delayed_job (these can be true even if delayed job is not
60
+ # installed, in which case, will just send emails synchronously anyway)
61
+ :delayed_job_for_controller_errors => false,
62
+ :delayed_job_for_non_controller_errors => false,
63
+ # mappings from exception classes to http status codes (see above)
64
+ # add/remove from this list as desired in environment configuration
65
+ :error_class_status_codes => Wrangler::codes_for_exception_classes,
66
+ # explicitly indicate which exceptions to send email notifications for
67
+ :notify_exception_classes => %w(),
68
+ # indicate which http status codes should result in email notification
69
+ :notify_status_codes => %w( 405 500 503 ),
70
+ # where to look for app-specific error page templates (ones you create
71
+ # yourself, for example...there are some defaults in this gem you can
72
+ # use as well...and that are configured already by default)
73
+ :error_template_dir => File.join(RAILS_ROOT, 'app', 'views', 'error'),
74
+ # excplicit mappings from exception class to arbitrary error page
75
+ # templates, different set for html and js responses (Wrangler determines
76
+ # which to use automatically, so you can have an entry in both
77
+ # hashes for the same error class)
78
+ :error_class_html_templates => {},
79
+ :error_class_js_templates => {},
80
+ # you can specify a fallback failsafe error template to render if
81
+ # no appropriate template is found in the usual places (you shouldn't
82
+ # rely on this, and error messages will be logged if this template is
83
+ # used). note: there's an even more failsafe template included in the
84
+ # gem (absolute_last_resort...) below, but DON'T CHANGE IT!!!
85
+ :default_error_template => '',
86
+ # these filter out any HTTP params that are undesired
87
+ :request_env_to_skip => [ /^rack\./,
88
+ "action_controller.rescue.request",
89
+ "action_controller.rescue.response" ],
90
+ # mapping from exception classes to templates (if desired), express
91
+ # in absolute paths. use wildcards like on cmd line (glob-like), NOT
92
+ # regexp-style
93
+
94
+ # just DON'T change this! this is the error template of last resort!
95
+ :absolute_last_resort_default_error_template =>
96
+ File.join(WRANGLER_ROOT,'rails','app','views','wrangler','500.html')
97
+ }
98
+
99
+ cattr_accessor :config
100
+
101
+ # allows for overriding default configuration settings.
102
+ # in your environment.rb or environments/<env name>.rb, use a block that
103
+ # accepts one argument
104
+ # * recommend against naming it 'config' as you will probably be calling it
105
+ # within the config block in env.rb...):
106
+ # * note that some of the config values are arrays or hashes; you can
107
+ # overwrite them completely, delete or insert/merge new entries into the
108
+ # default values as you see fit...but in most cases, recommend AGAINST
109
+ # overwriting the arrays/hashes completely unless you don't want to
110
+ # take advantage of lots of out-of-the-box config
111
+ #
112
+ # Wrangler::ExceptionHandler.configure do |handler_config|
113
+ # handler_config[:key1] = value1
114
+ # handler_config[:key2] = value2
115
+ # handler_config[:key_for_a_hash].merge! :subkey => value
116
+ # handler_config[:key_for_an_array] << another_value
117
+ # end
118
+ #
119
+ # OR
120
+ #
121
+ # Wrangler::ExceptionHandler.configure do |handler_config|
122
+ # handler_config.merge! :key1 => value1,
123
+ # :key2 => value2,
124
+ # handler_config[:key_for_a_hash].merge! :subkey => value
125
+ # handler_config[:key_for_an_array] << another_value
126
+ # end
127
+ #
128
+ # NOTE: sure, you can change this configuration on the fly in your app, but
129
+ # we don't recommend it. plus, if you do and you're using delayed_job, there
130
+ # may end up being configuration differences between the rails process and
131
+ # the delayed_job process, resulting in unexpected behavior. so recommend
132
+ # you just modify this in the environment config files...or if you're doing
133
+ # something sneaky, you're on your own.
134
+ #-----------------------------------------------------------------------------
135
+ def self.configure(&block)
136
+ yield @@config
137
+ end
138
+
139
+
140
+ # translate the exception class to an http status code, using default
141
+ # code (set in config) if the exception class isn't excplicitly mapped
142
+ # to a status code in config
143
+ #---------------------------------------------------------------------------
144
+ def self.status_code_for_exception(exception)
145
+ if exception.respond_to?(:status_code)
146
+ return exception.status_code
147
+ else
148
+ return config[:error_class_status_codes][exception.class] ||
149
+ config[:error_class_status_codes][:default]
150
+ end
151
+ end
152
+
153
+ end # end ExceptionHandler class
154
+
155
+ ##############################################################################
156
+ # actual exception handling code
157
+ ##############################################################################
158
+
159
+ # make all of these instance methods also module functions
160
+ module_function
161
+
162
+ # execute the code block passed as an argument, and follow notification
163
+ # rules if an exception bubbles out of the block.
164
+ #
165
+ # return value:
166
+ # * if an exception bubbles out of the block, the exception is re-raised to
167
+ # calling code.
168
+ # * otherwise, returns nil
169
+ #-----------------------------------------------------------------------------
170
+ def notify_on_error(proc_name = nil, &block)
171
+ begin
172
+ yield
173
+ rescue => exception
174
+ options = {}
175
+ options.merge! :proc_name => proc_name unless proc_name.nil?
176
+ handle_exception(exception, options)
177
+ end
178
+
179
+ return nil
180
+ end
181
+
182
+ # the main exception-handling method. decides whether to notify or not,
183
+ # whether to render an error page or not, and to make it happen.
184
+ #
185
+ # arguments:
186
+ # - exception: the exception that was caught
187
+ #
188
+ # options:
189
+ # :request: the request object (if any) that resulted in the exception
190
+ # :render_errors: boolean indicating if an error page should be rendered
191
+ # or not (Rails only)
192
+ # :proc_name: a string representation of the process/app that was running
193
+ # when the exception was raised. default value is
194
+ # Wrangler::ExceptionHandler.config[:app_name].
195
+ #-----------------------------------------------------------------------------
196
+ def handle_exception(exception, options = {})
197
+ request = options[:request]
198
+ render_errors = options[:render_errors] || false
199
+ proc_name = options[:proc_name] || config[:app_name]
200
+
201
+ status_code = Wrangler::ExceptionHandler.status_code_for_exception(exception)
202
+ request_data = request_data_from_request(request) unless request.nil?
203
+
204
+ if notify_on_exception?(exception, status_code)
205
+ if notify_with_delayed_job?
206
+ # don't pass in request as it contains not-easily-serializable stuff
207
+ Wrangler::ExceptionNotifier.send_later(:deliver_exception_notification,
208
+ exception,
209
+ proc_name,
210
+ exception.backtrace,
211
+ status_code,
212
+ request_data)
213
+ else
214
+ Wrangler::ExceptionNotifier.deliver_exception_notification(exception,
215
+ proc_name,
216
+ exception.backtrace,
217
+ status_code,
218
+ request_data,
219
+ request)
220
+ end
221
+ end
222
+
223
+ log_exception(exception, request_data, status_code)
224
+
225
+ if render_errors
226
+ render_error_template(exception, status_code)
227
+ end
228
+ end
229
+
230
+
231
+ # determine if the app is configured to notify for the given exception or
232
+ # status code
233
+ #-----------------------------------------------------------------------------
234
+ def notify_on_exception?(exception, status_code = nil)
235
+ # first determine if we're configured to notify given the context of the
236
+ # exception
237
+ if self.respond_to?(:local_request?)
238
+ if (local_request? && config[:notify_on_local_error]) ||
239
+ (!local_request? && config[:notify_on_public_error])
240
+ notify = true
241
+ else
242
+ notify = false
243
+ end
244
+ else
245
+ notify = config[:notify_on_background_error]
246
+ end
247
+
248
+ # now if config says notify in this case, check if we're configured to
249
+ # notify for this exception or this status code
250
+ return notify &&
251
+ (config[:notify_exception_classes].include?(exception.class) ||
252
+ config[:notify_status_codes].include?(status_code))
253
+ end
254
+
255
+ # determine if email should be sent with delayed job or not (delayed job
256
+ # must be installed and config set to use delayed job
257
+ #-----------------------------------------------------------------------------
258
+ def notify_with_delayed_job?
259
+ use_dj = false
260
+
261
+ if self.is_a?(ActionController::Base)
262
+ if config[:delayed_job_for_controller_errors] &&
263
+ ExceptionNotifier.respond_to?(:send_later)
264
+ use_dj = true
265
+ else
266
+ use_dj = false
267
+ end
268
+ else
269
+ if config[:delayed_job_for_non_controller_errors] &&
270
+ ExceptionNotifier.respond_to?(:send_later)
271
+ use_dj = true
272
+ else
273
+ use_dj = false
274
+ end
275
+ end
276
+
277
+ return use_dj
278
+ end
279
+
280
+ end