[ap4r-devel] [253] branches/200709_gihyo/async_shop/as_rails: Experimental implementation for sync sample application.

kato-k at rubyforge.org kato-k at rubyforge.org
Thu Aug 23 09:27:19 EDT 2007


Revision: 253
Author:   kato-k
Date:     2007-08-23 09:27:18 -0400 (Thu, 23 Aug 2007)

Log Message:
-----------
Experimental implementation for sync sample application.

Added Paths:
-----------
    branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb
    branches/200709_gihyo/async_shop/as_rails/app/helpers/async_shop_helper.rb
    branches/200709_gihyo/async_shop/as_rails/app/models/order.rb
    branches/200709_gihyo/async_shop/as_rails/app/models/payment.rb
    branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/
    branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/_form.rhtml
    branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/list.rhtml
    branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/new.rhtml
    branches/200709_gihyo/async_shop/as_rails/db/migrate/
    branches/200709_gihyo/async_shop/as_rails/db/migrate/001_create_orders.rb
    branches/200709_gihyo/async_shop/as_rails/db/migrate/002_create_payments.rb
    branches/200709_gihyo/async_shop/as_rails/test/fixtures/orders.yml
    branches/200709_gihyo/async_shop/as_rails/test/fixtures/payments.yml
    branches/200709_gihyo/async_shop/as_rails/test/functional/async_shop_controller_test.rb
    branches/200709_gihyo/async_shop/as_rails/test/unit/order_test.rb
    branches/200709_gihyo/async_shop/as_rails/test/unit/payment_test.rb
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb

Removed Paths:
-------------
    branches/200709_gihyo/async_shop/as_rails/log/development.log

Added: branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,42 @@
+class AsyncShopController < ApplicationController
+
+  def index
+    list
+    render :action => 'list'
+  end
+
+  def list
+    @order_pages, @orders = paginate :orders, :per_page => 10
+  end
+
+  def new
+    @order = Order.new
+  end
+
+  def order
+    begin
+      @order = Order.new(params[:order])
+      @order.save
+      payment(@order[:id])
+
+      flash[:notice] = 'Order was successfully created.'
+      redirect_to :action => 'list'
+    rescue Error
+      render :action => 'new'
+    end
+  end
+
+  def payment(order_id)
+    sleep 5
+
+    payment = Payment.new
+    payment.order_id = order_id
+    payment.save
+  end
+
+  def destroy
+    Order.find(params[:id]).destroy
+    redirect_to :action => 'list'
+  end
+
+end

Added: branches/200709_gihyo/async_shop/as_rails/app/helpers/async_shop_helper.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/helpers/async_shop_helper.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/app/helpers/async_shop_helper.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,2 @@
+module AsyncShopHelper
+end

Added: branches/200709_gihyo/async_shop/as_rails/app/models/order.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/models/order.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/app/models/order.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,2 @@
+class Order < ActiveRecord::Base
+end

Added: branches/200709_gihyo/async_shop/as_rails/app/models/payment.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/models/payment.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/app/models/payment.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,2 @@
+class Payment < ActiveRecord::Base
+end

Added: branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/_form.rhtml
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/_form.rhtml	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/_form.rhtml	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,6 @@
+<%= error_messages_for 'order' %>
+
+<!--[form:order]-->
+<P><label for="order_item">Item</label><br/>
+<%= text_field 'order', 'item'  %></p>
+<!--[eoform:order]-->

Added: branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/list.rhtml
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/list.rhtml	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/list.rhtml	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,25 @@
+<h1>Listing orders</h1>
+
+<table>
+  <tr>
+    <th>Item</th>
+    <th>Ordered at</th>
+    <th>Payed at<th>
+  </tr>
+  
+<% for order in @orders %>
+  <tr>
+    <td><%=h order.item %></td>
+    <td><%=h order.created_at %></td>
+    <td><%=h Payment.find(order.id).created_at %></td>
+    <td><%= link_to 'Destroy', { :action => 'destroy', :id => order }, :confirm => 'Are you sure?', :method => :post %></td>
+  </tr>
+<% end %>
+</table>
+
+<%= link_to 'Previous page', { :page => @order_pages.current.previous } if @order_pages.current.previous %>
+<%= link_to 'Next page', { :page => @order_pages.current.next } if @order_pages.current.next %> 
+
+<br />
+
+<%= link_to 'New order', :action => 'new' %>

Added: branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/new.rhtml
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/new.rhtml	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/app/views/async_shop/new.rhtml	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,8 @@
+<h1>New order</h1>
+
+<% form_tag :action => 'order' do %>
+  <%= render :partial => 'form' %>
+  <%= submit_tag "Order" %>
+<% end %>
+
+<%= link_to 'ordered list', :action => 'list' %>

Added: branches/200709_gihyo/async_shop/as_rails/db/migrate/001_create_orders.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/db/migrate/001_create_orders.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/db/migrate/001_create_orders.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,13 @@
+class CreateOrders < ActiveRecord::Migration
+  def self.up
+    create_table :orders do |t|
+      t.column :customer_id, :integer
+      t.column :item, :string
+      t.column :created_at, :datetime
+    end
+  end
+
+  def self.down
+    drop_table :orders
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/db/migrate/002_create_payments.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/db/migrate/002_create_payments.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/db/migrate/002_create_payments.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,12 @@
+class CreatePayments < ActiveRecord::Migration
+  def self.up
+    create_table :payments do |t|
+      t.column :order_id, :integer
+      t.column :created_at, :datetime
+    end
+  end
+
+  def self.down
+    drop_table :payments
+  end
+end

Deleted: branches/200709_gihyo/async_shop/as_rails/log/development.log
===================================================================

Added: branches/200709_gihyo/async_shop/as_rails/test/fixtures/orders.yml
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/test/fixtures/orders.yml	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/test/fixtures/orders.yml	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,11 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  customer_id: 1
+  item: MyString
+  orderd_at: 2007-08-23
+two:
+  id: 2
+  customer_id: 1
+  item: MyString
+  orderd_at: 2007-08-23

Added: branches/200709_gihyo/async_shop/as_rails/test/fixtures/payments.yml
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/test/fixtures/payments.yml	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/test/fixtures/payments.yml	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,9 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+  id: 1
+  order_id: 1
+  payed_at: 2007-08-23
+two:
+  id: 2
+  order_id: 1
+  payed_at: 2007-08-23

Added: branches/200709_gihyo/async_shop/as_rails/test/functional/async_shop_controller_test.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/test/functional/async_shop_controller_test.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/test/functional/async_shop_controller_test.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'async_shop_controller'
+
+# Re-raise errors caught by the controller.
+class AsyncShopController; def rescue_action(e) raise e end; end
+
+class AsyncShopControllerTest < Test::Unit::TestCase
+  def setup
+    @controller = AsyncShopController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/test/unit/order_test.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/test/unit/order_test.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/test/unit/order_test.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class OrderTest < Test::Unit::TestCase
+  fixtures :orders
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/test/unit/payment_test.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/test/unit/payment_test.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/test/unit/payment_test.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class PaymentTest < Test::Unit::TestCase
+  fixtures :payments
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,11 @@
+# Author:: Shunichi Shinohara
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'ap4r_client'
+
+class ActionController::Base
+  def ap4r
+    @ap4r_client ||= ::Ap4r::Client.new(self)
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,132 @@
+# Author:: Kiwamu Kato
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'forwardable'
+require 'async_helper'
+
+module Ap4r #:nodoc:
+
+  # This +Client+ is the Rails plugin for asynchronous processing.
+  # Asynchronous logics are called via various protocols, such as XML-RPC,
+  # SOAP, HTTP POST, and more. Now default protocol is HTTP POST.
+  #
+  # Examples: The part of calling next asynchronous logics in a controller in the HelloWorld Sample.
+  #
+  #     req = WorldRequest.new([:world_id => 1, :message => "World"})
+  #     ap4r.async_to({:controller => 'async_world', :action => 'execute'},
+  #                   {:world_id => 1, :message => "World"},
+  #                   {:dispatch_mode => :HTTP}) # skippable
+  #
+  #     render :action => 'response'
+  #
+  # Complement: Above +ap4r+ method is defiend init.rb in +%RAILS_ROOT%/vendor/plugin/ap4r/+.
+  #
+  class Client
+    extend Forwardable
+    include ::Ap4r::AsyncHelper::Base
+
+    def initialize controller
+      @controller = controller
+    end
+
+    def_delegators :@controller, :logger, :url_for
+
+    # Queue a message for next asynchronous logic. Some options are supported.
+    #
+    # Use options to specify target url, etc.
+    # Accurate meanings are defined by a individual converter class.
+    # * :controller (name of next logic)
+    # * :action (name of next logic)
+    #
+    # Use rm_options to pass parameter in queue-put.
+    # Listings below are AP4R extended options.
+    # See the reliable-msg docuememt for more details.
+    # * :target_url (URL of target, prevail over :controller)
+    # * :target_action (action of target, prevail over :action)
+    # * :target_method (HTTP method, e.g. "GET", "POST", etc.)
+    # * :dispatch_mode (protocol in dispatching)
+    # * :queue_name (prevail over :controller and :action)
+    #
+    # Object of argumemts (async_params, options and rm_options) will not be modified.
+    # Implementors (of this class and converters) should not modify them.
+    #
+    # Examples: the most simple
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'},
+    #                   {:world_id => 1, :message => "World"})
+    #
+    #
+    # Examples: taking block
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world_id, 1
+    #       body :message, "World"
+    #     end
+    #
+    #
+    # Examples: transmitting ActiveRecord object
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world, World.find(1)
+    #       body :message, "World"
+    #     end
+    #
+    #
+    # Examples: transmitting with xml format over http (now support text, json and yaml format).
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world, World.find(1)
+    #       body :message, "World"
+    #       format :xml
+    #     end
+    #
+    #
+    # Examples: direct assignment for formatted message body
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       world = World.find(1).to_xml :except => ...
+    #       body_as_xml world
+    #     end
+    #
+    #
+    # Examples: setting message header
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world_id, 1
+    #       body :message, "World"
+    #
+    #       header :priority, 1
+    #       http_header "Content-type", ...
+    #     end
+    #
+    alias :async_to :async_dispatch
+
+    # Provides at-least-once QoS level.
+    # +block+ are tipically composed of database accesses and +async_to+ calls.
+    # Database accesses are executed transactionallly by +active_record_class+'s transaction method.
+    # In the +block+, +async_to+ calls invoke NOT immediate queueing but just storing messages
+    # to the database (assumed to be the same one as application uses).
+    #
+    # If the execution of +block+ finishes successfully, database transaction is committed and
+    # forward process of each stored message begins.
+    # Forward process composed in two parts. First puts the message into a queue, secondary update
+    # or delete the entry from a management table.
+    #
+    # SAF (store and forward) processing like this guarantees that any message
+    # is never lost and keeps reasonable performance (without two phase commit).
+    #
+    # Examples: Just call async_to method in this block.
+    #
+    #     ap4r.transaction do
+    #       req = WorldRequest.new([:world_id => 1, :message => "World"})
+    #       ap4r.async_to({:controller => 'async_world', :action => 'execute'},
+    #                     {:world_id => 1, :message => "World"})
+    #
+    #       render :action => 'response'
+    #     end
+    #
+    alias :transaction :transaction_with_saf
+
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,262 @@
+# Author:: Shunichi Shinohara
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'reliable-msg'
+require 'ap4r/stored_message'
+require 'message_builder'
+
+module Ap4r
+
+  # This +AsyncHelper+ is included to +Ap4rClient+ and works the Rails plugin
+  # for asynchronous processing.
+  #
+  module AsyncHelper
+
+    module Base
+      Converters = {}
+
+      DRUBY_HOST = ENV['AP4R_DRUBY_HOST'] || 'localhost'
+      DRUBY_PORT = ENV['AP4R_DRUBY_PORT'] || '6438'
+      DRUBY_URI = "druby://#{DRUBY_HOST}:#{DRUBY_PORT}"
+
+      @@default_dispatch_mode = :HTTP
+      @@default_rm_options = { :delivery => :once, :dispatch_mode => @@default_dispatch_mode }
+      @@default_queue_prefix = "queue."
+
+      mattr_accessor :default_dispatch_mode, :default_rm_options, :default_queue_prefix, :saf_delete_mode
+
+      # This method is aliased as ::Ap4r::Client#transaction
+      #
+      def transaction_with_saf(active_record_class = ::Ap4r::StoredMessage, *objects, &block)
+
+        Thread.current[:use_saf] = true
+        Thread.current[:stored_messages] = {}
+
+        # store
+        active_record_class ||= ::Ap4r::StoredMessage
+        active_record_class.transaction(*objects, &block)
+
+        # forward
+        forwarded_messages = {}
+        begin
+
+          # TODO: reconsider forwarding strategy, 2006/10/13 kato-k
+          # Once some error occured, such as disconnect reliable-msg or server crush,
+          # which is smart to keep to put a message or stop to do it?
+          # In the case of being many async messages, the former strategy is not so good.
+          #
+          # TODO: add delayed forward mode 2007/05/02 by shino
+          Thread.current[:stored_messages].each {|k,v|
+            __queue_put(v[:queue_name], v[:queue_message], v[:queue_headers])
+            forwarded_messages[k] = v
+          }
+        rescue Exception => err
+          # Don't raise any Exception. Application logic has already completed and messages are saved.
+          logger.warn("Failed to put a message into queue: #{err}")
+        end
+
+        begin
+          StoredMessage.transaction do
+            options = {:delete_mode => @@saf_delete_mode || :physical}
+            forwarded_messages.keys.each {|id|
+              ::Ap4r::StoredMessage.destroy_if_exists(id, options)
+            }
+          end
+        rescue Exception => err
+          # Don't raise any Exception. Application logic has already completed and messages are saved.
+          logger.warn("Failed to put a message into queue: #{err}")
+        end
+
+      ensure
+        Thread.current[:use_saf] = false
+        Thread.current[:stored_messages] = nil
+      end
+
+      # This method is aliased as ::Ap4r::Client#async_to
+      #
+      def async_dispatch(url_options = {}, async_params = {}, rm_options = {}, &block)
+
+        if logger.debug?
+          logger.debug("url_options: ")
+          logger.debug(url_options.inspect)
+          logger.debug("async_params: ")
+          logger.debug(async_params.inspect)
+          logger.debug("rm_options: ")
+          logger.debug(rm_options.inspect)
+        end
+
+        # TODO: clone it, 2006/10/16 shino
+        url_options ||= {}
+        url_options[:controller] ||= @controller.controller_path.gsub("/", ".")
+        url_options[:url] ||= {:controller => url_options[:controller], :action => url_options[:action]}
+        url_options[:url][:controller] ||= url_options[:controller] if url_options[:url].kind_of?(Hash)
+
+        rm_options = @@default_rm_options.merge(rm_options || {})
+
+        # Only async_params is not cloned. options and rm_options are cloned before now.
+        # This is a current contract between this class and converter classes.
+        converter = Converters[rm_options[:dispatch_mode]].new(url_options, async_params, rm_options, self)
+        logger.debug{"druby uri for queue-manager : #{DRUBY_URI}"}
+
+        queue_name = __get_queue_name(url_options, rm_options)
+        queue_message = converter.make_params
+        queue_headers = converter.make_rm_options
+
+        message_builder = ::Ap4r::MessageBuilder.new(queue_name, queue_message, queue_headers)
+        if block_given?
+          message_builder.instance_eval(&block)
+        end
+        queue_name = message_builder.queue_name
+        queue_message = message_builder.format_message_body
+        queue_headers = message_builder.message_headers
+
+        if Thread.current[:use_saf]
+          stored_message = ::Ap4r::StoredMessage.store(queue_name, queue_message, queue_headers)
+
+          Thread.current[:stored_messages].store(
+                                                 stored_message.id,
+                                                 {
+                                                   :queue_message => queue_message,
+                                                   :queue_name => queue_name,
+                                                   :queue_headers => queue_headers
+                                                 } )
+          return stored_message.id
+        end
+
+        __queue_put(queue_name, queue_message, queue_headers)
+      end
+
+      private
+      def __queue_put(queue_name, queue_message, queue_headers)
+        # TODO: can use a Queue instance repeatedly? 2007/05/02 by shino
+        q = ReliableMsg::Queue.new(queue_name, :drb_uri => DRUBY_URI)
+        q.put(queue_message, queue_headers)
+      end
+
+      def __get_queue_name(options, rm_options)
+        if options[:url].kind_of?(Hash)
+          rm_options[:queue_name] ||=
+            @@default_queue_prefix.clone.concat(options[:url][:controller].to_s).concat('.').concat(options[:url][:action].to_s)
+        else
+          rm_options[:queue_name] ||=
+            @@default_queue_prefix.clone.chomp(".").concat(URI::parse(options[:url]).path.gsub("/", "."))
+        end
+        rm_options[:queue_name]
+      end
+
+    end
+
+    module Converters #:nodoc:
+
+      # A base class for converter classes.
+      # Responsibilities of subclasses are as folows
+      # * by +make_params+, convert async_params to appropriate object
+      # * by +make_rm_options+, make appropriate +Hash+ passed by <tt>ReliableMsg::Queue#put</tt>
+      class Base
+
+        # Difine a constant +DISPATCH_MODE+ to value 'mode_symbol' and
+        # add self to a Converters list.
+        def self.dispatch_mode(mode_symbol)
+          self.const_set(:DISPATCH_MODE, mode_symbol)
+          ::Ap4r::AsyncHelper::Base::Converters[mode_symbol] = self
+        end
+
+        def initialize(url_options, async_params, rm_options, url_for_handler)
+          @url_options = url_options
+          @async_params = async_params
+          @rm_options = rm_options
+          @url_for_handler = url_for_handler
+        end
+
+        # Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
+        # first argument +message+.
+        # Should be implemented by subclasses.
+        def make_params
+          raise 'must be implemented in subclasses'
+        end
+
+        # Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
+        # second argument +headers+.
+        # Should be implemented by subclasses.
+        def make_rm_options
+          raise 'must be implemented in subclasses'
+        end
+
+        private
+        # helper method for <tt>ActionController#url_for</tt>
+        def url_for(url_for_options, *parameter_for_method_reference)
+          return url_for_options if url_for_options.kind_of?(String)
+          @url_for_handler.url_for(url_for_options, *parameter_for_method_reference)
+        end
+
+      end
+
+      class Http < Base
+        dispatch_mode :HTTP
+
+        def make_params
+          @async_params
+        end
+
+        def make_rm_options
+          @rm_options[:target_url] ||= url_for(@url_options[:url])
+          @rm_options[:target_method] ||= 'POST'
+          #TODO: make option key to specify HTTP headers, 2006/10/16 shino
+          @rm_options
+        end
+      end
+
+      class WebService < Base
+        def make_params
+          message_obj = {}
+          @async_params.each_pair{|k,v| message_obj[k.to_sym]=v}
+          message_obj
+        end
+
+        def make_rm_options
+          @rm_options[:target_url] ||= target_url_name
+          @rm_options[:target_action] ||= action_api_name
+          @rm_options
+        end
+
+        def action_api_name
+          action_method_name = @url_options[:url][:action]
+          action_method_name.camelcase
+        end
+
+        def options_without_action
+          @url_options[:url].reject{ |k,v| k == :action }
+        end
+
+      end
+
+      class XmlRpc < WebService
+        dispatch_mode :XMLRPC
+
+        def target_url_name
+          url_for(options_without_action) + rails_api_url_suffix
+        end
+
+        private
+        def rails_api_url_suffix
+          '/api'
+        end
+      end
+
+      class SOAP < WebService
+        dispatch_mode :SOAP
+
+        def target_url_name
+          url_for(options_without_action) + rails_wsdl_url_suffix
+        end
+
+        private
+        def rails_wsdl_url_suffix
+          '/service.wsdl'
+        end
+      end
+
+    end
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb	2007-08-23 13:27:18 UTC (rev 253)
@@ -0,0 +1,181 @@
+# Author:: Kiwamu Kato
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'active_record'
+
+module Ap4r #:nodoc:
+
+  # This +MessageBuilder+ is the class for formatting message body.
+  # Current support formats are text, xml, json and yaml,
+  # and the formatted messages are sent over HTTP.
+  #
+  # Using +format+ method, this class automatically changes the format of
+  # the given message body and adds appropriate +Content-type+ to http header.
+  # Or using +body_as_*+ methods, you can directly assign formatted message body.
+  class MessageBuilder
+
+    def initialize(queue_name, queue_message, queue_headers)
+      @queue_name = queue_name
+      @message_body = queue_message
+      @message_headers = queue_headers
+      @format = nil
+      @message_body_with_format = nil
+      @to_xml_options = {:root => "root"}
+    end
+
+    attr_accessor :queue_name, :message_body, :message_headers
+    attr_reader :format, :to_xml_options
+
+    # Sets message body in async_to block.
+    # The first argument is key and the second one is value.
+    #
+    # options are for to_xml conversion on Array and Hash, ActiveRecord objects.
+    #
+    def body(k, v, options = { })
+      k ||= v.class
+      if v.kind_of? ActiveRecord::Base
+        @message_body[k.to_sym] = v
+        @to_xml_options = @to_xml_options.merge(options)
+      else
+        @message_body[k.to_sym] = v
+      end
+    end
+
+    # Sets message header in async_to block.
+    # The first argument is key and the second one is value.
+    #
+    # Now supports following keys:
+    #   :expire
+    #   :priority
+    #   :delivery
+    #   :max_deliveries
+    #   :dispatch_mode
+    #   :target_method
+    #   :target_url
+    #   :id
+    #
+    # For details, please refer the reliable-msg.
+    #
+    def header(k, v)
+      @message_headers[k.to_sym] = v
+    end
+
+    # Sets http header in async_to block such as 'Content_type'.
+    # The first argument is key and the second one is value.
+    #
+    def http_header(k, v)
+      @message_headers["http_header_#{k}".to_sym] = v
+    end
+
+    # Sets format message serialization.
+    # As to the format, automatically sets content-type.
+    # Unless any format, content-type is defined as "application/x-www-form-urlencoded".
+    #
+    def format(v)
+      case @format = v
+      when :text
+        set_content_type("text/plain")
+      when :xml
+        set_content_type("text/xml application/x-xml")
+      when :json
+        set_content_type("application/json")
+      when :yaml
+        set_content_type("text/plain text/yaml")
+      else
+        set_content_type("application/x-www-form-urlencoded")
+      end
+    end
+
+    # Sets text format message. No need to use +format+.
+    #
+    def body_as_text(text)
+      @message_body_with_format = text
+      format :text
+    end
+
+    # Sets xml format message. No need to use +format+.
+    #
+    def body_as_xml(xml)
+      @message_body_with_format = xml
+      format :xml
+    end
+
+    # Sets json format message. No need to use +format+.
+    #
+    def body_as_json(json)
+      @message_body_with_format = json
+      format :json
+    end
+
+    # Sets yaml format message. No need to use +format+.
+    #
+    def body_as_yaml(yaml)
+      @message_body_with_format = yaml
+      format :yaml
+    end
+
+    # Return converted message body, as to assigned format.
+    #
+    def format_message_body
+      return @message_body_with_format  if @message_body_with_format
+
+      case @format
+      when :text
+        return @message_budy.to_s
+      when :xml
+        return @message_body.to_xml @to_xml_options
+      when :json
+        return @message_body.to_json
+      when :yaml
+        return @message_body.to_yaml
+      else
+        @message_body.each do |k,v|
+          if v.kind_of? ActiveRecord::Base
+            @message_body[k] = v.attributes
+          end
+        end
+        return query_string(@message_body)
+      end
+    end
+
+    private
+    def query_string(hash)
+      build_query_string(hash, nil, nil)
+    end
+
+    def build_query_string(hash, query = nil, top = nil)
+      query ||= []
+      top ||= ""
+
+      _top = top.dup
+
+      hash.each do |k,v|
+        top = _top
+        top += top == "" ? "#{urlencode(k.to_s)}" : "[#{urlencode(k.to_s)}]"
+        if v.kind_of? Hash
+          build_query_string(v, query, top)
+        elsif v.kind_of? Array
+          v.each do |e|
+            query << "#{top}[]=#{urlencode(e.to_s)}"
+          end
+        else
+          query << "#{top}=#{urlencode(v.to_s)}"
+        end
+      end
+      query.join('&')
+    end
+
+    def simple_url_encoded_form_data(params, sep = '&')
+      params.map {|k,v| "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" }.join(sep)
+    end
+
+    def urlencode(str)
+      str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) }
+    end
+
+    def set_content_type(type)
+      http_header("Content-type", type)
+    end
+  end
+end




More information about the ap4r-devel mailing list