Ruby TCP "bot" using EventMachine - implementing a command dispatcher - ruby

I've crafted a basic TCP client using EventMachine. Code:
# run.rb
EventMachine::run do
EventMachine::connect $config_host, $config_port, Bot
end
# bot.rb
module Bot
def post_init
# log us in and do any other spinup.
sleep(1)
send_data $config_login + "\n"
EventMachine.add_periodic_timer($config_keepalive_duration) { send_data $config_keepalive_str + "\n" }
#valid_command = /^<#{$config_passphrase}:([^>:]+):(#\d+)>(.*)$/
end
def receive_data(data)
if(ma = #valid_command.match(data))
command, user, args = ma[1,3]
args.strip!
command.downcase!
p "Received: #{command}, #{user}, #{args}"
# and.. here we handle the command.
end
end
end
This all works quite well. The basic idea is that it should connect, listen for specially formatted commands, and execute the command; in executing the command, there may be any number of "actions" taken that result in various data sent by the client.
But, for my next trick, I need to add the ability to actually handle the commands that Bot receives.
I'd like to do this using a dynamic library of event listeners, or something similar to that; ie, I have an arbitrary number of plugins that can register to listen for a specific command and get a callback from bot.rb. (Eventually, I'd like to be able to reload these plugins without restarting the bot, too.)
I've looked at the ruby_events gem and I think this makes sense but I'm having a little trouble of figuring out the best way to architect things. My questions include...
I'm a little puzzled where to attach ruby_events listeners to - it just extends Object so it doesn't make it obvious how to implement it.
Bot is a module, so I can't just call Bot.send_data from one of the plugins to send data - how can I interact with the EM connection from one of the plugins?
I'm completely open to any architectural revisions or suggestions of other gems that make what I'm trying to do easier, too.

I'm not sure exactly what you're trying to do, but one common pattern of doing this in EM is to define the command handlers as callbacks. So then the command logic can be pushed up out of the Bot module itself, which just handles the basic socket communication and command parsing. Think of how a web server dispatches to an application - the web server doesn't do the work, it just dispatches.
For example something like this
EM::run do
bot = EM::connect $config_host, $config_port, Bot
bot.on_command_1 do |user, *args|
# handle command_1 in a block
end
# or if you want to modularize it, something like this
# where Command2Handler = lambda {|user, *args| #... }
bot.on_command_2(&Command2Handler)
end
So then you just need to implement #on_command_1, #on_command_2, etc. in your Bot, which is just a matter of storing the procs to instance vars; then calling them once you parse the passed commands out of your receive_data.
A good, very readable example of this in production is TwitterStream.

Related

How can I run a method at the end of a file from a gem?

I have a gem (e.g. mygem) and as is normal, I add mygem to a file by putting require "mygem" at the top. What if I have a method in mygem called finish_jobs and I want it to run in the following location:
require "mygem"
# code, code code
finish_jobs
How would I do that without forcing the user to add the method every time they use the gem?
Specifically, what I am trying to do is write a server app (with rack) and I need the methods in the body of the file to be processed before the server is started.
This is certainly possible.
Why not just add the code directly into the Gem (since it sounds like it is under your control and is not an external dependency)?
module MyGem
def printSomething
p 2 + 2
end
module_function :printSomething
printSomething()
# => 4
end
If this isn't what you had in mind, let me know and I can update the solution.
Also, see Kernel#at_exit
A more explanatory guide on Kernel#at_exit
I don't know of a way to do what you're describing.
One workaround would be to provide an API which accepts a block. This approach allows you to run code after the user's setup without exposing implementation details to them.
A user could call your library method, providing a block to set up their server:
require "mygem"
MyGem.code_code_code {
# user's code goes here
}
Then, your library code would:
Accept the block
Call some library code
Here's an example implementation:
module MyGem
# Run some user-provided code by `yield`-ing the block
# Then run the gem's finalizer
def self.code_code_code
# Execute the block:
yield
# Finalize:
finish_jobs
end
end
This way, you can accept code from the user but still control setup and finalization.
I hope it helps!

How to fake a web service with ruby and MiniTest

I'm writing a Rails app to send text messages using the Twilio API:
http://www.twilio.com/docs/api/rest/sending-sms
and to this end, I do:
client = Twilio::REST::Client.new account_sid, auth_token
client.account.sms.messages.create({
# stuff ...
})
That's all good and nice -- however, I don't want my tests to send a bunch of text messages because that would be stupid. So, I'd like to override Twilio::REST::Client.new to give me an object that'll let me call acccount.sms.messages.create in my tests without undue fuss.
I have a solution that works, but feels ugly:
def mock_twilio_service(stub_client)
Twilio::REST::Client.stub :new, stub_client do
yield
end
end
class Recordy
attr_accessor :calls
def initialize
#calls = []
end
def method_missing(method, *args)
ret = self.class.new
#calls << {
method: method,
args: args,
block_given: block_given?,
ret: ret
}
yield if block_given?
ret
end
end
and then in my test:
test "send a text" do
cli = Recordy.new
mock_twilio_service cli do
# ... stuff
end
end
I feel like I'm missing something Super Obvious, but I'm not sure. Am I? Or am I totally barking up the wrong tree? (Yes, I've looked at How do I mock a Class with Ruby? but I don't think it's quite the same...?)
Another idea would be to use WebMock. As your client is making requests to Twilio. You can just stub out the requests. Within the stub you can also define what is returned from the requests and with which parameters it can be called.
And when you set
WebMock.disable_net_connect!
it is sure that no real requests can be made from the test.
This way you don't change any behavior of your test and will not rely on an external API for your tests to pass.
Twilio evangelist here.
We wrote Test Credentials exactly for this scenario. Test Credentials are a special set of credentials (AccountSid and AuthToken) that you can use when you make requests to the Twilio REST API that tell it to basically just go through the motions of making a phone call or sending a text message, but not actually do it (or charge you for it).
You can also use a special set of phone numbers to get Twilio to return specific success or error conditions.
You can find your test credentials in your Twilio dashboard.
Hope that helps.

rails persist objects over requests in development mode

I am trying to interact with Matlab.Application.Single win32ole objects in my rails application. The problem I am running into is that while I am developing my application, each separate request reloads my win32ole objects so I loose the connection to my matlab orignal instances and new instances are made. Is there a way to persist live objects between requests in rails? or is there a way to reconnect to my Matlab.Application.Single instances?
In production mode I use module variables to store my connection between requests, but in development mode Module variables are reloaded every request.
here is a snippet of my code
require 'win32ole'
module Calculator
#engine2 = nil
#engine3 = nil
def self.engine2
if #engine2.nil?
#engine2 = WIN32OLE.new("Matlab.Application.Single")
#engine2.execute("run('setup_path.m')")
end
#engine2
end
def self.engine3
if #engine3.nil?
#engine3 = WIN32OLE.new("Matlab.Application.Single")
#engine3.execute("run('setup_path.m')")
end
#engine3
end
def self.load_CT_image(file)
Calculator.engine2.execute("spm_image('Init','#{file}')")
end
def self.load_MR_image(file)
Calculator.engine3.execute("spm_image('Init','#{file}')")
end
end
I am then able to use my code in my controllers like this:
Calculator.load_CT_image('Post_Incident_CT.hdr')
Calculator.load_MR_image('Post_Incident_MRI.hdr')
You can keep an app-wide object in a constant that won't be reset for every request. Add this to a new file in config/initializers/:
ENGINE_2 = WIN32OLE.new("Matlab.Application.Single")
You might also need to include the .execute("run('setup_path.m')") line here as well (I'm not familiar with WIN32OLE). You can then assign that object to your instance variables in your Calculator module (just replace the WIN32OLE.new("Matlab.Application.Single") call with ENGINE_2, or simply refer to them directly.
I know this is beyond the scope of your question, but you have a lot of duplicated code here, and you might want to think about creating a class or module to manage your Matlab instances -- spinning up new ones as needed, and shutting down old ones that are no longer in use.

Replacement in 1.9 for Socket's ready? method

For some time I have been using an old Ruby distribution (I think it was 1.8.6) on which I coded with the socket library. The old library had a method called ready?, which checked whether there was still data to be received without blocking. What would be the best replacement for that in 1.9?
The reason why I require it is as I have a program structured like this:
def handle_socket_messages
while true
break unless messages_to_send
sent_messages
end
while #s and #s.ready?
#read messages
readStr = #s.recv(0x1024)
...
end
end
(I then have another loop which keeps executing the handle_socket_messages method and then uses a sleep, so that the loop doesn't spin too fast, along with some other methods.
As you can see, I need to check whether I will receive data using #s.ready? (#s is a socket), otherwise the loops hang at readStr = #s.recv(0x1024), where the socket keeps wanting to receive data which the server doesn't send (It's waiting for data when it shouldn't).
What would be the best replacement for this method?
The solution was:
class Socket
def ready
not IO.select([self], nil, nil, 0) == nil
end
end
I've been using the ready? method successfully in Ruby 2.2.2 by requiring io/wait. There is a bit more info in this SO answer: https://stackoverflow.com/a/3983850/2464

Is it possible to access Ruby EventMachine Channels from Thin/Rack/Sinatra?

I'm looking to build a simple, RESTful notification system for an internal project leveraging Sinatra. I've used EventMachine channels in the past to subscribe/publish to events, but in all my previous cases I was using EventMachine directly.
Does anyone know if it's possible to create, subscribe, and publish to EventMachine channels (running in Thin) from a Sinatra application, or even from some Rack middleware for that matter?
Have a look at async_sinatra.
Basically, to make it possible to use EventMachine when running in Thin you need to make it aware that you want to serve requests asynchronously. The Rack protocol is synchronous by design, and Thin expects a request to be done when the handler returns. There are ways to make Thin aware that you want to handle the request asynchronously (see think_async for an example how), and async_sinatra makes it very easy.
Bryan,
You can use the em-http-request library (https://github.com/igrigorik/em-http-request), this will allow you to reference a specific EventMachine application running on either A. the same server, B. a different server, or C. wherever you want really.
require 'eventmachine'
require 'em-http-request'
require 'sinatra/base'
require 'thin'
class ServerClass < EventMachine::Connection
def initialize(*args)
# ruby singleton - store channel data in global hash
($channels ||= [])
end
def post_init
puts "initialized"
$cb.call("initialized");
end
def receive_data(data)
# got information from client connection
end
def channel_send(msg,channel)
$channels[channel].send_data(msg)
end
def channels_send(msg)
$channels.each{|channel| channel.send_data(msg)}
end
def unbind
# puts user left
end
end
EventMachine.run do
$cb = EM.callback {|msg| puts msg #do something creative}
$ems = EventMachine::start_server('0.0.0.0',ServerClass,args)
class App < Sinatra::Base
set :public, File.dirname(__FILE__) + '/public'
get '/' do
erb :index
end
end
App.run!({:port => 3000})
end
Above is a basic wireframe. Depending on how you want to go about sending data, you can use WebSockets (em-websocket) and bind each user on login (have to add a login system), or you can use this for whatever. As long as you have a global reference to the Eventmachine Object (connection, websocket, channel) you can pass messages from within your application.
BTW - It is optional to add the EventMachine.run do;....end loop, since Thin will do this anyways. It helps to know how it works though.
Good Luck

Resources