For this I used Daniel Berger’s win32-service package. This is a great package that does literally everything for you to enable you to create a nice Windows service. Just install the gem like this:
gem install win32-service
I used examples/daemon_test.rb as the base template to make my Windows Service. This piece of code contains all that is needed to run the code snippet above as a service. In this file you will find a class called Daemon that has a number of methods necessary for running a Windows service. Put in the necessary requires and place your ActiveRecord initialization code at the beginning of the code. Then under service_main, while the status == RUNNING, put in the main processing code, minus the loop. Your code could possibly look something like this (don’t cut and paste this code, just use it as a reference):
require 'rubygems' require 'logger' require 'active_record' require 'c:/myrailsapp/app/models/message' # remember to put in the absolute path here require "win32/service" include Win32 ActiveRecord::Base.establish_connection({ :adapter => "mysql", :host => "localhost", :username => "xxx", :password => "xxx", :database => "mydatabase" }) # I start the service name with an 'A' so that it appears at the top SERVICE_NAME = "MyProcess Service" SERVICE_DISPLAYNAME = "MyProcess" if ARGV[0] == "install" svc = Service.new svc.create_service{ |s| s.service_name = SERVICE_NAME s.display_name = SERVICE_DISPLAYNAME s.binary_path_name = 'ruby ' + File.expand_path($0) s.dependencies = [] } svc.close puts "installed" elsif ARGV[0] == "start" Service.start(SERVICE_NAME) # do stuff before starting puts "Ok, started" elsif ARGV[0] == "stop" Service.stop(SERVICE_NAME) # do stuff before stopping puts "Ok, stopped" elsif ARGV[0] == "uninstall" || ARGV[0] == "delete" begin Service.stop(SERVICE_NAME) rescue end Service.delete(SERVICE_NAME) # do stuff before deleting puts "deleted" elsif ARGV[0] == "pause" Service.pause(SERVICE_NAME) # do stuff before pausing puts "Ok, paused" elsif ARGV[0] == "resume" Service.resume(SERVICE_NAME) # do stuff before resuming puts "Ok, resumed" else if ENV["HOMEDRIVE"]!=nil puts "No option provided. You must provide an option. Exiting..." exit end ## SERVICE BODY START class Daemon logger = Logger.new("c:/myprocess.log") def service_stop logger.info "Service stopped" end def service_pause logger.info "Service paused" end def service_resume logger.info "Service resumed" end def service_init logger.info "Service initializing" # some initialization code for your process end ## worker function def service_main begin while state == RUNNING || state == PAUSED while state == RUNNING # --- start processing code messages = Message.find :all, :conditions => ['sent = (?)', 0], # check if the message has been sent :limit => 20 # retrieve and process 20 at a time # array of threads threads = [] # iterate through each message for message in messages do # start a new thread to process the message threads < < Thread.new(message) do |message| # process the message here ... message.sent = 1 message.save end # don't finish until all threads are done threads.each do |t| begin # join the threads when it's done t.join rescue RuntimeError => e # do some rescuing puts “Failed: #{e.message}” end end end # — end processing code end if state == PAUSED # if you want do something when the process is paused end end rescue StandardError, Interrupt => e logger.error “Service error : #{e}” end end end d = Daemon.new d.mainloop end #if
Important to note that you need to put in the absolute path in the require as the service wouldn’t be starting at the Rails app.
Now you can install and start the Windows service (assuming the code is written in a file called ‘message_service.rb’:
c:/>ruby message_service.rb install c:/>ruby messag_service.rb start
You can also control it from your Windows Services MMC console. What you have now is a Windows service that loops around until there is a message record in your database that is not sent (sent = 0). If there are, it will retrieve up to 20 messages at a go and process them with a thread each (parallelizing the processing to make it faster). Once it is processed, it will indicate the meesage has been sent (sent = 1) and loop again. Now you can happily create messages from your Rails app and stuff them into the database, while your message processor will process them separately.