docs/GettingStarted.textile in amqp-0.8.0.rc13 vs docs/GettingStarted.textile in amqp-0.8.0.rc14
- old
+ new
@@ -1,30 +1,31 @@
-# @title Ruby AMQP gem: Getting Started with AMQP and Ruby
+# @title Ruby amqp gem: Getting Started with AMQP and Ruby
-h1. Getting started with AMQP Ruby gem
+h1. Getting started with the Ruby amqp gem
h2. About this guide
-This guide is a quick tutorial that helps you to get started with AMQP 0.9.1 in general and amqp gem in particular.
-It should take about 20 minutes to read and study provided code examples. This guide covers
+This guide is a quick tutorial that helps you to get started with v0.9.1 of the AMQP specification in general and the "Ruby amqp gem":http://github.com/ruby-amqp/amqp in particular.
+It should take about 20 minutes to read and study the provided code examples. This guide covers:
- * Installing RabbitMQ, a mature popular implementation of multiple versions of AMQP protocol.
- * Installing amqp gem via "Rubygems":http://rubygems.org and "Bundler":http://gembundler.com.
- * Running the "Hello, world" of messaging, a simple demonstration of 1:1 communication.
- * Creating a "Twitter like" publish/subscribe example with 1 publisher and 4 subscribers, a case of 1:n communication.
- * Creating a topic routing example with 2 publishers and 8 subscribers, a case of n:m communication when subscribers only receive messages they are interested in.
+ * Installing RabbitMQ, a mature popular server implementation of the AMQP protocol.
+ * Installing the amqp gem via "Rubygems":http://rubygems.org and "Bundler":http://gembundler.com.
+ * Running a "Hello, world" messaging example that is a simple demonstration of 1:1 communication.
+ * Creating a "Twitter-like" publish/subscribe example with 1 publisher and 4 subscribers that demonstrates 1:n communication.
+ * Creating a topic routing example with 2 publishers and 8 subscribers showcasing n:m communication when subscribers only receive messages that they are interested in.
+ * Learning how the amqp gem can be integrated with Ruby objects in a way that makes unit testing easy.
-h2. Covered versions
+h2. Which versions of the amqp gem does this guide cover?
-This guide covers "Ruby amqp gem":http://github.com/ruby-amqp/amqp v0.8.0 and later.
+This guide covers v0.8.0 and later of the "Ruby amqp gem":https://github.com/ruby-amqp/amqp.
h2. Installing RabbitMQ
-"RabbitMQ site":http://rabbitmq.com has a good "installation guide":http://www.rabbitmq.com/install.html that covers many operating systems.
+The "RabbitMQ site":http://rabbitmq.com has a good "installation guide":http://www.rabbitmq.com/install.html that addresses many operating systems.
On Mac OS X, the fastest way to install RabbitMQ is with "Homebrew":http://mxcl.github.com/homebrew/:
<pre>
<code>
brew install rabbitmq
@@ -37,209 +38,205 @@
<code>
rabbitmq-server
</code>
</pre>
-On Debian and Ubuntu, you can either "download RabbitMQ .deb package":http://www.rabbitmq.com/server.html and install it with
-"dpkg":http://www.debian.org/doc/FAQ/ch-pkgtools.en.html or use "apt repository RabbitMQ team provides":http://www.rabbitmq.com/debian.html#apt.
-For RPM-based distributions like RedHat or CentOS RabbitMQ team provides an "RPM package":http://www.rabbitmq.com/install.html#rpm.
+On Debian and Ubuntu, you can either "download the RabbitMQ .deb package":http://www.rabbitmq.com/server.html and install it with
+"dpkg":http://www.debian.org/doc/FAQ/ch-pkgtools.en.html or make use of the "apt repository":http://www.rabbitmq.com/debian.html#apt that the RabbitMQ team provides.
+For RPM-based distributions like RedHat or CentOS, the RabbitMQ team provides an "RPM package":http://www.rabbitmq.com/install.html#rpm.
<span class="note">
-RabbitMQ package in even recent (10.10) versions of Ubuntu are outdated and *won't work with amqp gem 0.8.0 and later* (we need at least version 2.0).
+The RabbitMQ package that ships with recent Ubuntu 10.10 versions is outdated and *will not work with v0.8.0 and later of the amqp gem* (we need at least RabbitMQ v2.0 for use with this guide).
</span>
-h2. Installing Ruby amqp gem
+h2. Installing the Ruby amqp gem
-h3. Make sure you have Ruby installed
+h3. Make sure that you have Ruby and "Rubygems":http://docs.rubygems.org/read/chapter/3 installed
-This guides assumes you have one of the supported Ruby implementations installed:
+This guide assumes that you have installed one of the following supported Ruby implementations:
- * Ruby 1.8.7
- * Ruby 1.9.2
- * JRuby (we recommend 1.6)
- * Rubinius 1.2 or higher
+ * Ruby v1.8.7
+ * Ruby v1.9.2
+ * JRuby (we recommend v1.6)
+ * Rubinius v1.2 or higher
* Ruby Enterprise Edition
+h3. You can use Rubygems to install the amqp gem
-h3. With Rubygems
+h4. On Microsoft Windows 7:
-To get amqp gem 0.8.0
-
-h4. On Microsoft Windows 7
-
<pre>
gem install eventmachine --pre
-gem install amqp --pre --version "~> 0.8.0.RC12"
+gem install amqp --pre
</pre>
h4. On other OSes or JRuby:
<pre>
-gem install amqp --pre --version "~> 0.8.0.RC12"
+gem install amqp --pre
</pre>
-h3. With Bundler
+h3. You can also use Bundler to install the gem
<pre>
<code>
source :rubygems
-gem "amqp", "~> 0.8.0.RC12" # optionally: :git => "git://github.com/ruby-amqp/amqp.git", :branch => "master"
+gem "amqp", "~> 0.8.0.RC13" # optionally: :git => "git://github.com/ruby-amqp/amqp.git", :branch => "master"
</code>
</pre>
h3. Verifying your installation
-Lets verify your installation with this quick irb session:
+Let us verify your installation with this quick irb session:
<pre>
<code>
irb -rubygems
:001 > require "amqp"
=> true
:002 > AMQP::VERSION
-=> "0.8.0.rc12"
+=> "0.8.0.rc13"
</code>
</pre>
h2. "Hello, world" example
-Lets begin with the classic "Hello, world" example. First, here's the code:
+Let us begin with the classic "Hello, world" example. First, here is the code:
<script src="https://gist.github.com/998690.js"> </script>
(if the example above isn't displayed, see this "gist":https://gist.github.com/998690)
-This example demonstrates a very common communication scenario: app A wants to publish a message that will end up in
-a queue that app B listens on. In this example, queue name is "amqpgem.examples.hello". Lets go through this example
+This example demonstrates a very common communication scenario: *application A* wants to publish a message that will end up in
+a queue that *application B* listens on. In this case, the queue name is "amqpgem.examples.hello". Let us go through the code
step by step:
<pre>
<code>
require "rubygems"
require "amqp"
</code>
</pre>
-is the simplest way to load amqp gem if you have installed it with RubyGems. The following piece of code
+is the simplest way to load the amqp gem if you have installed it with RubyGems. The following piece of code
<pre>
<code>
EventMachine.run do
# ...
end
</code>
</pre>
-runs what is called EventMachine reactor. Without paying much attention to what exactly does reactor mean in this case,
-let us say that amqp gem is asynchronous and is based on an asynchronous network I/O library called "EventMachine":http://rubyeventmachine.com.
+runs what is called the "EventMachine":http://rubyeventmachine.com reactor. We will not go into what the term 'reactor' means here, but suffice it to say
+that the amqp gem is asynchronous and is based on an asynchronous network I/O library called _EventMachine_.
-Next line
+The next line
<pre>
<code>
connection = AMQP.connect(:host => '127.0.0.1')
</code>
</pre>
-connects to the server running on localhost, with default port, username, password and virtual host.
+connects to the server running on localhost, with the default port (5672), username (guest), password (guest) and virtual host ('/').
+The next line
+
<pre>
<code>
channel = AMQP::Channel.new(connection)
</code>
</pre>
-opens the channel. AMQP is a multi-channeled protocol. Channels is a way to multiplex a TCP connection.
-Because channels are open on a connection, AMQP::Channel constructor takes connection object as a parameter.
+opens a new _channel_. AMQP is a multi-channeled protocol that uses channels to multiplex a TCP connection.
+Channels are opened on a connection, therefore the AMQP::Channel constructor takes a connection object as a parameter.
This line
<pre>
<code>
queue = channel.queue("amqpgem.examples.helloworld", :auto_delete => true)
</code>
</pre>
-declares a queue on the channel we've just opened. Queues are where consumer applications get messages from.
-We declare this queue with "auto-delete" parameter. Basically, that means "when there is no one left
-consuming messages from this queue, delete it".
+declares a _queue_ on the channel that we have just opened. Consumer applications get messages from queues.
+We declared this queue with the "auto-delete" parameter. Basically, this means that the queue will be deleted when there are no more processes
+consuming messages from it.
-The next line,
+The next line
<pre>
<code>
exchange = channel.direct("")
</code>
</pre>
-instantiates an exchange. Exchange is where messages are sent by producers. Exchanges route messages to queues
-according to rules called bindings. In this particular example, there are no explicitly defined bindings.
-Exchange we defined is known as default exchange and it has implied binding to all queues. Before we get
-into that, lets see how we define a handler for incoming messages:
+instantiates an _exchange_. Exchanges receive messages that are sent by producers. Exchanges route messages to queues
+according to rules called _bindings_. In this particular example, there are no explicitly defined bindings.
+The exchange that we defined is known as the _default exchange_ and it has implied bindings to all queues. Before we get
+into that, let us see how we define a handler for incoming messages
<pre>
<code>
queue.subscribe do |payload|
puts "Received a message: #{payload}. Disconnecting..."
-
- connection.close {
- EM.stop { exit }
- }
+ connection.close { EventMachine.stop }
end
</code>
</pre>
-{AMQP::Queue#subscribe} takes a block that will be called every time a message arrives. {AMQP::Session#close} closes
-AMQP connection and runs a callback that stops EventMachine reactor.
+{AMQP::Queue#subscribe} takes a block that will be called every time a message arrives. {AMQP::Session#close} closes the
+AMQP connection and runs a callback that stops the EventMachine reactor.
-Finally, we publish our message:
+Finally, we publish our message
<pre>
<code>
exchange.publish "Hello, world!", :routing_key => queue.name
</code>
</pre>
-Routing key is one of _message attributes_. Default exchange will route message to a queue that has the same name
-as message's routing key. This is how our message ends up in amqpgem.examples.helloworld queue.
+Routing key is one of the _message attributes_. The default exchange will route the message to a queue that has the same name
+as the message's routing key. This is how our message ends up in the "amqpgem.examples.helloworld" queue.
-This first example can be modified to use method chaining technique:
+This first example can be modified to use the method chaining technique:
<script src="https://gist.github.com/998691.js"> </script>
-(if the example above isn't displayed, see this "gist":https://gist.github.com/998691)
+This diagram demonstrates the "Hello, world" example data flow:
-With classes and methods introduced in this example, lets move on to a little bit more
-sophisticated one.
+!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/001_hello_world_example_routing.png!
+For the sake of simplicity, both the message producer (App I) and the consumer (App II) are running in the same Ruby process.
+Now let us move on to a little bit more sophisticated example.
-h2. Blabblr: one-to-many publish/subscribe example
-Previous example demonstrated how connection to the broker is made and how to do 1:1 communication
-using default exchange. Now lets take a look at another common scenario: broadcast, or multiple consumers
+h2. Blabblr: one-to-many publish/subscribe (pubsub) example
+
+The previous example demonstrated how a connection to a broker is made and how to do 1:1 communication
+using the default exchange. Now let us take a look at another common scenario: broadcast, or multiple consumers
and one producer.
-A very well know example of broadcast is Twitter: every time a person tweets, followers receive a notification.
+A very well-known broadcast example is Twitter: every time a person tweets, followers receive a notification.
Blabbr, our imaginary information network, models this scenario: every network member has a separate
-queue and publishes blabs to a separate exchange. 3 Blabbr members, Joe, Aaron and Bob, follow official NBA
-account on Blabbr to get updates about what is up in the world of basketball. Here is the code:
+queue and publishes blabs to a separate exchange. 3 Blabbr members, Joe, Aaron and Bob, follow the official NBA
+account on Blabbr to get updates about what is happening in the world of basketball. Here is the code:
<script src="https://gist.github.com/998692.js"> </script>
-(if the example above isn't displayed, see this "gist":https://gist.github.com/998692)
+The first line has a few differences from the "Hello, world" example above:
-First line has a few difference from "Hello, world" example above:
-
* We use {AMQP.start} instead of {AMQP.connect}
- * Instead of return values, we pass connection method a block and it yields connection
- object back as soon as connection is established.
- * Instead of passing connection parameters as a hash, we used a URI string.
+ * Instead of return values, we pass a block to the connection method and it yields a connection
+ object back as soon as the connection is established.
+ * Instead of passing connection parameters as a hash, we use a URI string.
{AMQP.start} is just a convenient way to do
<pre>
<code>
@@ -249,28 +246,28 @@
end
end
</code>
</pre>
-{AMQP.start} call blocks current thread so it's use is limited to scripts and small command
+The {AMQP.start} call blocks the current thread which means that its use is limited to scripts and small command
line applications. Blabbr is just that.
-{AMQP.connect}, when invoked with a block, will yield connection object to it as soon as AMQP connection
-is open. Finally, connection parameters maybe given as a Hash or as a connection string. {AMQP.connect}
-method documentation has all the details.
+{AMQP.connect}, when invoked with a block, will yield a connection object as soon as the AMQP connection
+is open. Finally, connection parameters may be supplied as a Hash or as a connection string. The {AMQP.connect}
+method documentation contains all of the details.
-Opening a channel in this example is no different from opening a channel in the example before that,
-but exchange is instantiated differently:
+In this example, opening a channel is no different to opening a channel in the previous example,
+however, the exchange is declared differently
<pre>
<code>
exchange = channel.fanout("nba.scores")
</code>
</pre>
-Exchange we declare above using {AMQP::Channel#fanout} is a _fanout exchange_. Fanout exchanges deliver messages to every queue that
-was bound to it: exactly what we want in case of Blabbr!
+The exchange that we declare above using {AMQP::Channel#fanout} is a _fanout exchange_. A fanout exchange delivers messages to all of the queues that
+ are bound to it: exactly what we want in the case of Blabbr!
This piece of code
<pre>
<code>
@@ -278,146 +275,153 @@
puts "#{payload} => joe"
end
</code>
</pre>
-is similar to how we subscribed for message delivery before, but what does that {AMQP::Queue#bind}
-method do? It sets up a _binding_ between the queue and an exchange you pass to it. We need to do this
-to make sure that our fanout exchange routes messages to follower queues.
+is similar to the subscription code that we used for message delivery previously, but what does that {AMQP::Queue#bind}
+method do? It sets up a binding between the queue and the exchange that you pass to it. We need to do this
+to make sure that our fanout exchange routes messages to the queues of any subscribed followers.
<pre>
<code>
exchange.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88")
</code>
</pre>
-demonstrates {AMQP::Exchange#publish} calls chaining. Because Blabbr members use fanout exchange
-for publishing, there is no need to specify routing key: every queue that was bound to exchange receiving
-a message will get it's own message copy, regardless of queue name and routing key used.
+demonstrates {AMQP::Exchange#publish} call chaining. Blabbr members use a fanout exchange
+for publishing, so there is no need to specify a message routing key because every queue that is bound to the exchange will get its own copy of all messages, regardless of the queue name and routing key used.
+A diagram for Blabbr looks like this:
+
+!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/002_blabbr_example_routing.png!
+
+
Next we use EventMachine's {http://eventmachine.rubyforge.org/EventMachine.html#M000466 add_timer} method to
run a piece of code in 1 second from now:
<pre>
<code>
EventMachine.add_timer(1) do
exchange.delete
- connection.close {
- EM.stop { exit }
- }
+ connection.close { EventMachine.stop }
end
</code>
</pre>
-The code we want to run deletes exchange we declared earlier using {AMQP::Exchange#delete} and closes AMQP
-connection with {AMQP::Session#close}. Finally, we stop EventMachine event loop and exit.
+The code that we want to run deletes the exchange that we declared earlier using {AMQP::Exchange#delete} and closes the AMQP
+connection with {AMQP::Session#close}. Finally, we stop the EventMachine event loop and exit.
-Blabbr is pretty unlikely to secure hundreds of millions in funding but it does a pretty good job of
+Blabbr is pretty unlikely to secure hundreds of millions of dollars in funding, but it does a pretty good job of
demonstrating how one can use AMQP fanout exchanges to do broadcasting.
h2. Weathr: many-to-many topic routing example
-So far we have seen point-to-point communication and broadcast. These two are possible with many protocols:
-HTTP handles these scenarios just fine. What differentiates AMQP? Next we are going to introduce you to topic
-exchanges and routing with patterns, one of the features that makes AMQP very powerful.
+So far, we have seen point-to-point communication and broadcasting. Those two communication styles are possible with many protocols, for instance,
+HTTP handles these scenarios just fine. You may ask "what differentiates AMQP?" Well, next we are going to introduce you to _topic
+exchanges_ and routing with patterns, one of the features that makes AMQP very powerful.
-Our third example is weather condition updates. What makes it different from the previous two is that
-not all consumers are interested in all messages: people who live in Portland usually don't care about
-weather in Hong Kong very much (unless they are going there soon). They are certainly interested in
+Our third example involves weather condition updates. What makes it different from the previous two examples is that
+not all of the consumers are interested in all of the messages. People who live in Portland usually do not care about
+the weather in Hong Kong (unless they are visiting soon). They are much more interested in
weather conditions around Portland, possibly all of Oregon and sometimes a few neighbouring states.
Our example features multiple consumer applications monitoring updates for different regions. Some are
-interested in updates for a specific city, others for a specific state and so on all the way up to continents.
-Updates may overlap: an update for San Diego, CA _is_ an update for California, and should certainly show up
-on North America updates list.
+interested in updates for a specific city, others for a specific state and so on, all the way up to continents.
+Updates may overlap so that an update for San Diego, CA appears as an update for California, but also should show up
+on the North America updates list.
Here is the code:
<script src="https://gist.github.com/998694.js"> </script>
-(if the example above isn't displayed, see this "gist":https://gist.github.com/998694)
+The first line that is different from the Blabbr example is
-First line that is different from Blabbr example is
-
<pre>
<code>
exchange = channel.topic("pub/sub", :auto_delete => true)
</code>
</pre>
-We use a _topic exchange_ here. Topic exchanges are used for "multicast":http://en.wikipedia.org/wiki/Multicast messaging
-where consumers indicate what topics they are interested in (think of it as of subscribing to a feed for individual tag
-of your favourite blog as opposed to full feed). They do it by specifying _routing pattern_ on binding, for example:
+We use a topic exchange here. Topic exchanges are used for "multicast":http://en.wikipedia.org/wiki/Multicast messaging
+where consumers indicate which topics they are interested in (think of it as subscribing to a feed for an individual tag
+in your favourite blog as opposed to the full feed). Routing with a topic exchange is done by specifying a _routing pattern_ on binding, for example:
<pre>
<code>
channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |headers, payload|
puts "An update for South America: #{payload}, routing key is #{headers.routing_key}"
end
</code>
</pre>
-Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using {AMQP::Queue#bind} method.
-This means that only messages with routing key matching americas.south.# will be routed to that queue. Routing pattern consists of several words
-separated by dots, similarly to URI path segments joined by slash. A few of examples:
+Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using the {AMQP::Queue#bind} method.
+This means that only messages with a routing key matching "americas.south.#" will be routed to that queue. A routing pattern consists of several words
+separated by dots, in a similar way to URI path segments joined by slashes. Here are a few examples:
* asia.southeast.thailand.bangkok
* sports.basketball
* usa.nasdaq.aapl
* tasks.search.indexing.accounts
-Now lets take a look at a few routing keys that do match "americas.south.#" pattern:
+Now let us take a look at a few routing keys that match the "americas.south.#" pattern:
* americas.south
* americas.south.*brazil*
* americas.south.*brazil.saopaolo*
* americas.south.*chile.santiago*
-In other words, # part of the pattern matches 0 or more words. For "americas.south.*", some of matching routing keys are
+In other words, the "#" part of the pattern matches 0 or more words.
+For a pattern like "americas.south.*", some matching routing keys would be:
+
* americas.south.*brazil*
* americas.south.*chile*
* americas.south.*peru*
but not
* americas.south
* americas.south.chile.santiago
-so * matches a single word, whatever it is. AMQP 0.9.1 spec says that topic segments (words) may contain the letters A-Z and a-z
+so "*" only matches a single word. The AMQP v0.9.1 specification says that topic segments (words) may contain the letters A-Z and a-z
and digits 0-9.
+A (very simplistic) diagram to demonstrate topic exchange in action:
+
+!https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/003_weathr_example_routing.png!
+
+
One more thing that is different from previous examples is that the block we pass to {AMQP::Queue#subscribe} now takes two arguments:
-header and body (aka payload). Long story short, the _header_ parameter lets you access metadata associated with the message. Some
-examples of message metadata attributes are
+a _header_ and a _body_ (often called the _payload_). Long story short, the header parameter lets you access metadata associated with the message. Some
+examples of message metadata attributes are:
* message content type
* message content encoding
* message priority
* message expiration time
* message identifier
- * reply to, to what message this message is a reply to
- * application id, identifier of application that produced the message
+ * reply to (specifies which message this is a reply to)
+ * application id (identifier of the application that produced the message)
and so on.
-As this binding demonstrates, # (and *) can appear in the beginning of routing patterns, too:
+As the following binding demonstrates, "#" and "*" can also appear at the beginning of routing patterns:
<pre>
<code>
channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |headers, payload|
puts "An update for Austin, TX: #{payload}, routing key is #{headers.routing_key}"
end
</code>
</pre>
-Publishing of messages is not different from previous examples. Running this example demonstrates that, for example,
-message published with routing key of "americas.north.us.ca.berkeley" is routed to several queues: us.california and
-_server-named queue_ we declared by passing blank string as the name:
+For this example the publishing of messages is no different from that of previous examples. If we were to run the program,
+a message published with a routing key of "americas.north.us.ca.berkeley" would be routed to 2 queues: "us.california" and the
+_server-named queue_ that we declared by passing a blank string as the name:
<pre>
<code>
channel.queue("", :exclusive => true) do |queue|
queue.bind(exchange, :routing_key => "americas.north.#").subscribe do |headers, payload|
@@ -425,52 +429,183 @@
end
end
</code>
</pre>
-Name of server-named queue is generated by the broker and sent back to the client with queue declaration confirmation.
-Because of queue name is not known before reply arrives, we passed {AMQP::Channel#queue} a callback and it yielded us back
+The name of the server-named queue is generated by the broker and sent back to the client with a queue declaration confirmation.
+Because the queue name is not known before the reply arrives, we pass {AMQP::Channel#queue} a callback and it yields us back
a queue object once confirmation has arrived.
h3. Avoid race conditions
A word of warning: you may find examples on the Web of {AMQP::Channel#queue} usage that do not use
-callback: we *strongly recommend you always use a callback for server-named queues*. Otherwise your code may be a subject
-to "race conditions":http://en.wikipedia.org/wiki/Race_condition and even though amqp gem tries to be reasonably smart and protect you from most common problems, there
-is no way it can do so for every case. The only reason we support {AMQP::Channel#queue} usage w/o a callback for server-named queues is
+callbacks. We *recommend that you use a callback for server-named queues*, otherwise your code may be subject
+to "race conditions":http://en.wikipedia.org/wiki/Race_condition. Even though the amqp gem tries to be reasonably smart and protect you from most common problems
+(for example, binding operations will be delayed until after queue name is received from the broker), there
+is no way it can do so for every case. The primary reason for supporting {AMQP::Channel#queue} usage without a callback for server-named queues is
backwards compatibility with earlier versions.
+
+h2. Integration with objects
+
+Since Ruby is a genuine object-oriented language, it is important to demonstrate how the Ruby amqp gem can be integrated
+into rich object-oriented code.
+
+The {AMQP::Queue#subscribe} callback does not have to be a block. It can be any Ruby object that responds to `call`.
+A common technique is to combine {http://rubydoc.info/stdlib/core/1.8.7/Object:method Object#method} and {http://rubydoc.info/stdlib/core/1.8.7/Method:to_proc Method#to_proc}
+and use object methods as message handlers.
+
+An example to demonstrate this technique:
+
+<pre>
+<code>
+class Consumer
+
+ #
+ # API
+ #
+
+ def handle_message(metadata, payload)
+ puts "Received a message: #{payload}, content_type = #{metadata.content_type}"
+ end # handle_message(metadata, payload)
+end
+
+
+class Worker
+
+ #
+ # API
+ #
+
+
+ def initialize(channel, queue_name = AMQ::Protocol::EMPTY_STRING, consumer = Consumer.new)
+ @queue_name = queue_name
+
+ @channel = channel
+ @channel.on_error(&method(:handle_channel_exception))
+
+ @consumer = consumer
+ end # initialize
+
+ def start
+ @queue = @channel.queue(@queue_name, :exclusive => true)
+ @queue.subscribe(&@consumer.method(:handle_message))
+ end # start
+
+
+
+ #
+ # Implementation
+ #
+
+ def handle_channel_exception(channel, channel_close)
+ puts "Oops... a channel-level exception: code = #{channel_close.reply_code}, message = #{channel_close.reply_text}"
+ end # handle_channel_exception(channel, channel_close)
+end
+</code>
+</pre>
+
+The "Hello, world" example can be ported to use this technique:
+<script src="https://gist.github.com/1009447.js"> </script>
+
+The most important line in this example is
+
+<pre>
+<code>
+@queue.subscribe(&@consumer.method(:handle_message))
+</code>
+</pre>
+
+Ampersand (&) preceding an object is equivalent to calling the #to_proc method on it. We obtain a Consumer#handle_message method reference
+with
+
+<pre>
+<code>
+@consumer.method(:handle_message)
+</code>
+</pre>
+
+and then the ampersand calls #to_proc on it. {AMQP::Queue#subscribe} then will be using this Proc instance to handle incoming messages.
+
+
+Note that the `Consumer` class above can be easily tested in isolation, without spinning up any AMQP connections.
+Here is one example using "RSpec":http://relishapp.com/rspec
+
+<pre>
+<code>
+require "ostruct"
+require "json"
+
+# RSpec example
+describe Consumer do
+ describe "when a new message arrives" do
+ subject { described_class.new }
+
+ let(:metadata) do
+ o = OpenStruct.new
+
+ o.content_type = "application/json"
+ o
+ end
+ let(:payload) { JSON.encode({ :command => "reload_config" }) }
+
+ it "does some useful work" do
+ # check preconditions here if necessary
+
+ subject.handle_message(metadata, payload)
+
+ # add your code expectations here
+ end
+ end
+end
+</code>
+</pre>
+
+
h2. Wrapping up
-This tutorial ends here. Congratulations! You have learned quite a bit about both AMQP 0.9.1 and amqp gem.
+This is the end of the tutorial. Congratulations! You have learned quite a bit about both AMQP v0.9.1 and the amqp gem. This is only
+the tip of the iceberg. AMQP has many more features built into the protocol:
+ * Reliable delivery of messages
+ * Message confirmations (a way to tell broker that a message was or was not processed successfully)
+ * Message redelivery when consumer applications fail or crash
+ * Load balancing of messages between multiple consumers
+ * Message metadata attributes
+and so on. Other guides explain these features in depth, as well as use cases for them. To stay up to date with amqp gem
+development, "follow @rubyamqp on Twitter":http://twitter.com/rubyamqp and "join our mailing list":http://groups.google.com/group/ruby-amqp.
+
+
+
+
h2. What to read next
Documentation is organized as a number of {file:docs/DocumentationGuidesIndex.textile documentation guides}, covering all kinds of
-topics from {file:docs/Routing.textile routing} to {file:docs/ErrorHandling.textile error handling} to
+topics from {file:docs/Exchanges.textile use cases for various exchange types} to {file:docs/ErrorHandling.textile error handling} and
{file:docs/VendorSpecificExchanges.textile Broker-specific AMQP 0.9.1 extensions}.
-To learn more on what you have seen in this tutorial, check out
+We recommend that you read the following guides next, if possible, in this order:
- * {file:docs/ConnectingToTheBroker.textile Connection to the broker}
- * {file:docs/Queues.textile Queues}
- * {file:docs/Exchanges.textile Exchanges}
- * {file:docs/Bindings.textile Bindings}
+ * {file:docs/ConnectingToTheBroker.textile Connection to the broker}. This guide explains how to connect to an AMQP broker and how to integrate the amqp gem into standalone and Web applications.
+ * {file:docs/Queues.textile Working With Queues}. This guide focuses on features that consumer applications use heavily.
+ * {file:docs/Exchanges.textile Working With Exchanges}. This guide focuses on features that producer applications use heavily.
+ * {file:docs/PatternsAndUseCases.textile Patterns & Use Cases}. This guide focuses implementation of "common messaging patterns":http://www.eaipatterns.com/ using AMQP Model features as building blocks.
+ * {file:docs/ErrorHandling.textile Error Handling & Recovery}. This guide explains how to handle protocol errors, network failures and other things that may go wrong in real world projects.
-If you are migrating your application from earlier versions of amqp gem (0.6.x and 0.7.x), to 0.8.x and later, there is
+If you are migrating your application from earlier versions of the amqp gem (0.6.x and 0.7.x), to 0.8.x and later, there is the
{file:docs/08Migration.textile amqp gem 0.8 migration guide}.
h2. Tell us what you think!
-Please take a moment and tell us what you think about this guide on "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp:
-what was unclear? what wasn't covered? maybe you don't like guide style or grammar and spelling are incorrect? Readers feedback is
-key to making documentation better.
+Please take a moment to tell us what you think about this guide "on Twitter":http://twitter.com/rubyamqp or the "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp.
+ Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is
+key to making the documentation better.
-If mailing list communication is not an option for you for some reason, you can "contact guides author directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
+If, for some reason, you cannot use the communication channels mentioned above, you can "contact the author of the guides directly":mailto:michael@novemberain.com?subject=amqp%20gem%20documentation
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES * * */