Skip to content

Commit

Permalink
Add Rodbot.request to query the app service from anywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
svoop committed Sep 9, 2023
1 parent a056685 commit 92ac3aa
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 30 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Minimalistic yet polyglot framework to build chat bots on top of a Roda backend
&emsp;&emsp;&emsp;[Relay Services](#label-Relay-Services) <br>
&emsp;&emsp;&emsp;[Schedule Service](#label-Schedule-Service) <br>
[CLI](#label-CLI) <br>
[Request](#request)<br>
[Say](#say)<br>
[Routes and Commands](#label-Routes-and-Commands) <br>
[Database](#label-Database) <br>
[Environments](#environments) <br>
Expand Down Expand Up @@ -189,7 +191,9 @@ Enter the command `!pay EUR 123` and you see the request `GET /pay?argument=EUR+

### Schedule Service

The **schedule service** is a [Clockwork process](https://github.com/Rykian/clockwork) which triggers HTTP requests to the **app service** based on timed events.
The **schedule service** is a [Clockwork process](https://github.com/Rykian/clockwork) which triggers Ruby code asynchronously as configured in `config/schedule.rb`.

It's a good idea to have the **app service** do the heavy lifting while the schedule simply fires the corresponding HTTP request.

## CLI

Expand Down Expand Up @@ -296,6 +300,21 @@ brew install overmind
overmind start
```

## Request

To query the **app service**, you can either use the bundled [HTTParty](https://rubygems.org/gems/httparty) gem or the following convenience wrapper:

```ruby
response = Rodbot.request('/time', query: { zone: 'UTC' })
```

This uses the default `method: :get` and the default `timeout: 10` seconds, it returns an instance of [HTTParty::Response](https://www.rubydoc.info/gems/httparty/HTTParty/Response):

```ruby
response.code # => 200
response.body # => '2023-09-06 22:51:50.231703 UTC'
```

## Say

You can send proactive messages to communication networks with `Rodbot.say`.
Expand Down
3 changes: 3 additions & 0 deletions lib/rodbot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
# * +Rodbot.plugins+ -> {Rodbot::Plugins}
# * +Rodbot.db+ -> {Rodbot::Db#db}
# * +Rodbot.log+ -> {Rodbot::Log#log}
# * +Rodbot.request+ -> {Rodbot::Rack#request}
# * +Rodbot.say+ -> {Rodbot::Relay#say}
module Rodbot
include Rodbot::Constants
extend Dry::Credentials
Expand All @@ -38,6 +40,7 @@ class << self
def_delegator :@plugins, :itself, :plugins
def_delegator :@db, :itself, :db
def_delegator :@log, :log
def_delegator 'Rodbot::Rack', :request
def_delegator 'Rodbot::Relay', :say

def boot(root: nil)
Expand Down
61 changes: 40 additions & 21 deletions lib/rodbot/rack.rb
Original file line number Diff line number Diff line change
@@ -1,34 +1,53 @@
# frozen-string-literal: true

require 'httparty'

using Rodbot::Refinements

module Rodbot
module Rack

# Default +config.ru+
#
# In case you wish to do things differently, just copy the contents of
# this method into your +config.ru+ file and tweak it.
def self.boot(rack)
loader = Zeitwerk::Loader.new
loader.logger = Rodbot::Log.logger('loader')
loader.push_dir(Rodbot.env.root.join('lib'))
loader.push_dir(Rodbot.env.root.join('app'))

if Rodbot.env.development? || Rodbot.env.test?
loader.enable_reloading
loader.setup
rack.run ->(env) do
loader.reload
class << self

# Default +config.ru+
#
# In case you wish to do things differently, just copy the contents of
# this method into your +config.ru+ file and tweak it.
def boot(rack)
loader = Zeitwerk::Loader.new
loader.logger = Rodbot::Log.logger('loader')
loader.push_dir(Rodbot.env.root.join('lib'))
loader.push_dir(Rodbot.env.root.join('app'))

if Rodbot.env.development? || Rodbot.env.test?
loader.enable_reloading
loader.setup
rack.run ->(env) do
loader.reload
# TODO: obsolete?
# Rodbot.plugins.extend_app
App.call(env)
end
else
loader.setup
Zeitwerk::Loader.eager_load_all
App.call(env)
end
else
loader.setup
Zeitwerk::Loader.eager_load_all
# TODO: obsolete?
# Rodbot.plugins.extend_app
rack.run App.freeze.app
rack.run App.freeze.app
end
end

# Send request to the app service
#
# @param path [String] path e.g. +/help+
# @param query [Hash] query hash e.g. +{ search: 'foobar' }+
# @param method [Symbol, String] HTTP method
# @param timeout [Integer] max seconds to wait for response
# @return [HTTParty::Response]
def request(path, query: {}, method: :get, timeout: 10)
HTTParty.send(method, Rodbot::Services::App.url.uri_concat(path), query: query, timeout: timeout)
end

end

end
Expand Down
3 changes: 2 additions & 1 deletion lib/rodbot/relay.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'socket'
require 'httparty'

# TODO: obsolete?
using Rodbot::Refinements

module Rodbot
Expand Down Expand Up @@ -92,7 +93,7 @@ def loops
# @param argument [String, nil] optional arguments
# @return [String] response as Markdown
def command(command, argument=nil)
response = HTTParty.get(Rodbot::Services::App.url.uri_concat(command), query: { argument: argument }, timeout: 10)
response = Rodbot.request(command, query: { argument: argument })
case response.code
when 200 then response.body
when 404 then "[[SENDER]] I don't know what do do with `!#{command}`. 🤔"
Expand Down
2 changes: 1 addition & 1 deletion lib/rodbot/simulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def reply_to(message)
return "(no command given)" unless message.match?(/^!/)
command, argument = message[1..].split(/\s+/, 2)
body = begin
response = HTTParty.get(Rodbot::Services::App.url.uri_concat(command), query: { argument: argument }, timeout: 10)
response = Rodbot.request(command, query: { argument: argument })
case response.code
when 200 then response.body
when 404 then "[[SENDER]] I've never heard of `!#{command}`, try `!help` instead. 🤔"
Expand Down
19 changes: 19 additions & 0 deletions spec/lib/rodbot/rack_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require_relative '../../spec_helper'

module Minitest
module HTTParty
def self.get(url, query:, timeout:)
url == 'http://localhost:7200/search' && query == { foo: 'bar' } && timeout == 10
end
end
end

describe Rodbot::Rack do
describe :request do
with '::HTTParty', Minitest::HTTParty

it "does a GET request to the full URL using HTTParty" do
_(Rodbot.request('/search', query: { foo: 'bar' })).must_equal true
end
end
end
12 changes: 6 additions & 6 deletions spec/lib/rodbot/simulator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,39 @@

it "handles 404 error from the app" do
mock_response = Minitest::Mock.new.expect(:code, 404)
HTTParty.stub(:get, mock_response) do
Rodbot.stub(:request, mock_response) do
_(subject.send(:reply_to, '!unknown')).must_match(/never heard of/)
_(mock_response.verify).must_equal true
end
end

it "handles other errors from the app" do
mock_response = Minitest::Mock.new.expect(:code, 500)
HTTParty.stub(:get, mock_response) do
Rodbot.stub(:request, mock_response) do
_(subject.send(:reply_to, '!broken')).must_match(/having trouble talking to the app/)
_(mock_response.verify).must_equal true
end
end

it "returns the response body" do
mock_response = Minitest::Mock.new.expect(:code, 200).expect(:body, 'hello')
HTTParty.stub(:get, mock_response) do
Rodbot.stub(:request, mock_response) do
_(subject.send(:reply_to, '!known')).must_equal 'hello'
_(mock_response.verify).must_equal true
end
end

it "interpolates [[SENDER]] in the response body" do
mock_response = Minitest::Mock.new.expect(:code, 200).expect(:body, 'hello [[SENDER]]')
HTTParty.stub(:get, mock_response) do
Rodbot.stub(:request, mock_response) do
_(subject.send(:reply_to, '!known')).must_equal 'hello tester'
_(mock_response.verify).must_equal true
end
end

it "converts Markdown in the response body" do
mock_response = Minitest::Mock.new.expect(:code, 200).expect(:body, '* list')
HTTParty.stub(:get, mock_response) do
Rodbot.stub(:request, mock_response) do
_(subject.send(:reply_to, '!known')).must_equal '● list'
_(mock_response.verify).must_equal true
end
Expand All @@ -67,7 +67,7 @@
describe :reply_to do
it "returns raw Markdown in the response body" do
mock_response = Minitest::Mock.new.expect(:code, 200).expect(:body, '* hello')
HTTParty.stub(:get, mock_response) do
Rodbot.stub(:request, mock_response) do
_(subject.send(:reply_to, '!known')).must_equal '* hello'
_(mock_response.verify).must_equal true
end
Expand Down

0 comments on commit 92ac3aa

Please sign in to comment.