A wrapper around the ruote workflow engine, built as a modular sinatra application.
Follow development:
-
#ruote on Freenode
-
@kennethkalmer on Twitter
ruote-kit uses Bundler to setup and maintain its environment. Before running ruote-kit for the first time you need to install Bundler (gem install bundler) and then run:
$ bundle install
Bundler will download all the required gems and install them for you.
Have a look at the Gemfile if you want to see the various dependencies.
-
Get the source files using git
$ git clone http://github.com/tosch/ruote-kit.git && cd ruote-kit
-
Make sure every dependency is resolved
$ bundle install
-
Get going
$ rackup
-
You’ll need an empty directory
$ mkdir my-ruote-kit && cd my-ruote-kit
-
There are two files needed in that directory: Gemfile and config.ru
-
Gemfile
source :gemcutter gem 'ruote', '~> 2.2.0' gem 'ruote-kit', '~> 2.2.0'
-
config.ru
begin # Try to require the preresolved locked set of gems. require ::File.expand_path('.bundle/environment', __FILE__) rescue LoadError # Fall back on doing an unlocked resolve at runtime. require "rubygems" require "bundler" Bundler.setup(:default) end # load json backend require 'yajl-ruby' # fastest, but uses a c module # require 'json_pure' # should work everywhere require 'ruote-kit' RuoteKit.engine = Ruote::Engine.new( Ruote::Worker.new( Ruote::FsStorage.new('ruote_work'))) RuoteKit.engine.register do catchall end use Rack::CommonLogger use Rack::Lint run RuoteKit::Application
-
-
Install all needed gems
$ bundle install
-
Get going
$ rackup
If ruote-kit starts up without any issues (ie missing dependencies), you can point your browser to ‘localhost:9292/_ruote/’ to get going. By default ruote-kit binds to all IP addresses, so this works out the box remotely too.
ruote-kit is fully self-sufficient piece of rack, but can be slotted into any rack middleware stack without issues.
Example:
RuoteKit.engine = Ruote::Engine.new( Ruote::Worker.new( Ruote::FsStorage.new('ruote_work'))) RuoteKit.engine.register do catchall end # Slot into the stack use RuoteKit::Application
ruote-kit ships with an application template for Ruby on Rails 3.
-
install Rails
$ gem install rails
-
create a new Rails app by running
$ rails new foo -m https://github.com/tosch/ruote-kit/raw/master/rails-template.rb
-
cd into the new Rails dir
$ cd foo
-
make sure all dependencies are met
$ bundle install
-
in your Rails dir, apply the app template
$ rake rails:template LOCATION=https://github.com/tosch/ruote-kit/raw/master/rails-template.rb
See config/initializers/ruote-kit.rb in your Rails app.
$ rails server
Browse to localhost:3000/_ruote and you’ll see there are no running processes. You could change that using the “Launch process” link ;-)
You can access Ruote’s engine anywhere in your Rails code by calling
RuoteKit.engine
So launching a workflow process is as easy as
RuoteKit.engine.launch your_process_definition
The storage participant (used by the catchall participant) is available at
RuoteKit.storage_participant
See github.com/tosch/ruote-on-rails for an example Rails app.
ruote-kit itself needs only little configuration, the only thing to do is to bind a ruote engine to use. That engine may be configured, see ruote.rubyforge.org/configuration.html for details.
When using the source version, you’ll have to edit the config.ru
file if you want to change the engine configuration. It defaults to use file system persistence and runs a worker within the engine. The persistence files will be stored in a sub directory called “ruote_work_#{ENV}”.
When using the gem version or plugging ruote-kit into your rack stack, you’ll have to bind the ruote engine to use yourself. See the examples above.
ruote participants may be registered using the register method of the engine. It expects a block containing of participant
and one or less catchall
calls.
require 'ruote/part/no_op_participant' RuoteKit.engine.register do participant 'no-op', Ruote::NoOpParticipant catchall end
Note that all participant
calls after a catchall
one are pretty useless: The catchall will eat all their cookies.
If you want to learn more about ruote’s participants, have a look at ruote.rubyforge.org/participants.html .
The workitems
resource relies on the Ruote::StorageParticipant. You’ll have to register at least one Storage Participant if you ever want to see a workitem in the resource. Example:
require 'ruote/part/storage_participant' RuoteKit.engine.register_participant :storage, Ruote::StorageParticipant
You may also use the catchall participant provided by ruote. It’s named ‘.+’, so it will catch all workitems for any participant mentioned in your workflow definitions which are not already caught by another (previously) registered participant. So make sure to register the catchall after your own participants. The catchall participant may be registered by calling catchall
within the block given to Ruote::Engine#register (this will use Ruote::StorageParticipant as participant implementation, you may use any options of Ruote::Engine#register_participant to overwrite that default – see the example above).
Always make sure to have a running ruote worker for your storage. The shipped config.ru takes care of that, but if you use the gem version or use ruote-kit as part of your rack stack, you’ll need to make sure there is a running worker.
Perhaps it’s best to give some more explanations about the architecture of ruote here. Ruote’s engine class is shallow, just a few methods that insert launch and reply orders in the storage and read it when querying for process statuses. You see: The storage is important, it is used as communications backend between the various parts of ruote. The engine class and the storage are not enough, though. The real work is done by one or more workers which query the storage for things to do (note: it is unlikely that you will ever need more than one worker for one ruote instance; ruote isn’t fast, but it’s faster than most human processes, so that you’ll won’t need more than one worker unless you are running hundreds of processes at the same time ).
Ruote ships with one worker implementation (you won’t need more) and various different storages. Have a look at ruote.rubyforge.org/configuration.html#storage for an overview.
In the most examples above, the worker instance is bound to the engine which is given to RuoteKit.engine:
RuoteKit.engine = Ruote::Engine.new( Ruote::Worker.new( Ruote::FsStorage.new('ruote_work')))
That is fine, especially when there is only one instance of the app running or the storage implementation supports multiple workers, because if there is more than one instance of the app running, there will be more than one worker operating on the same storage.
If you want to use a storage implementation which doesn’t support multiple workers (Ruote::FsStorage under Windows, for example), you should start a dedicated worker in its own instance. In config.ru (or whereever you configure the engine to be used by ruote-kit), instanciate the engine without a worker:
RuoteKit.engine = Ruote::Engine.new( Ruote::FsStorage.new('ruote_work'))
ruote-kit or your rack app will start with no problems, you may even launch processes, but they’ll never show up under /_ruote/processes: There is no worker which processes the launch requests stored in the storage.
To run a worker you need to setup a worker script similar to the rake task example below:
require 'rake' require 'ruote-kit' desc "Run a ruote-kit worker" task :ruote_kit_worker do RuoteKit.run_worker(Ruote::FsStorage.new('ruote_work')) end
Make sure to configure the storage in the same way as in the rest of the application or you won’t get what you expect ;-)
If you used the Ruby on Rails 3 template for ‘installing’ ruote-kit, a ruote:run_worker task is added automatically (in lib/tasks/ruote.rake).
You also should consider using a separate worker instance when you’re running ruote-kit or your rack app in an environment like Passenger: You won’t be sure the app runs all the time, so it’s likely that scheduled events will be missed (better: triggered too late). If you don’t want to start a separate worker process, configure Passenger in a way that your app won’t be killed in a very long time (Passenger 3 provides an option to ensure one instance of the app won’t be killed) and make sure your storage implementation supports multiple workers.
Feedback and bug reports are welcome on the mailing-list, or on the #ruote IRC channel at Freenode.net.
Please do not hesitate to come back with any feedback.
(The MIT License)
Copyright © 2009-2011 Kenneth Kalmer (Internet Exchange CC, Clear Planet Information Solutions Pty Ltd)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
rack, rack.rubyforge.org/
-
sinatra, www.sinatrarb.com/
-
sinatra-respond_to, github.com/cehoffman/sinatra-respond_to
-
haml, haml-lang.com/
-
yajl-ruby, github.com/brianmario/yajl-ruby
-
jquery, jquery.com/
many thanks to the authors and contributors