[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