The Forwardable
module provides delegation of specified methods to a designated object, using the methods def_delegator
and def_delegators
.
For example, say you have a class RecordCollection which contains an array @records
. You could provide the lookup method record_number(), which simply calls [] on the @records
array, like this:
require 'forwardable' class RecordCollection attr_accessor :records extend Forwardable def_delegator :@records, :[], :record_number end
We can use the lookup method like so:
r = RecordCollection.new r.records = [4,5,6] r.record_number(0) # => 4
Further, if you wish to provide the methods size, <<, and map, all of which delegate to @records, this is how you can do it:
class RecordCollection # re-open RecordCollection class def_delegators :@records, :size, :<<, :map end r = RecordCollection.new r.records = [1,2,3] r.record_number(0) # => 1 r.size # => 3 r << 4 # => [1, 2, 3, 4] r.map { |x| x * 2 } # => [2, 4, 6, 8]
You can even extend regular objects with Forwardable
.
my_hash = Hash.new my_hash.extend Forwardable # prepare object for delegation my_hash.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts() my_hash.puts "Howdy!"
We want to rely on what has come before obviously, but with delegation we can take just the methods we need and even rename them as appropriate. In many cases this is preferable to inheritance, which gives us the entire old interface, even if much of it isn’t needed.
class Queue extend Forwardable def initialize @q = [ ] # prepare delegate object end # setup preferred interface, enq() and deq()... def_delegator :@q, :push, :enq def_delegator :@q, :shift, :deq # support some general Array methods that fit Queues well def_delegators :@q, :clear, :first, :push, :shift, :size end q = Queue.new q.enq 1, 2, 3, 4, 5 q.push 6 q.shift # => 1 while q.size > 0 puts q.deq end q.enq "Ruby", "Perl", "Python" puts q.first q.clear puts q.first
This should output:
2 3 4 5 6 Ruby nil
Be advised, RDoc
will not detect delegated methods.
forwardable.rb
provides single-method delegation via the def_delegator
and def_delegators
methods. For full-class delegation via DelegateClass, see delegate.rb
.
SingleForwardable
can be used to setup delegation at the object level as well.
printer = String.new printer.extend SingleForwardable # prepare object for delegation printer.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts() printer.puts "Howdy!"
Also, SingleForwardable
can be used to set up delegation for a Class
or Module
.
class Implementation def self.service puts "serviced!" end end module Facade extend SingleForwardable def_delegator :Implementation, :service end Facade.service #=> serviced!
If you want to use both Forwardable
and SingleForwardable
, you can use methods def_instance_delegator and def_single_delegator
, etc.
In concurrent programming, a monitor is an object or module intended to be used safely by more than one thread. The defining characteristic of a monitor is that its methods are executed with mutual exclusion. That is, at each point in time, at most one thread may be executing any of its methods. This mutual exclusion greatly simplifies reasoning about the implementation of monitors compared to reasoning about parallel code that updates a data structure.
You can read more about the general principles on the Wikipedia page for Monitors
require 'monitor.rb' buf = [] buf.extend(MonitorMixin) empty_cond = buf.new_cond # consumer Thread.start do loop do buf.synchronize do empty_cond.wait_while { buf.empty? } print buf.shift end end end # producer while line = ARGF.gets buf.synchronize do buf.push(line) empty_cond.signal end end
The consumer thread waits for the producer thread to push a line to buf while buf.empty?
. The producer thread (main thread) reads a line from ARGF and pushes it into buf then calls empty_cond.signal
to notify the consumer thread of new data.
Class
include require 'monitor' class SynchronizedArray < Array include MonitorMixin def initialize(*args) super(*args) end alias :old_shift :shift alias :old_unshift :unshift def shift(n=1) self.synchronize do self.old_shift(n) end end def unshift(item) self.synchronize do self.old_unshift(item) end end # other methods ... end
SynchronizedArray
implements an Array with synchronized access to items. This Class
is implemented as subclass of Array which includes the MonitorMixin
module.
OpenURI
is an easy-to-use wrapper for Net::HTTP
, Net::HTTPS and Net::FTP
.
It is possible to open an http, https or ftp URL as though it were a file:
open("http://www.ruby-lang.org/") {|f| f.each_line {|line| p line} }
The opened file has several getter methods for its meta-information, as follows, since it is extended by OpenURI::Meta
.
open("http://www.ruby-lang.org/en") {|f| f.each_line {|line| p line} p f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/> p f.content_type # "text/html" p f.charset # "iso-8859-1" p f.content_encoding # [] p f.last_modified # Thu Dec 05 02:45:02 UTC 2002 }
Additional header fields can be specified by an optional hash argument.
open("http://www.ruby-lang.org/en/", "User-Agent" => "Ruby/#{RUBY_VERSION}", "From" => "foo@bar.invalid", "Referer" => "http://www.ruby-lang.org/") {|f| # ... }
The environment variables such as http_proxy, https_proxy and ftp_proxy are in effect by default. Here we disable proxy:
open("http://www.ruby-lang.org/en/", :proxy => nil) {|f| # ... }
See OpenURI::OpenRead.open
and Kernel#open
for more on available options.
URI
objects can be opened in a similar way.
uri = URI.parse("http://www.ruby-lang.org/en/") uri.open {|f| # ... }
URI
objects can be read directly. The returned string is also extended by OpenURI::Meta
.
str = uri.read p str.base_uri
Tanaka Akira <akr@m17n.org>
Open3
grants you access to stdin, stdout, stderr and a thread to wait for the child process when running another program. You can specify various attributes, redirections, current directory, etc., of the program in the same way as for Process.spawn
.
Open3.popen3
: pipes for stdin, stdout, stderr
Open3.popen2
: pipes for stdin, stdout
Open3.popen2e
: pipes for stdin, merged stdout and stderr
Open3.capture3
: give a string for stdin; get strings for stdout, stderr
Open3.capture2
: give a string for stdin; get a string for stdout
Open3.capture2e
: give a string for stdin; get a string for merged stdout and stderr
Open3.pipeline_rw
: pipes for first stdin and last stdout of a pipeline
Open3.pipeline_r
: pipe for last stdout of a pipeline
Open3.pipeline_w
: pipe for first stdin of a pipeline
Open3.pipeline_start
: run a pipeline without waiting
Open3.pipeline
: run a pipeline and wait for its completion
Profile provides a way to Profile your Ruby application.
Profiling your program is a way of determining which methods are called and how long each method takes to complete. This way you can detect which methods are possible bottlenecks.
Profiling your program will slow down your execution time considerably, so activate it only when you need it. Don’t confuse benchmarking with profiling.
There are two ways to activate Profiling:
Run your Ruby script with -rprofile
:
ruby -rprofile example.rb
If you’re profiling an executable in your $PATH
you can use ruby -S
:
ruby -rprofile -S some_executable
Just require ‘profile’:
require 'profile' def slow_method 5000.times do 9999999999999999*999999999 end end def fast_method 5000.times do 9999999999999999+999999999 end end slow_method fast_method
The output in both cases is a report when the execution is over:
ruby -rprofile example.rb % cumulative self self total time seconds seconds calls ms/call ms/call name 68.42 0.13 0.13 2 65.00 95.00 Integer#times 15.79 0.16 0.03 5000 0.01 0.01 Fixnum#* 15.79 0.19 0.03 5000 0.01 0.01 Fixnum#+ 0.00 0.19 0.00 2 0.00 0.00 IO#set_encoding 0.00 0.19 0.00 1 0.00 100.00 Object#slow_method 0.00 0.19 0.00 2 0.00 0.00 Module#method_added 0.00 0.19 0.00 1 0.00 90.00 Object#fast_method 0.00 0.19 0.00 1 0.00 190.00 #toplevel
RSS
reading and writing Really Simple Syndication (RSS
) is a family of formats that describe ‘feeds,’ specially constructed XML
documents that allow an interested person to subscribe and receive updates from a particular web service. This portion of the standard library provides tooling to read and create these feeds.
The standard library supports RSS
0.91, 1.0, 2.0, and Atom
, a related format. Here are some links to the standards documents for these formats:
RSS
If you’d like to read someone’s RSS
feed with your Ruby code, you’ve come to the right place. It’s really easy to do this, but we’ll need the help of open-uri:
require 'rss' require 'open-uri' url = 'http://www.ruby-lang.org/en/feeds/news.rss' open(url) do |rss| feed = RSS::Parser.parse(rss) puts "Title: #{feed.channel.title}" feed.items.each do |item| puts "Item: #{item.title}" end end
As you can see, the workhorse is RSS::Parser#parse, which takes the source of the feed and a parameter that performs validation on the feed. We get back an object that has all of the data from our feed, accessible through methods. This example shows getting the title out of the channel element, and looping through the list of items.
RSS
Producing our own RSS
feeds is easy as well. Let’s make a very basic feed:
require "rss" rss = RSS::Maker.make("atom") do |maker| maker.channel.author = "matz" maker.channel.updated = Time.now.to_s maker.channel.about = "http://www.ruby-lang.org/en/feeds/news.rss" maker.channel.title = "Example Feed" maker.items.new_item do |item| item.link = "http://www.ruby-lang.org/en/news/2010/12/25/ruby-1-9-2-p136-is-released/" item.title = "Ruby 1.9.2-p136 is released" item.updated = Time.now.to_s end end puts rss
As you can see, this is a very Builder-like DSL. This code will spit out an Atom
feed with one item. If we needed a second item, we’d make another block with maker.items.new_item and build a second one.
Copyright © 2003-2007 Kouhei Sutou <kou@cozmixng.org>
You can redistribute it and/or modify it under the same terms as Ruby.
There is an additional tutorial by the author of RSS
at: www.cozmixng.org/~rwiki/?cmd=view;name=RSS+Parser%3A%3ATutorial.en
The Singleton
module implements the Singleton
pattern.
To use Singleton
, include the module in your class.
class Klass include Singleton # ... end
This ensures that only one instance of Klass can be created.
a,b = Klass.instance, Klass.instance a == b # => true Klass.new # => NoMethodError - new is private ...
The instance is created at upon the first call of Klass.instance().
class OtherKlass include Singleton # ... end ObjectSpace.each_object(OtherKlass){} # => 0 OtherKlass.instance ObjectSpace.each_object(OtherKlass){} # => 1
This behavior is preserved under inheritance and cloning.
This above is achieved by:
Making Klass.new and Klass.allocate private.
Overriding Klass.inherited(sub_klass) and Klass.clone() to ensure that the Singleton
properties are kept when inherited and cloned.
Providing the Klass.instance() method that returns the same object each time it is called.
Overriding Klass._load(str) to call Klass.instance().
Overriding Klass#clone and Klass#dup to raise TypeErrors to prevent cloning or duping.
Singleton
and Marshal
By default Singleton’s _dump(depth)
returns the empty string. Marshalling by default will strip state information, e.g. instance variables and taint state, from the instance. Classes using Singleton
can provide custom _load(str) and _dump(depth) methods to retain some of the previous state of the instance.
require 'singleton' class Example include Singleton attr_accessor :keep, :strip def _dump(depth) # this strips the @strip information from the instance Marshal.dump(@keep, depth) end def self._load(str) instance.keep = Marshal.load(str) instance end end a = Example.instance a.keep = "keep this" a.strip = "get rid of this" a.taint stored_state = Marshal.dump(a) a.keep = nil a.strip = nil b = Marshal.load(stored_state) p a == b # => true p a.keep # => "keep this" p a.strip # => nil
Timeout
long-running blocks
require 'timeout' status = Timeout::timeout(5) { # Something that should be interrupted if it takes more than 5 seconds... }
Timeout
provides a way to auto-terminate a potentially long-running operation if it hasn’t finished in a fixed amount of time.
Previous versions didn’t use a module for namespacing, however timeout
is provided for backwards compatibility. You should prefer Timeout#timeout
instead.
© 2000 Network Applied Communication Laboratory, Inc.
© 2000 Information-technology Promotion Agency, Japan
Error
raised when a response from the server is non-parseable.
Configuration options for dumping YAML.
This API is experimental, and subject to change.
parser = PullParser.new( "<a>text<b att='val'/>txet</a>" ) while parser.has_next? res = parser.next puts res[1]['att'] if res.start_tag? and res[0] == 'b' end
See the PullEvent
class for information on the content of the results. The data is identical to the arguments passed for the various events to the StreamListener
API.
Notice that:
parser = PullParser.new( "<a>BAD DOCUMENT" ) while parser.has_next? res = parser.next raise res[1] if res.error? end
Nat Price gave me some good ideas for the API.
You don’t want to use this class. Really. Use XPath
, which is a wrapper for this class. Believe me. You don’t want to poke around in here. There is strange, dark magic at work in this code. Beware. Go back! Go back while you still can!
See Net::HTTPGenericRequest
for attributes and methods.
This API is experimental, and subject to change.
parser = PullParser.new( "<a>text<b att='val'/>txet</a>" ) while parser.has_next? res = parser.next puts res[1]['att'] if res.start_tag? and res[0] == 'b' end
See the PullEvent
class for information on the content of the results. The data is identical to the arguments passed for the various events to the StreamListener
API.
Notice that:
parser = PullParser.new( "<a>BAD DOCUMENT" ) while parser.has_next? res = parser.next raise res[1] if res.error? end
Nat Price gave me some good ideas for the API.