Yuuki
A caller / runner framework for Ruby.
Yuuki lets you tag methods of a class and invoke them by tag. Arguments are delivered to each method by parameter name, and methods can be run in threads, ordered by priority, or executed periodically.
Installation
Add this line to your application's Gemfile:
gem 'yuuki'
And then execute:
$ bundle
Or install it yourself as:
$ gem install yuuki
Usage
Basics
Define a runner class that inherits Yuuki::Runner. A method named on_<tag> is automatically registered with the tag <tag>. Any other method can be registered with the on decorator, which applies to the next method definition.
require 'yuuki'
class Greeter < Yuuki::Runner
# registered with the tag :greet (taken from the method name)
def on_greet
puts 'hello!'
end
# registered with the tags :greet and :farewell
on :greet
on :farewell
def say_goodbye
puts 'goodbye!'
end
end
yuuki = Yuuki::Caller.new(Greeter)
yuuki.run(:greet)
# hello!
# goodbye!
yuuki.run(:farewell)
# goodbye!
Yuuki::Caller.new (and #add) accepts runner classes and runner instances. A class is instantiated automatically. Inside a runner method, yuuki returns the caller the instance belongs to.
Passing arguments
Keyword arguments given to run are delivered to each method by parameter name — no matter whether the parameter is positional or keyword. Extra arguments are simply ignored, and a missing required parameter raises Yuuki::Error. A block given to run is forwarded as the block of each method.
class Worker < Yuuki::Runner
def on_process(name, size: 1)
puts "processing #{name} (size: #{size})"
end
end
yuuki = Yuuki::Caller.new(Worker)
yuuki.run(:process, name: 'job', size: 3, unused: 42)
# processing job (size: 3)
Decorators
Decorators are class methods that annotate the next method definition.
| Decorator | Effect |
|---|---|
on :tag |
Adds a tag. Can be specified multiple times. |
priority n |
Runs methods with higher priority first (default 0). Also used as Thread#priority for threaded methods. |
thread |
Runs the method in a new thread. |
periodic interval |
Runs the method every interval seconds (Yuuki::PeriodicCaller only). |
first_run |
Runs the method once on startup (Yuuki::PeriodicCaller only). |
Threading
Methods marked with thread run in their own thread; run returns without waiting for them.
class Background < Yuuki::Runner
on :heavy
thread
def heavy_task
sleep 1
puts 'done'
end
end
yuuki = Yuuki::Caller.new(Background)
yuuki.run(:heavy) # returns immediately
yuuki.running? # => true
yuuki.join # waits for all running threads
Periodic execution
Yuuki::PeriodicCaller runs periodic methods repeatedly and first_run methods once on startup. It is not required by default:
require 'yuuki/periodic_caller'
class Watcher < Yuuki::Runner
first_run
def setup
puts 'starting'
end
periodic 60
def check
puts 'checking...'
end
periodic 86_400
def daily
puts 'a new day has come'
end
end
yuuki = Yuuki::PeriodicCaller.new(Watcher)
yuuki.on_error { |error| warn error. }
yuuki.run # blocks forever
Intervals are aligned to wall-clock boundaries in local time: periodic 60 fires at the top of every minute, and periodic 86_400 fires at local midnight (pass a GMT offset as the first argument of run to change the time zone). Intervals shorter than 1 second are not supported.
An exception raised by a (non-threaded) runner method is passed to the on_error callback and the loop keeps running. Without on_error, the exception stops run.
Loading runner files
Yuuki::Caller.require_dir requires all *.rb files in a directory:
Yuuki::Caller.require_dir('runners')
Yuuki::Caller.require_dir('runners', recursive: true)
Graceful shutdown with Yoshinon
By default, each method invocation is wrapped in a Yoshinon lock so that a trapped signal waits for running methods to finish. Pass use_yoshinon: false to Yuuki::Caller.new to disable this.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ishotihadus/yuuki.
License
The gem is available as open source under the terms of the MIT License.