deas 0.31.0 → 0.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +1 -1
- data/lib/deas/exceptions.rb +2 -0
- data/lib/deas/handler_proxy.rb +41 -0
- data/lib/deas/redirect_proxy.rb +2 -1
- data/lib/deas/route.rb +13 -23
- data/lib/deas/route_proxy.rb +12 -7
- data/lib/deas/router.rb +70 -14
- data/lib/deas/server.rb +4 -0
- data/lib/deas/version.rb +1 -1
- data/test/support/routes.rb +22 -0
- data/test/system/rack_tests.rb +16 -4
- data/test/unit/exceptions_tests.rb +8 -3
- data/test/unit/handler_proxy_tests.rb +115 -0
- data/test/unit/redirect_proxy_tests.rb +7 -7
- data/test/unit/route_proxy_tests.rb +22 -4
- data/test/unit/route_tests.rb +34 -75
- data/test/unit/router_tests.rb +215 -88
- data/test/unit/server_configuration_tests.rb +5 -7
- data/test/unit/server_tests.rb +5 -4
- data/test/unit/sinatra_app_tests.rb +2 -3
- metadata +89 -111
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2e01bef9b6af4303f73139d7163cffe10e365422
|
4
|
+
data.tar.gz: 72a8a94d809478d18b0977d6f08db810a4801547
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d911b586a2a5550d68b1213da0928c9660bded2411454543f838d5aa882652412d7bc3717c526fd9b53349c5250ce37c66c0feffb5105e8c65ea7382bb0ebf9a
|
7
|
+
data.tar.gz: 29061f4db02882144065819d5f86c720cf9859eb100972f88c070ce23c90257b65bf8f52a0cc6c973f3c94b2a6c7746656aa20613916d2be4a2049526ce321da
|
data/Gemfile
CHANGED
data/lib/deas/exceptions.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'deas/exceptions'
|
2
|
+
require 'deas/sinatra_runner'
|
3
|
+
|
4
|
+
module Deas
|
5
|
+
|
6
|
+
class HandlerProxy
|
7
|
+
|
8
|
+
attr_reader :handler_class_name, :handler_class
|
9
|
+
|
10
|
+
def initialize(handler_class_name)
|
11
|
+
@handler_class_name = handler_class_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate!
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(sinatra_call)
|
19
|
+
runner = SinatraRunner.new(self.handler_class, {
|
20
|
+
:sinatra_call => sinatra_call,
|
21
|
+
:request => sinatra_call.request,
|
22
|
+
:response => sinatra_call.response,
|
23
|
+
:session => sinatra_call.session,
|
24
|
+
:params => sinatra_call.params,
|
25
|
+
:logger => sinatra_call.settings.logger,
|
26
|
+
:router => sinatra_call.settings.router,
|
27
|
+
:template_source => sinatra_call.settings.template_source
|
28
|
+
})
|
29
|
+
|
30
|
+
sinatra_call.request.env.tap do |env|
|
31
|
+
env['deas.params'] = runner.params
|
32
|
+
env['deas.handler_class_name'] = self.handler_class.name
|
33
|
+
env['deas.logging'].call " Handler: #{env['deas.handler_class_name']}"
|
34
|
+
env['deas.logging'].call " Params: #{env['deas.params'].inspect}"
|
35
|
+
end
|
36
|
+
runner.run
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/lib/deas/redirect_proxy.rb
CHANGED
data/lib/deas/route.rb
CHANGED
@@ -1,39 +1,29 @@
|
|
1
|
-
require 'deas/
|
1
|
+
require 'deas/exceptions'
|
2
2
|
|
3
3
|
module Deas
|
4
4
|
|
5
5
|
class Route
|
6
6
|
|
7
|
-
attr_reader :method, :path, :
|
7
|
+
attr_reader :method, :path, :handler_proxies
|
8
8
|
|
9
|
-
def initialize(method, path,
|
10
|
-
@method, @path, @
|
9
|
+
def initialize(method, path, handler_proxies)
|
10
|
+
@method, @path, @handler_proxies = method, path, handler_proxies
|
11
11
|
end
|
12
12
|
|
13
13
|
def validate!
|
14
|
-
@
|
15
|
-
|
14
|
+
@handler_proxies.each do |request_type_name, proxy|
|
15
|
+
proxy.validate!
|
16
|
+
end
|
16
17
|
end
|
17
18
|
|
18
19
|
def run(sinatra_call)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
:params => sinatra_call.params,
|
25
|
-
:logger => sinatra_call.settings.logger,
|
26
|
-
:router => sinatra_call.settings.router,
|
27
|
-
:template_source => sinatra_call.settings.template_source
|
28
|
-
})
|
29
|
-
|
30
|
-
sinatra_call.request.env.tap do |env|
|
31
|
-
env['deas.params'] = runner.params
|
32
|
-
env['deas.handler_class_name'] = self.handler_class.name
|
33
|
-
env['deas.logging'].call " Handler: #{env['deas.handler_class_name']}"
|
34
|
-
env['deas.logging'].call " Params: #{env['deas.params'].inspect}"
|
20
|
+
type = sinatra_call.settings.router.request_type_name(sinatra_call.request)
|
21
|
+
proxy = begin
|
22
|
+
@handler_proxies[type]
|
23
|
+
rescue HandlerProxyNotFound
|
24
|
+
sinatra_call.halt(404)
|
35
25
|
end
|
36
|
-
|
26
|
+
proxy.run(sinatra_call)
|
37
27
|
end
|
38
28
|
|
39
29
|
end
|
data/lib/deas/route_proxy.rb
CHANGED
@@ -1,18 +1,22 @@
|
|
1
1
|
require 'deas/exceptions'
|
2
|
-
require 'deas/
|
2
|
+
require 'deas/handler_proxy'
|
3
3
|
|
4
4
|
module Deas
|
5
|
-
class RouteProxy
|
6
5
|
|
7
|
-
|
6
|
+
class RouteProxy < HandlerProxy
|
8
7
|
|
9
|
-
def initialize(handler_class_name)
|
10
|
-
|
8
|
+
def initialize(handler_class_name, view_handler_ns = nil)
|
9
|
+
raise(NoHandlerClassError.new(handler_class_name)) if handler_class_name.nil?
|
10
|
+
|
11
|
+
if view_handler_ns && !(handler_class_name =~ /^::/)
|
12
|
+
handler_class_name = "#{view_handler_ns}::#{handler_class_name}"
|
13
|
+
end
|
14
|
+
super(handler_class_name)
|
11
15
|
end
|
12
16
|
|
13
17
|
def validate!
|
14
|
-
@handler_class = constantize(
|
15
|
-
raise(NoHandlerClassError.new(
|
18
|
+
@handler_class = constantize(self.handler_class_name).tap do |handler_class|
|
19
|
+
raise(NoHandlerClassError.new(self.handler_class_name)) if !handler_class
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
@@ -27,4 +31,5 @@ module Deas
|
|
27
31
|
end
|
28
32
|
|
29
33
|
end
|
34
|
+
|
30
35
|
end
|
data/lib/deas/router.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
+
require 'deas/exceptions'
|
1
2
|
require 'deas/redirect_proxy'
|
2
3
|
require 'deas/route'
|
3
4
|
require 'deas/route_proxy'
|
4
5
|
require 'deas/url'
|
5
6
|
|
6
7
|
module Deas
|
8
|
+
|
7
9
|
class Router
|
8
10
|
|
9
|
-
|
11
|
+
DEFAULT_REQUEST_TYPE_NAME = 'default'
|
12
|
+
|
13
|
+
attr_reader :request_types, :urls, :routes
|
10
14
|
|
11
15
|
def initialize(&block)
|
16
|
+
@request_types = []
|
12
17
|
@urls, @routes = {}, []
|
18
|
+
default_request_type_name(DEFAULT_REQUEST_TYPE_NAME)
|
13
19
|
self.instance_eval(&block) if !block.nil?
|
14
20
|
end
|
15
21
|
|
@@ -41,21 +47,42 @@ module Deas
|
|
41
47
|
prepend_base_url(url.path_for(*args))
|
42
48
|
end
|
43
49
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
def default_request_type_name(value = nil)
|
51
|
+
@default_request_type = RequestType.new(value) if !value.nil?
|
52
|
+
@default_request_type.name
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_request_type(name, &proc)
|
56
|
+
@request_types << RequestType.new(name, proc)
|
57
|
+
end
|
58
|
+
|
59
|
+
# ideally someday the request should just *know* its request type
|
60
|
+
def request_type_name(request)
|
61
|
+
(self.request_types.detect{ |rt| rt.proc.call(request) } || @default_request_type).name
|
62
|
+
end
|
49
63
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
64
|
+
def get(path, *args); self.route(:get, path, *args); end
|
65
|
+
def post(path, *args); self.route(:post, path, *args); end
|
66
|
+
def put(path, *args); self.route(:put, path, *args); end
|
67
|
+
def patch(path, *args); self.route(:patch, path, *args); end
|
68
|
+
def delete(path, *args); self.route(:delete, path, *args); end
|
69
|
+
|
70
|
+
def route(http_method, from_path, *args)
|
71
|
+
handler_names = args.last.kind_of?(::Hash) ? args.pop : {}
|
72
|
+
default_handler_name = args.last
|
73
|
+
if !handler_names.key?(self.default_request_type_name) && default_handler_name
|
74
|
+
handler_names[self.default_request_type_name] = default_handler_name
|
75
|
+
end
|
76
|
+
|
77
|
+
proxies = handler_names.inject({}) do |proxies, (req_type_name, handler_name)|
|
78
|
+
proxies[req_type_name] = Deas::RouteProxy.new(handler_name, self.view_handler_ns)
|
79
|
+
proxies
|
53
80
|
end
|
54
|
-
proxy = Deas::RouteProxy.new(handler_class_name)
|
55
81
|
|
56
82
|
from_url = self.urls[from_path]
|
57
83
|
from_url_path = from_url.path if from_url
|
58
|
-
|
84
|
+
|
85
|
+
add_route(http_method, prepend_base_url(from_url_path || from_path), proxies)
|
59
86
|
end
|
60
87
|
|
61
88
|
def redirect(from_path, to_path = nil, &block)
|
@@ -63,11 +90,14 @@ module Deas
|
|
63
90
|
if to_path.kind_of?(::Symbol) && to_url.nil?
|
64
91
|
raise ArgumentError, "no url named `#{to_path.inspect}`"
|
65
92
|
end
|
93
|
+
|
66
94
|
proxy = Deas::RedirectProxy.new(to_url || to_path, &block)
|
95
|
+
proxies = { self.default_request_type_name => proxy }
|
67
96
|
|
68
97
|
from_url = self.urls[from_path]
|
69
98
|
from_url_path = from_url.path if from_url
|
70
|
-
|
99
|
+
|
100
|
+
add_route(:get, prepend_base_url(from_url_path || from_path), proxies)
|
71
101
|
end
|
72
102
|
|
73
103
|
private
|
@@ -76,10 +106,36 @@ module Deas
|
|
76
106
|
self.urls[name] = Deas::Url.new(name, path)
|
77
107
|
end
|
78
108
|
|
79
|
-
def add_route(http_method, path,
|
80
|
-
|
109
|
+
def add_route(http_method, path, proxies)
|
110
|
+
proxies = HandlerProxies.new(proxies, self.default_request_type_name)
|
111
|
+
Deas::Route.new(http_method, path, proxies).tap{ |r| self.routes.push(r) }
|
81
112
|
end
|
82
113
|
|
114
|
+
class HandlerProxies
|
115
|
+
|
116
|
+
attr_reader :default_type
|
117
|
+
|
118
|
+
def initialize(proxies, default_name)
|
119
|
+
@proxies = proxies
|
120
|
+
@default_type = default_name
|
121
|
+
end
|
122
|
+
|
123
|
+
def [](type)
|
124
|
+
@proxies[type] || @proxies[@default_type] || raise(HandlerProxyNotFound)
|
125
|
+
end
|
126
|
+
|
127
|
+
def each(&block)
|
128
|
+
@proxies.each(&block)
|
129
|
+
end
|
130
|
+
|
131
|
+
def empty?
|
132
|
+
@proxies.empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
RequestType = Struct.new(:name, :proc)
|
138
|
+
|
83
139
|
end
|
84
140
|
|
85
141
|
end
|
data/lib/deas/server.rb
CHANGED
@@ -211,6 +211,10 @@ module Deas::Server
|
|
211
211
|
def url(*args, &block); self.router.url(*args, &block); end
|
212
212
|
def url_for(*args, &block); self.router.url_for(*args, &block); end
|
213
213
|
|
214
|
+
def default_request_type_name(*args); self.router.default_request_type_name(*args); end
|
215
|
+
def add_request_type(*args, &block); self.router.add_request_type(*args, &block); end
|
216
|
+
def request_type_name(*args); self.router.request_type_name(*args); end
|
217
|
+
|
214
218
|
def get(*args, &block); self.router.get(*args, &block); end
|
215
219
|
def post(*args, &block); self.router.post(*args, &block); end
|
216
220
|
def put(*args, &block); self.router.put(*args, &block); end
|
data/lib/deas/version.rb
CHANGED
data/test/support/routes.rb
CHANGED
@@ -19,6 +19,10 @@ class DeasTestServer
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
default_request_type_name 'desktop'
|
23
|
+
add_request_type('regular'){ |r| r.path_info =~ /regular/ }
|
24
|
+
add_request_type('mobile'){ |r| r.path_info =~ /mobile/ }
|
25
|
+
|
22
26
|
get '/show', 'ShowHandler'
|
23
27
|
get '/show.html', 'ShowHtmlHandler'
|
24
28
|
get '/show.json', 'ShowJsonHandler'
|
@@ -26,6 +30,9 @@ class DeasTestServer
|
|
26
30
|
get '/show-text', 'ShowTextHandler'
|
27
31
|
get '/show-headers-text', 'ShowHeadersTextHandler'
|
28
32
|
|
33
|
+
get '/req-type-show/:type', 'regular' => 'ShowHandler',
|
34
|
+
'mobile' => 'ShowMobileHandler'
|
35
|
+
|
29
36
|
get '/halt', 'HaltHandler'
|
30
37
|
get '/error', 'ErrorHandler'
|
31
38
|
get '/redirect', 'RedirectHandler'
|
@@ -71,6 +78,21 @@ class ShowHandler
|
|
71
78
|
|
72
79
|
end
|
73
80
|
|
81
|
+
class ShowMobileHandler
|
82
|
+
include Deas::ViewHandler
|
83
|
+
|
84
|
+
attr_reader :message
|
85
|
+
|
86
|
+
def init!
|
87
|
+
@message = "[MOBILE] #{params['message']}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def run!
|
91
|
+
@message
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
74
96
|
class ShowHtmlHandler
|
75
97
|
include Deas::ViewHandler
|
76
98
|
|
data/test/system/rack_tests.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'assert'
|
2
|
-
require 'assert-rack-test'
|
3
2
|
require 'deas'
|
4
3
|
|
4
|
+
require 'assert-rack-test'
|
5
|
+
|
5
6
|
module Deas
|
6
7
|
|
7
8
|
class RackTestsContext < Assert::Context
|
@@ -20,8 +21,7 @@ module Deas
|
|
20
21
|
get '/show', 'message' => 'this is a test'
|
21
22
|
|
22
23
|
assert_equal 200, last_response.status
|
23
|
-
|
24
|
-
assert_equal exp, last_response.body
|
24
|
+
assert_equal "this is a test", last_response.body
|
25
25
|
end
|
26
26
|
|
27
27
|
should "set the content type appropriately" do
|
@@ -44,6 +44,19 @@ module Deas
|
|
44
44
|
assert_equal 'text/plain', last_response.headers['Content-Type']
|
45
45
|
end
|
46
46
|
|
47
|
+
should "render different handlers for the same meth/path based on the type" do
|
48
|
+
get '/req-type-show/regular', 'message' => 'this is a test request'
|
49
|
+
assert_equal 200, last_response.status
|
50
|
+
assert_equal "this is a test request", last_response.body
|
51
|
+
|
52
|
+
get '/req-type-show/mobile', 'message' => 'this is a test request'
|
53
|
+
assert_equal 200, last_response.status
|
54
|
+
assert_equal "[MOBILE] this is a test request", last_response.body
|
55
|
+
|
56
|
+
get '/req-type-show/other', 'message' => 'this is a test request'
|
57
|
+
assert_equal 404, last_response.status
|
58
|
+
end
|
59
|
+
|
47
60
|
should "allow halting with a custom response" do
|
48
61
|
get '/halt', 'with' => 234
|
49
62
|
|
@@ -115,7 +128,6 @@ module Deas
|
|
115
128
|
desc "handler"
|
116
129
|
setup do
|
117
130
|
get 'handler/tests?a-param=something'
|
118
|
-
|
119
131
|
@data_inspect = last_response.body
|
120
132
|
end
|
121
133
|
|
@@ -11,7 +11,7 @@ module Deas
|
|
11
11
|
assert_kind_of RuntimeError, Deas::Error.new
|
12
12
|
end
|
13
13
|
|
14
|
-
should "provide a no handler class exception
|
14
|
+
should "provide a no handler class exception" do
|
15
15
|
assert Deas::NoHandlerClassError
|
16
16
|
|
17
17
|
handler_class_name = 'AHandlerClass'
|
@@ -23,12 +23,12 @@ module Deas
|
|
23
23
|
assert_equal exp_msg, e.message
|
24
24
|
end
|
25
25
|
|
26
|
-
should "provide a server exception
|
26
|
+
should "provide a server exception" do
|
27
27
|
assert Deas::ServerError
|
28
28
|
assert_kind_of Deas::Error, Deas::ServerError.new
|
29
29
|
end
|
30
30
|
|
31
|
-
should "provide a server root exception
|
31
|
+
should "provide a server root exception" do
|
32
32
|
assert Deas::ServerRootError
|
33
33
|
|
34
34
|
e = Deas::ServerRootError.new
|
@@ -36,6 +36,11 @@ module Deas
|
|
36
36
|
assert_equal "server `root` not set but required", e.message
|
37
37
|
end
|
38
38
|
|
39
|
+
should "provide a handler proxy not found exception" do
|
40
|
+
assert Deas::HandlerProxyNotFound
|
41
|
+
assert_kind_of Deas::Error, Deas::HandlerProxyNotFound.new
|
42
|
+
end
|
43
|
+
|
39
44
|
end
|
40
45
|
|
41
46
|
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'deas/handler_proxy'
|
3
|
+
|
4
|
+
require 'deas/exceptions'
|
5
|
+
require 'deas/sinatra_runner'
|
6
|
+
require 'test/support/fake_sinatra_call'
|
7
|
+
require 'test/support/view_handlers'
|
8
|
+
|
9
|
+
class Deas::HandlerProxy
|
10
|
+
|
11
|
+
class UnitTests < Assert::Context
|
12
|
+
desc "Deas::HandlerProxy"
|
13
|
+
setup do
|
14
|
+
@proxy = Deas::HandlerProxy.new('EmptyViewHandler')
|
15
|
+
end
|
16
|
+
subject{ @proxy }
|
17
|
+
|
18
|
+
should have_readers :handler_class_name, :handler_class
|
19
|
+
should have_imeths :validate!, :run
|
20
|
+
|
21
|
+
should "know its handler class name" do
|
22
|
+
assert_equal 'EmptyViewHandler', subject.handler_class_name
|
23
|
+
end
|
24
|
+
|
25
|
+
should "not implement its validate! method" do
|
26
|
+
assert_raises(NotImplementedError){ subject.validate! }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class RunTests < UnitTests
|
32
|
+
desc "when run"
|
33
|
+
setup do
|
34
|
+
@runner_spy = SinatraRunnerSpy.new
|
35
|
+
Assert.stub(Deas::SinatraRunner, :new) do |*args|
|
36
|
+
@runner_spy.build(*args)
|
37
|
+
@runner_spy
|
38
|
+
end
|
39
|
+
|
40
|
+
Assert.stub(@proxy, :handler_class){ EmptyViewHandler }
|
41
|
+
|
42
|
+
@fake_sinatra_call = FakeSinatraCall.new
|
43
|
+
@proxy.run(@fake_sinatra_call)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "build and run a sinatra runner" do
|
47
|
+
assert_equal subject.handler_class, @runner_spy.handler_class
|
48
|
+
|
49
|
+
exp_args = {
|
50
|
+
:sinatra_call => @fake_sinatra_call,
|
51
|
+
:request => @fake_sinatra_call.request,
|
52
|
+
:response => @fake_sinatra_call.response,
|
53
|
+
:session => @fake_sinatra_call.session,
|
54
|
+
:params => @fake_sinatra_call.params,
|
55
|
+
:logger => @fake_sinatra_call.settings.logger,
|
56
|
+
:router => @fake_sinatra_call.settings.router,
|
57
|
+
:template_source => @fake_sinatra_call.settings.template_source
|
58
|
+
}
|
59
|
+
assert_equal exp_args, @runner_spy.args
|
60
|
+
|
61
|
+
assert_true @runner_spy.run_called
|
62
|
+
end
|
63
|
+
|
64
|
+
should "add the runner params to the request env" do
|
65
|
+
exp = @runner_spy.params
|
66
|
+
assert_equal exp, @fake_sinatra_call.request.env['deas.params']
|
67
|
+
end
|
68
|
+
|
69
|
+
should "add the handler class name to the request env" do
|
70
|
+
exp = subject.handler_class.name
|
71
|
+
assert_equal exp, @fake_sinatra_call.request.env['deas.handler_class_name']
|
72
|
+
end
|
73
|
+
|
74
|
+
should "log the handler and params" do
|
75
|
+
exp_msgs = [
|
76
|
+
" Handler: #{subject.handler_class}",
|
77
|
+
" Params: #{@runner_spy.params.inspect}"
|
78
|
+
]
|
79
|
+
assert_equal exp_msgs, @fake_sinatra_call.request.logging_msgs
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
class SinatraRunnerSpy
|
85
|
+
|
86
|
+
attr_reader :run_called
|
87
|
+
attr_reader :handler_class, :args
|
88
|
+
attr_reader :sinatra_call
|
89
|
+
attr_reader :request, :response, :session, :params
|
90
|
+
attr_reader :logger, :router, :template_source
|
91
|
+
|
92
|
+
def initialize
|
93
|
+
@run_called = false
|
94
|
+
end
|
95
|
+
|
96
|
+
def build(handler_class, args)
|
97
|
+
@handler_class, @args = handler_class, args
|
98
|
+
|
99
|
+
@sinatra_call = args[:sinatra_call]
|
100
|
+
@request = args[:request]
|
101
|
+
@response = args[:response]
|
102
|
+
@session = args[:session]
|
103
|
+
@params = args[:params]
|
104
|
+
@logger = args[:logger]
|
105
|
+
@router = args[:router]
|
106
|
+
@template_source = args[:template_source]
|
107
|
+
end
|
108
|
+
|
109
|
+
def run
|
110
|
+
@run_called = true
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|