celluloid_pubsub 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.reek +11 -0
- data/.rubocop.yml +4 -0
- data/Rakefile +3 -23
- data/celluloid_pubsub.gemspec +1 -5
- data/examples/redis_test.rb +1 -1
- data/examples/shared_classes.rb +14 -33
- data/examples/simple_test.rb +1 -1
- data/lib/celluloid_pubsub/client.rb +59 -49
- data/lib/celluloid_pubsub/helper.rb +64 -0
- data/lib/celluloid_pubsub/reactor.rb +60 -49
- data/lib/celluloid_pubsub/redis/redis.rb +38 -0
- data/lib/celluloid_pubsub/redis/redis_reactor.rb +97 -0
- data/lib/celluloid_pubsub/version.rb +1 -1
- data/lib/celluloid_pubsub/web_server.rb +78 -57
- data/spec/lib/celluloid_pubsub/client_pubsub_spec.rb +6 -6
- data/spec/lib/celluloid_pubsub/reactor_spec.rb +2 -0
- data/spec/spec_helper.rb +0 -8
- metadata +10 -88
- data/lib/celluloid_pubsub/redis.rb +0 -25
- data/lib/celluloid_pubsub/redis_reactor.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6aad68d75f135124f475c703d0950b1bc0b45e2c
|
4
|
+
data.tar.gz: 5e47b790b66d01dc32af3313ec54bb7ba09c8208
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c33c34fab870f68825fd75b42917b556880172b7dbb64108f64a94ed1fcd067980e984715be8118d936fbdb999b105d03c8aa85c1bd26efe2093caa9f56f73bc
|
7
|
+
data.tar.gz: 885da36eee95bce09adb0c7e6644d8a53691c87b622355de60b9d94b98757ee14a87360ca48812ed757836e29762cf095c4bdec6585602cae58951e0a41badde
|
data/.reek
ADDED
data/.rubocop.yml
CHANGED
data/Rakefile
CHANGED
@@ -10,13 +10,6 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
|
|
10
10
|
spec.rspec_opts = ['--backtrace', '--fail-fast'] if ENV['DEBUG']
|
11
11
|
end
|
12
12
|
|
13
|
-
# desc "Prepare dummy application"
|
14
|
-
# task :prepare do
|
15
|
-
# ENV["RAILS_ENV"] ||= 'test'
|
16
|
-
# require File.expand_path("./spec/dummy/config/environment", File.dirname(__FILE__))
|
17
|
-
# Dummy::Application.load_tasks
|
18
|
-
# Rake::Task["db:test:prepare"].invoke
|
19
|
-
# end
|
20
13
|
YARD::Config.options[:load_plugins] = true
|
21
14
|
YARD::Config.load_plugins
|
22
15
|
|
@@ -26,31 +19,18 @@ YARD::Rake::YardocTask.new do |t|
|
|
26
19
|
t.stats_options = ['--list-undoc'] # optional
|
27
20
|
end
|
28
21
|
|
29
|
-
unless ENV['TRAVIS']
|
30
|
-
require 'rvm-tester'
|
31
|
-
RVM::Tester::TesterTask.new(:suite) do |t|
|
32
|
-
t.rubies = %w(1.9.3 2.0.0 2.1.0) # which versions to test (required!)
|
33
|
-
t.bundle_install = true # updates Gemfile.lock, default is true
|
34
|
-
t.use_travis = true # looks for Rubies in .travis.yml (on by default)
|
35
|
-
t.command = 'bundle exec rake' # runs plain "rake" by default
|
36
|
-
t.env = { 'VERBOSE' => '1', 'RAILS_ENV' => 'test', 'RACK_ENV' => 'test' } # set any ENV vars
|
37
|
-
t.num_workers = 5 # defaults to 3
|
38
|
-
t.verbose = true # shows more output, off by default
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
22
|
desc 'Default: run the unit tests.'
|
43
23
|
task default: [:all]
|
44
24
|
|
45
25
|
desc 'Test the plugin under all supported Rails versions.'
|
46
26
|
task :all do |_t|
|
47
27
|
if ENV['TRAVIS']
|
48
|
-
exec(' bundle exec
|
28
|
+
exec(' bundle exec rubocop . && bundle exec rake spec && bundle exec rake coveralls:push')
|
49
29
|
else
|
50
|
-
exec('bundle exec rubocop -a .
|
30
|
+
exec('bundle exec rubocop -a . && bundle exec reek . && bundle exec rake spec')
|
51
31
|
end
|
52
32
|
end
|
53
33
|
|
54
34
|
task :docs do
|
55
|
-
exec(' bundle exec rubocop -a . && bundle exec
|
35
|
+
exec(' bundle exec rubocop -a . && bundle exec reek . && bundle exec inch --pedantic && bundle exec yard')
|
56
36
|
end
|
data/celluloid_pubsub.gemspec
CHANGED
@@ -26,20 +26,16 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.3'
|
27
27
|
|
28
28
|
s.add_development_dependency 'rspec-rails', '~> 3.3', '>= 3.3'
|
29
|
-
s.add_development_dependency 'guard', '~> 2.13', '>= 2.13'
|
30
|
-
s.add_development_dependency 'guard-rspec', '~> 4.6', '>= 4.6'
|
31
29
|
s.add_development_dependency 'simplecov', '~> 0.10', '>= 0.10'
|
32
30
|
s.add_development_dependency 'simplecov-summary', '~> 0.0.4', '>= 0.0.4'
|
33
31
|
s.add_development_dependency 'mocha', '~> 1.1', '>= 1.1'
|
34
32
|
s.add_development_dependency 'coveralls', '~> 0.7', '>= 0.7'
|
35
|
-
s.add_development_dependency 'rvm-tester', '~> 1.1', '>= 1.1'
|
36
33
|
|
37
34
|
s.add_development_dependency 'rubocop', '~> 0.33', '>= 0.33'
|
38
|
-
s.add_development_dependency '
|
35
|
+
s.add_development_dependency 'reek', '~> 3.8', '>= 3.8.1'
|
39
36
|
s.add_development_dependency 'yard', '~> 0.8', '>= 0.8.7'
|
40
37
|
s.add_development_dependency 'yard-rspec', '~> 0.1', '>= 0.1'
|
41
38
|
s.add_development_dependency 'redcarpet', '~> 3.3', '>= 3.3'
|
42
39
|
s.add_development_dependency 'github-markup', '~> 1.3', '>= 1.3.3'
|
43
40
|
s.add_development_dependency 'inch', '~> 0.6', '>= 0.6'
|
44
|
-
s.add_development_dependency 'guard-inch', '~> 0.1', '>= 0.1.0'
|
45
41
|
end
|
data/examples/redis_test.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
1
|
+
ENV['USE_REDIS'] = 'true'
|
2
2
|
require_relative './shared_classes'
|
data/examples/shared_classes.rb
CHANGED
@@ -3,27 +3,14 @@ require 'celluloid_pubsub'
|
|
3
3
|
require 'logger'
|
4
4
|
|
5
5
|
debug_enabled = ENV['DEBUG'].present? && ENV['DEBUG'].to_s == 'true'
|
6
|
+
use_redis = ENV['USE_REDIS'].present? && ENV['USE_REDIS'].to_s == 'true'
|
7
|
+
log_file_path = File.join(File.expand_path(File.dirname(__FILE__)), 'log', 'celluloid_pubsub.log')
|
6
8
|
|
7
|
-
if debug_enabled == true
|
8
|
-
log_file_path = File.join(File.expand_path(File.dirname(__FILE__)), 'log', 'celluloid_pubsub.log')
|
9
|
-
puts log_file_path
|
10
|
-
FileUtils.mkdir_p(File.dirname(log_file_path))
|
11
|
-
log_file = File.open(log_file_path, 'w')
|
12
|
-
log_file.sync = true
|
13
|
-
Celluloid.logger = ::Logger.new(log_file_path)
|
14
|
-
Celluloid.task_class = Celluloid::TaskThread
|
15
|
-
Celluloid.exception_handler do |ex|
|
16
|
-
unless ex.is_a?(Interrupt)
|
17
|
-
puts ex
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
9
|
|
22
10
|
# actor that subscribes to a channel
|
23
11
|
class Subscriber
|
24
12
|
include Celluloid
|
25
13
|
include Celluloid::Logger
|
26
|
-
finalizer :shutdown
|
27
14
|
|
28
15
|
def initialize(options = {})
|
29
16
|
@client = CelluloidPubsub::Client.new({ actor: Actor.current, channel: 'test_channel' }.merge(options))
|
@@ -35,7 +22,6 @@ class Subscriber
|
|
35
22
|
@client.publish('test_channel2', 'data' => ' subscriber got successfull subscription') # the message needs to be a Hash
|
36
23
|
else
|
37
24
|
puts "subscriber got message #{message.inspect}"
|
38
|
-
@client.unsubscribe('test_channel')
|
39
25
|
end
|
40
26
|
end
|
41
27
|
|
@@ -43,48 +29,43 @@ class Subscriber
|
|
43
29
|
puts "websocket connection closed: #{code.inspect}, #{reason.inspect}"
|
44
30
|
terminate
|
45
31
|
end
|
46
|
-
|
47
|
-
|
48
|
-
terminate
|
49
|
-
end
|
32
|
+
|
33
|
+
|
50
34
|
end
|
51
35
|
|
52
36
|
# actor that publishes a message in a channel
|
53
37
|
class Publisher
|
54
38
|
include Celluloid
|
55
39
|
include Celluloid::Logger
|
56
|
-
finalizer :shutdown
|
57
40
|
|
58
41
|
def initialize(options = {})
|
59
42
|
@client = CelluloidPubsub::Client.new({ actor: Actor.current, channel: 'test_channel2' }.merge(options))
|
60
43
|
end
|
61
44
|
|
62
45
|
def on_message(message)
|
63
|
-
|
64
|
-
|
65
|
-
|
46
|
+
if @client.succesfull_subscription?(message)
|
47
|
+
puts "publisher got successful subscription #{message.inspect}"
|
48
|
+
@client.publish('test_channel', 'data' => ' my_message') # the message needs to be a Hash
|
49
|
+
else
|
50
|
+
puts "publisher got message #{message.inspect}"
|
51
|
+
end
|
66
52
|
end
|
67
53
|
|
68
54
|
def on_close(code, reason)
|
69
55
|
puts "websocket connection closed: #{code.inspect}, #{reason.inspect}"
|
70
56
|
terminate
|
71
57
|
end
|
72
|
-
|
73
|
-
debug "#{self.class} tries to 'shudown'"
|
74
|
-
terminate
|
75
|
-
end
|
58
|
+
|
76
59
|
end
|
77
60
|
|
78
|
-
|
61
|
+
|
62
|
+
CelluloidPubsub::WebServer.supervise_as(:web_server, enable_debug: debug_enabled, use_redis: use_redis,log_file_path: log_file_path )
|
79
63
|
Subscriber.supervise_as(:subscriber, enable_debug: debug_enabled)
|
80
64
|
Publisher.supervise_as(:publisher, enable_debug: debug_enabled)
|
81
65
|
signal_received = false
|
82
66
|
|
83
|
-
at_exit do
|
84
|
-
Celluloid.shutdown
|
85
|
-
end
|
86
67
|
Signal.trap('INT') do
|
87
|
-
puts "\nAn interrupt signal
|
68
|
+
puts "\nAn interrupt signal has been triggered!"
|
88
69
|
signal_received = true
|
89
70
|
end
|
90
71
|
|
data/examples/simple_test.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
1
|
+
ENV['USE_REDIS'] = 'false'
|
2
2
|
require_relative './shared_classes'
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative './helper'
|
1
2
|
module CelluloidPubsub
|
2
3
|
# worker that subscribes to a channel or publishes to a channel
|
3
4
|
# if it used to subscribe to a channel the worker will dispatch the messages to the actor that made the
|
@@ -9,8 +10,8 @@ module CelluloidPubsub
|
|
9
10
|
# @!attribute connect_blk
|
10
11
|
# @return [Proc] Block that will execute after the connection is opened
|
11
12
|
#
|
12
|
-
# @!attribute
|
13
|
-
# @return [Celluloid::WebSocket::Client] A websocket
|
13
|
+
# @!attribute connection
|
14
|
+
# @return [Celluloid::WebSocket::Client] A websocket connection that is used to chat witht the webserver
|
14
15
|
#
|
15
16
|
# @!attribute options
|
16
17
|
# @return [Hash] the options that can be used to connect to webser and send additional data
|
@@ -26,7 +27,9 @@ module CelluloidPubsub
|
|
26
27
|
class Client
|
27
28
|
include Celluloid
|
28
29
|
include Celluloid::Logger
|
29
|
-
|
30
|
+
include CelluloidPubsub::Helper
|
31
|
+
|
32
|
+
attr_reader :actor, :connection, :options, :hostname, :port, :path, :channel
|
30
33
|
finalizer :shutdown
|
31
34
|
# receives a list of options that are used to connect to the webserver and an actor to which the callbacks are delegated to
|
32
35
|
# when receiving messages from a channel
|
@@ -43,44 +46,61 @@ module CelluloidPubsub
|
|
43
46
|
#
|
44
47
|
# @api public
|
45
48
|
def initialize(options)
|
46
|
-
|
49
|
+
@options = options.stringify_keys!
|
50
|
+
@actor ||= @options.fetch('actor', nil)
|
51
|
+
@channel ||= @options.fetch('channel', nil)
|
47
52
|
raise "#{self}: Please provide an actor in the options list!!!" if @actor.blank?
|
48
53
|
raise "#{self}: Please provide an channel in the options list!!!" if @channel.blank?
|
49
|
-
|
50
|
-
|
51
|
-
Actor.current.link @client
|
54
|
+
supervise_actors
|
55
|
+
setup_celluloid_logger
|
52
56
|
end
|
53
57
|
|
54
|
-
|
55
|
-
|
58
|
+
def log_file_path
|
59
|
+
@log_file_path ||= @options.fetch('log_file_path', nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
# the method will link the current actor to the actor that is attached to, and the connection to the current actor
|
56
63
|
#
|
57
64
|
# @return [void]
|
58
65
|
#
|
59
66
|
# @api public
|
60
|
-
def
|
61
|
-
|
62
|
-
|
67
|
+
def supervise_actors
|
68
|
+
current_actor = Actor.current
|
69
|
+
@actor.link current_actor if @actor.respond_to?(:link)
|
70
|
+
current_actor.link connection
|
63
71
|
end
|
64
72
|
|
65
|
-
#
|
73
|
+
# the method will return the client that is used to
|
74
|
+
#
|
75
|
+
#
|
76
|
+
# @return [Celluloid::WebSocket::Client] the websocket connection used to connect to server
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def connection
|
80
|
+
@connection ||= Celluloid::WebSocket::Client.new("ws://#{hostname}:#{port}#{path}", Actor.current)
|
81
|
+
end
|
82
|
+
|
83
|
+
def hostname
|
84
|
+
@hostname ||= @options.fetch('hostname', CelluloidPubsub::WebServer::HOST)
|
85
|
+
end
|
86
|
+
|
87
|
+
def port
|
88
|
+
@port ||= @options.fetch('port', CelluloidPubsub::WebServer::PORT)
|
89
|
+
end
|
90
|
+
|
91
|
+
def path
|
92
|
+
@path ||= @options.fetch('path', CelluloidPubsub::WebServer::PATH)
|
93
|
+
end
|
94
|
+
|
95
|
+
# the method will terminate the current actor
|
66
96
|
#
|
67
|
-
# @param [Hash] options the options that can be used to connect to webser and send additional data
|
68
|
-
# @option options [String] :actor The actor that made the connection
|
69
|
-
# @option options [String]:hostname The hostname on which the webserver runs on
|
70
|
-
# @option options [String] :port The port on which the webserver runs on
|
71
|
-
# @option options [String] :path The request path that the webserver accepts
|
72
97
|
#
|
73
98
|
# @return [void]
|
74
99
|
#
|
75
100
|
# @api public
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
@actor = @options.fetch('actor', nil)
|
80
|
-
@channel = @options.fetch('channel', nil)
|
81
|
-
@hostname = @options.fetch('hostname', CelluloidPubsub::WebServer::HOST)
|
82
|
-
@port = @options.fetch('port', CelluloidPubsub::WebServer::PORT)
|
83
|
-
@path = @options.fetch('path', CelluloidPubsub::WebServer::PATH)
|
101
|
+
def shutdown
|
102
|
+
log_debug "#{self.class} tries to 'shudown'"
|
103
|
+
terminate
|
84
104
|
end
|
85
105
|
|
86
106
|
# checks if debug is enabled
|
@@ -101,21 +121,10 @@ module CelluloidPubsub
|
|
101
121
|
#
|
102
122
|
# @api public
|
103
123
|
def subscribe(channel)
|
104
|
-
|
124
|
+
log_debug("#{@actor.class} tries to subscribe to channel #{channel}")
|
105
125
|
async.send_action('subscribe', channel)
|
106
126
|
end
|
107
127
|
|
108
|
-
# checks if the message has the successfull subscription action
|
109
|
-
#
|
110
|
-
# @param [string] message
|
111
|
-
#
|
112
|
-
# @return [void]
|
113
|
-
#
|
114
|
-
# @api public
|
115
|
-
def succesfull_subscription?(message)
|
116
|
-
message.present? && message['client_action'].present? && message['client_action'] == 'successful_subscription'
|
117
|
-
end
|
118
|
-
|
119
128
|
# publishes to a channel some data (can be anything)
|
120
129
|
#
|
121
130
|
# @param [string] channel
|
@@ -165,10 +174,14 @@ module CelluloidPubsub
|
|
165
174
|
#
|
166
175
|
# @api public
|
167
176
|
def on_open
|
168
|
-
|
177
|
+
log_debug("#{@actor.class} websocket connection opened")
|
169
178
|
async.subscribe(@channel)
|
170
179
|
end
|
171
180
|
|
181
|
+
def log_debug(message)
|
182
|
+
debug message if debug_enabled?
|
183
|
+
end
|
184
|
+
|
172
185
|
# callback executes when actor receives a message from a subscribed channel
|
173
186
|
# and parses the message using JSON.parse and dispatches the parsed
|
174
187
|
# message to the original actor that made the connection
|
@@ -179,9 +192,8 @@ module CelluloidPubsub
|
|
179
192
|
#
|
180
193
|
# @api public
|
181
194
|
def on_message(data)
|
182
|
-
debug("#{@actor.class} received plain #{data}") if debug_enabled?
|
183
195
|
message = JSON.parse(data)
|
184
|
-
|
196
|
+
log_debug("#{@actor.class} received JSON #{message}")
|
185
197
|
@actor.async.on_message(message)
|
186
198
|
end
|
187
199
|
|
@@ -195,9 +207,9 @@ module CelluloidPubsub
|
|
195
207
|
#
|
196
208
|
# @api public
|
197
209
|
def on_close(code, reason)
|
198
|
-
|
210
|
+
connection.terminate
|
199
211
|
terminate
|
200
|
-
|
212
|
+
log_debug("#{@actor.class} dispatching on close #{code} #{reason}")
|
201
213
|
@actor.async.on_close(code, reason)
|
202
214
|
end
|
203
215
|
|
@@ -213,9 +225,8 @@ module CelluloidPubsub
|
|
213
225
|
#
|
214
226
|
# @api private
|
215
227
|
def send_action(action, channel = nil, data = {})
|
216
|
-
|
217
|
-
publishing_data =
|
218
|
-
publishing_data = publishing_data.merge('data' => data) if data.present?
|
228
|
+
data = data.is_a?(Hash) ? data : {}
|
229
|
+
publishing_data = { 'client_action' => action, 'channel' => channel, 'data' => data }.reject { |_key, value| value.blank? }
|
219
230
|
async.chat(publishing_data)
|
220
231
|
end
|
221
232
|
|
@@ -232,12 +243,11 @@ module CelluloidPubsub
|
|
232
243
|
final_message = nil
|
233
244
|
if message.is_a?(Hash)
|
234
245
|
final_message = message.to_json
|
235
|
-
debug("#{@actor.class} sends #{message.to_json}") if debug_enabled?
|
236
246
|
else
|
237
247
|
final_message = JSON.dump(action: 'message', message: message)
|
238
|
-
debug("#{@actor.class} sends JSON #{final_message}") if debug_enabled?
|
239
248
|
end
|
240
|
-
@
|
249
|
+
log_debug("#{@actor.class} sends JSON #{final_message}")
|
250
|
+
connection.text final_message
|
241
251
|
end
|
242
252
|
end
|
243
253
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module CelluloidPubsub
|
2
|
+
# class that holds the options that are configurable for this gem
|
3
|
+
module Helper
|
4
|
+
# checks if the message has the successfull subscription action
|
5
|
+
#
|
6
|
+
# @param [string] message
|
7
|
+
#
|
8
|
+
# @return [void]
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
def succesfull_subscription?(message)
|
12
|
+
message.is_a?(Hash) && message['client_action'] == 'successful_subscription'
|
13
|
+
end
|
14
|
+
|
15
|
+
module_function
|
16
|
+
|
17
|
+
def setup_celluloid_logger
|
18
|
+
return if !debug_enabled? || (respond_to?(:log_file_path) && log_file_path.blank?)
|
19
|
+
setup_log_file
|
20
|
+
Celluloid.logger = ::Logger.new(log_file_path.present? ? log_file_path : STDOUT)
|
21
|
+
setup_celluloid_exception_handler
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup_celluloid_exception_handler
|
25
|
+
Celluloid.task_class = Celluloid::TaskThread
|
26
|
+
Celluloid.exception_handler do |ex|
|
27
|
+
puts ex unless filtered_error?(ex)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup_log_file
|
32
|
+
return if !debug_enabled? || (respond_to?(:log_file_path) && log_file_path.blank?)
|
33
|
+
FileUtils.mkdir_p(File.dirname(log_file_path)) unless File.directory?(log_file_path)
|
34
|
+
log_file = File.open(log_file_path, 'w')
|
35
|
+
log_file.sync = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def filtered_error?(error)
|
39
|
+
[Interrupt].any? { |class_name| error.is_a?(class_name) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# receives a list of options that are used to configure the webserver
|
43
|
+
#
|
44
|
+
# @param [Hash] options the options that can be used to connect to webser and send additional data
|
45
|
+
# @option options [String]:hostname The hostname on which the webserver runs on
|
46
|
+
# @option options [Integer] :port The port on which the webserver runs on
|
47
|
+
# @option options [String] :path The request path that the webserver accepts
|
48
|
+
# @option options [Boolean] :spy Enable this only if you want to enable debugging for the webserver
|
49
|
+
# @option options [Integer]:backlog How many connections the server accepts
|
50
|
+
#
|
51
|
+
# @return [void]
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
def parse_options(options)
|
55
|
+
options = options.is_a?(Array) ? options.first : options
|
56
|
+
options = options.is_a?(Hash) ? options.stringify_keys : {}
|
57
|
+
options
|
58
|
+
end
|
59
|
+
|
60
|
+
def log_debug(message)
|
61
|
+
debug message if respond_to?(:debug_enabled?) && debug_enabled?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|