harmony-service 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/README.md +7 -27
- data/bin/harmony_service +35 -0
- data/harmony-service.gemspec +3 -3
- data/lib/harmony/service/calculator/request.rb +1 -1
- data/lib/harmony/service/flow/ended_request.rb +3 -0
- data/lib/harmony/service/form/get_request.rb +3 -0
- data/lib/harmony/service/form/get_response.rb +3 -0
- data/lib/harmony/service/rpc_service.rb +14 -22
- data/lib/harmony/service/version.rb +9 -1
- data/lib/harmony/service.rb +5 -0
- data/spec/harmony/rpc_service_spec.rb +93 -0
- data/spec/spec_helper.rb +15 -0
- metadata +14 -7
- data/bin/console +0 -14
- data/bin/setup +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b38543c27255399b4d62dc894bd21a6b585d73e
|
|
4
|
+
data.tar.gz: b887efe0fd12aebe15f877964bd2d82de5460084
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cbf739554afce68602f12c1bc07e1e2d9c9848ad7f1d19f286d2a645cf6a5486246c4df8bb4b36215991e4215a8e25eadbbfd1c4de2b32ecbf8200e79b4c25cf
|
|
7
|
+
data.tar.gz: 3eafe55ff0d0ffdf6798a6bcda38684338388fe9ef9ceb8374909674aa345028fec3c63097b68c6aa44a0ce54bb115b5102a30cad121d410efded47dfdc2bc1a
|
data/README.md
CHANGED
|
@@ -1,41 +1,21 @@
|
|
|
1
1
|
# Harmony::Service
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
|
3
|
+
The Harmony Service gem simplifies creation of a Service in the Harmony Platform.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
gem 'harmony-service'
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
And then execute:
|
|
16
|
-
|
|
17
|
-
$ bundle
|
|
18
|
-
|
|
19
|
-
Or install it yourself as:
|
|
20
|
-
|
|
21
|
-
$ gem install harmony-service
|
|
7
|
+
1. Create a new Service via the Harmony platform
|
|
8
|
+
1. Download the code for your Service.
|
|
9
|
+
1. Run `bundle install`
|
|
22
10
|
|
|
23
11
|
## Usage
|
|
24
12
|
|
|
25
|
-
|
|
13
|
+
In order to test your code locally you can run `bundle exec rspec`. Once you have completed development you can upload into the Harmony Platform.
|
|
26
14
|
|
|
27
15
|
## Development
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
|
-
|
|
33
|
-
## Contributing
|
|
34
|
-
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/harmony-service.
|
|
36
|
-
|
|
17
|
+
Your Service should respond to request messages appropriately. You can find the relevant mappings [here](https://github.com/HarmonyMobile/harmony-service/blob/master/lib/harmony/service/rpc_service.rb#L95).
|
|
37
18
|
|
|
38
19
|
## License
|
|
39
20
|
|
|
40
|
-
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
41
|
-
|
|
21
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/bin/harmony_service
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'sneakers'
|
|
3
|
+
require 'sneakers/runner'
|
|
4
|
+
require 'harmony/service'
|
|
5
|
+
|
|
6
|
+
abort("usage: harmony_service [handler class name] --require [handler class file]") unless ARGV.length == 3
|
|
7
|
+
|
|
8
|
+
handler_class = ARGV[0]
|
|
9
|
+
puts "Starting Harmony Service with handler: #{handler_class}"
|
|
10
|
+
load File.expand_path(ARGV[2])
|
|
11
|
+
|
|
12
|
+
opts = {
|
|
13
|
+
amqp: ENV['ampq_address'] || 'amqp://localhost:5672',
|
|
14
|
+
vhost: ENV['ampq_vhost'] || '/',
|
|
15
|
+
exchange: 'sneakers',
|
|
16
|
+
exchange_type: :direct,
|
|
17
|
+
metrics: Sneakers::Metrics::LoggingMetrics.new,
|
|
18
|
+
handler: Sneakers::Handlers::Maxretry,
|
|
19
|
+
handler_class: handler_class
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Sneakers.server = true
|
|
23
|
+
Sneakers.configure(opts)
|
|
24
|
+
Sneakers.logger.level = ENV['log_level'] == 'debug' ? Logger::DEBUG : Logger::INFO
|
|
25
|
+
|
|
26
|
+
Rollbar.configure do |config|
|
|
27
|
+
config.access_token = ENV['rollbar_access_token']
|
|
28
|
+
config.environment = ENV['RACK_ENV']
|
|
29
|
+
config.enabled = ENV['RACK_ENV'] == 'staging' || ENV['RACK_ENV'] == 'production'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
r = Sneakers::Runner.new([Harmony::Service::RpcService])
|
|
33
|
+
pid = Sneakers::CONFIG[:pid_path]
|
|
34
|
+
puts "Started pid: #{pid}"
|
|
35
|
+
r.run
|
data/harmony-service.gemspec
CHANGED
|
@@ -11,9 +11,9 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = "Gem which helps you to build Harmony services"
|
|
12
12
|
spec.homepage = "https://github.com/HarmonyMobile/harmony-service"
|
|
13
13
|
spec.license = "MIT"
|
|
14
|
-
spec.files = `git ls-files
|
|
15
|
-
spec.
|
|
16
|
-
spec.executables = spec.files.grep(
|
|
14
|
+
spec.files = `git ls-files`.split($/).reject { |f| f == 'Gemfile.lock' }
|
|
15
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
|
16
|
+
spec.executables = spec.files.grep(/^bin/).map { |f| File.basename(f) }
|
|
17
17
|
spec.require_paths = ["lib"]
|
|
18
18
|
|
|
19
19
|
spec.add_dependency "sneakers", '~> 2.4'
|
|
@@ -5,29 +5,11 @@ require 'json'
|
|
|
5
5
|
require 'oj'
|
|
6
6
|
require 'rollbar'
|
|
7
7
|
|
|
8
|
-
opts = {
|
|
9
|
-
amqp: ENV['ampq_address'] || 'amqp://localhost:5672',
|
|
10
|
-
vhost: ENV['ampq_vhost'] || '/',
|
|
11
|
-
exchange: 'sneakers',
|
|
12
|
-
exchange_type: :direct,
|
|
13
|
-
metrics: Sneakers::Metrics::LoggingMetrics.new,
|
|
14
|
-
handler: Sneakers::Handlers::Maxretry
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
Sneakers.configure(opts)
|
|
18
|
-
Sneakers.logger.level = ENV['log_level'] == 'debug' ? Logger::DEBUG : Logger::INFO
|
|
19
|
-
|
|
20
|
-
Rollbar.configure do |config|
|
|
21
|
-
config.access_token = ENV['rollbar_access_token']
|
|
22
|
-
config.environment = ENV['RACK_ENV']
|
|
23
|
-
config.enabled = ENV['RACK_ENV'] == 'staging' || ENV['RACK_ENV'] == 'production'
|
|
24
|
-
end
|
|
25
|
-
|
|
26
8
|
module Harmony
|
|
27
9
|
module Service
|
|
28
10
|
class RpcService
|
|
29
|
-
|
|
30
11
|
include Sneakers::Worker
|
|
12
|
+
from_queue ENV['harmony_queue'], timeout_job_after: 10, threads: 1
|
|
31
13
|
|
|
32
14
|
def work_with_params(message, delivery_info, metadata)
|
|
33
15
|
begin
|
|
@@ -35,9 +17,9 @@ module Harmony
|
|
|
35
17
|
request = Oj.load(message)
|
|
36
18
|
request_class = request.class
|
|
37
19
|
response_class = request_response_mapping[request_class]
|
|
38
|
-
raise "Unacceptable request class: #{request_class}" if response_class.nil?
|
|
20
|
+
raise "Unacceptable request class: #{request_class}" if response_class.nil?
|
|
39
21
|
|
|
40
|
-
result = work_with_request(request)
|
|
22
|
+
result = new_handler.work_with_request(request)
|
|
41
23
|
raise "Unacceptable response class: #{result.class}" unless response_class === result
|
|
42
24
|
|
|
43
25
|
json = Oj.dump(result)
|
|
@@ -98,9 +80,19 @@ module Harmony
|
|
|
98
80
|
ActionList::ListRequest => Array,
|
|
99
81
|
ActionList::ItemRequest => ActionList::Item,
|
|
100
82
|
ActionList::ActionRequest => NilClass,
|
|
101
|
-
Chart::Request => Chart::Response
|
|
83
|
+
Chart::Request => Chart::Response,
|
|
84
|
+
Form::GetRequest => Form::GetResponse,
|
|
85
|
+
Flow::EndedRequest => Response
|
|
102
86
|
}
|
|
103
87
|
end
|
|
88
|
+
|
|
89
|
+
def new_handler
|
|
90
|
+
handler_class = Sneakers::CONFIG[:handler_class]
|
|
91
|
+
raise "No handler specified" if handler_class.nil?
|
|
92
|
+
|
|
93
|
+
handler = Object.const_get(handler_class).new
|
|
94
|
+
raise "Unable to create handler: #{handler_class}" if handler.nil?
|
|
95
|
+
end
|
|
104
96
|
end
|
|
105
97
|
end
|
|
106
98
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Harmony
|
|
2
2
|
module Service
|
|
3
|
-
VERSION = "0.
|
|
3
|
+
VERSION = "0.3.0"
|
|
4
4
|
end
|
|
5
5
|
end
|
|
6
6
|
|
|
@@ -16,3 +16,11 @@ end
|
|
|
16
16
|
module Harmony::Service::ActionList
|
|
17
17
|
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
module Harmony::Service::Form
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Harmony::Service::Flow
|
|
25
|
+
|
|
26
|
+
end
|
data/lib/harmony/service.rb
CHANGED
|
@@ -16,5 +16,10 @@ require 'harmony/service/action_list/list_request'
|
|
|
16
16
|
require 'harmony/service/action_list/item_request'
|
|
17
17
|
require 'harmony/service/action_list/item'
|
|
18
18
|
|
|
19
|
+
require 'harmony/service/form/get_request'
|
|
20
|
+
require 'harmony/service/form/get_response'
|
|
21
|
+
|
|
22
|
+
require 'harmony/service/flow/ended_request'
|
|
23
|
+
|
|
19
24
|
require "harmony/service/rpc_service"
|
|
20
25
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Harmony::Service::RpcService do
|
|
4
|
+
|
|
5
|
+
describe "#work_with_params" do
|
|
6
|
+
let(:metadata) { instance_double("Metadata", reply_to: "harmony.trello", correlation_id: "abc123") }
|
|
7
|
+
let(:handler) { handler = double("Object") }
|
|
8
|
+
|
|
9
|
+
context "ack!" do
|
|
10
|
+
before(:each) do
|
|
11
|
+
expect(handler).to receive(:work_with_request).with(kind_of(request.class)) { response }
|
|
12
|
+
allow(subject).to receive(:new_handler) { handler }
|
|
13
|
+
allow(subject).to receive(:send_response)
|
|
14
|
+
allow(subject).to receive(:ack!)
|
|
15
|
+
subject.work_with_params(Oj.dump(request), {}, metadata)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context "calculation" do
|
|
19
|
+
let(:request) { Harmony::Service::Calculator::Request.new(harmony_user_email: "matt@futureworkshops.com", inputs: {"lat" => "51.0", "lon" => "0.1"}) }
|
|
20
|
+
let(:response) { Harmony::Service::Calculator::Response.new(outputs: {price: 50})}
|
|
21
|
+
|
|
22
|
+
it { expect(subject).to have_received(:send_response).with("{\"^o\":\"Harmony::Service::Calculator::Response\",\"outputs\":{\":price\":50}}", "harmony.trello", "abc123") }
|
|
23
|
+
it { expect(subject).to have_received(:ack!) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "action list" do
|
|
27
|
+
context "list" do
|
|
28
|
+
let(:request) { Harmony::Service::ActionList::ListRequest.new(harmony_user_email: "matt@futureworkshops.com", page: 0, per_page: 10) }
|
|
29
|
+
let(:response) { [Harmony::Service::ActionList::Item.new({id: 1, title: "Carrots"})]}
|
|
30
|
+
|
|
31
|
+
it { expect(subject).to have_received(:send_response).with("[{\"^o\":\"Harmony::Service::ActionList::Item\",\"id\":1,\"title\":\"Carrots\"}]", "harmony.trello", "abc123") }
|
|
32
|
+
it { expect(subject).to have_received(:ack!) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context "item" do
|
|
36
|
+
let(:request) { Harmony::Service::ActionList::ItemRequest.new(harmony_user_email: "matt@futureworkshops.com", id: 1) }
|
|
37
|
+
let(:response) { Harmony::Service::ActionList::Item.new({id: 1, title: "Carrots", subtitle: "Bag of Carrots", detail_html: "<html><body>Carrots</body></html>"})}
|
|
38
|
+
|
|
39
|
+
it { expect(subject).to have_received(:send_response).with("{\"^o\":\"Harmony::Service::ActionList::Item\",\"id\":1,\"title\":\"Carrots\",\"subtitle\":\"Bag of Carrots\",\"detail_html\":\"<html><body>Carrots</body></html>\"}", "harmony.trello", "abc123") }
|
|
40
|
+
it { expect(subject).to have_received(:ack!) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context "action" do
|
|
44
|
+
let(:request) { Harmony::Service::ActionList::ActionRequest.new(harmony_user_email: "matt@futureworkshops.com", id: 1, action: "Done") }
|
|
45
|
+
let(:response) { nil }
|
|
46
|
+
|
|
47
|
+
it { expect(subject).to have_received(:send_response).with("null", "harmony.trello", "abc123") }
|
|
48
|
+
it { expect(subject).to have_received(:ack!) }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "chart" do
|
|
53
|
+
let(:request) { Harmony::Service::Chart::Request.new(harmony_user_email: "matt@futureworkshops.com") }
|
|
54
|
+
let(:response) { Harmony::Service::Chart::Response.new(x_values: ["Jan", "Feb", "Mar"], y_values: [10, 20, 40])}
|
|
55
|
+
it { expect(subject).to have_received(:send_response).with("{\"^o\":\"Harmony::Service::Chart::Response\",\"x_values\":[\"Jan\",\"Feb\",\"Mar\"],\"y_values\":[10,20,40]}", "harmony.trello", "abc123") }
|
|
56
|
+
it { expect(subject).to have_received(:ack!) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "form" do
|
|
60
|
+
context 'get' do
|
|
61
|
+
let(:request) { Harmony::Service::Form::GetRequest.new(harmony_user_email: "matt@futureworkshops.com", inputs: ['satisfaction_level']) }
|
|
62
|
+
let(:response) { Harmony::Service::Form::GetResponse.new({input_values: [{'satisfaction_level': ['1','2','3']}]})}
|
|
63
|
+
it { expect(subject).to have_received(:send_response).with("{\"^o\":\"Harmony::Service::Form::GetResponse\",\"input_values\":[{\":satisfaction_level\":[\"1\",\"2\",\"3\"]}],\"options\":{}}", "harmony.trello", "abc123") }
|
|
64
|
+
it { expect(subject).to have_received(:ack!) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "flow ended" do
|
|
69
|
+
let(:request) { Harmony::Service::Flow::EndedRequest.new(harmony_user_email: "matt@futureworkshops.com", pages: [{page_id: 1}]) }
|
|
70
|
+
let(:response) { Harmony::Service::Response.new}
|
|
71
|
+
it { expect(subject).to have_received(:send_response).with("{\"^o\":\"Harmony::Service::Response\"}", "harmony.trello", "abc123") }
|
|
72
|
+
it { expect(subject).to have_received(:ack!) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context "unacceptable request class" do
|
|
78
|
+
before(:each) do
|
|
79
|
+
allow(subject).to receive(:work_with_request).and_raise("A timeout occured")
|
|
80
|
+
allow(subject).to receive(:send_response)
|
|
81
|
+
allow(subject).to receive(:reject!)
|
|
82
|
+
|
|
83
|
+
metadata = instance_double("Metadata", reply_to: "harmony.trello", correlation_id: "abc123")
|
|
84
|
+
subject.work_with_params("{\"trello_board_id\": \"12345\"}", {}, metadata)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it { expect(subject).to have_received(:send_response).with("{\"^o\":\"Harmony::Service::ErrorResponse\",\"message\":\"An error occured.\",\"detailed_message\":\"Unacceptable request class: Hash\"}", "harmony.trello", "abc123") }
|
|
88
|
+
it { expect(subject).to have_received(:reject!) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
|
2
|
+
require 'harmony/service'
|
|
3
|
+
require 'byebug'
|
|
4
|
+
|
|
5
|
+
opts = {
|
|
6
|
+
amqp: 'amqp://localhost:5672',
|
|
7
|
+
vhost: '/',
|
|
8
|
+
exchange: 'sneakers',
|
|
9
|
+
exchange_type: :direct,
|
|
10
|
+
metrics: Sneakers::Metrics::LoggingMetrics.new,
|
|
11
|
+
handler: Sneakers::Handlers::Maxretry
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Sneakers.configure(opts)
|
|
15
|
+
Sneakers.logger.level = Logger::DEBUG
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: harmony-service
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Brooke-Smith
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2017-01-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sneakers
|
|
@@ -111,7 +111,8 @@ dependencies:
|
|
|
111
111
|
description:
|
|
112
112
|
email:
|
|
113
113
|
- matt@futureworkshops.com
|
|
114
|
-
executables:
|
|
114
|
+
executables:
|
|
115
|
+
- harmony_service
|
|
115
116
|
extensions: []
|
|
116
117
|
extra_rdoc_files: []
|
|
117
118
|
files:
|
|
@@ -124,8 +125,7 @@ files:
|
|
|
124
125
|
- LICENSE.txt
|
|
125
126
|
- README.md
|
|
126
127
|
- Rakefile
|
|
127
|
-
- bin/
|
|
128
|
-
- bin/setup
|
|
128
|
+
- bin/harmony_service
|
|
129
129
|
- harmony-service.gemspec
|
|
130
130
|
- lib/harmony/service.rb
|
|
131
131
|
- lib/harmony/service/action_list/action_request.rb
|
|
@@ -137,11 +137,16 @@ files:
|
|
|
137
137
|
- lib/harmony/service/chart/request.rb
|
|
138
138
|
- lib/harmony/service/chart/response.rb
|
|
139
139
|
- lib/harmony/service/error_response.rb
|
|
140
|
+
- lib/harmony/service/flow/ended_request.rb
|
|
141
|
+
- lib/harmony/service/form/get_request.rb
|
|
142
|
+
- lib/harmony/service/form/get_response.rb
|
|
140
143
|
- lib/harmony/service/message.rb
|
|
141
144
|
- lib/harmony/service/request.rb
|
|
142
145
|
- lib/harmony/service/response.rb
|
|
143
146
|
- lib/harmony/service/rpc_service.rb
|
|
144
147
|
- lib/harmony/service/version.rb
|
|
148
|
+
- spec/harmony/rpc_service_spec.rb
|
|
149
|
+
- spec/spec_helper.rb
|
|
145
150
|
homepage: https://github.com/HarmonyMobile/harmony-service
|
|
146
151
|
licenses:
|
|
147
152
|
- MIT
|
|
@@ -166,4 +171,6 @@ rubygems_version: 2.4.5.1
|
|
|
166
171
|
signing_key:
|
|
167
172
|
specification_version: 4
|
|
168
173
|
summary: Gem which helps you to build Harmony services
|
|
169
|
-
test_files:
|
|
174
|
+
test_files:
|
|
175
|
+
- spec/harmony/rpc_service_spec.rb
|
|
176
|
+
- spec/spec_helper.rb
|
data/bin/console
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "harmony/service"
|
|
5
|
-
|
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
-
|
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
-
# require "pry"
|
|
11
|
-
# Pry.start
|
|
12
|
-
|
|
13
|
-
require "irb"
|
|
14
|
-
IRB.start
|