
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index

METHOD INVOCATION INTERCEPTION 421
And here’s the implementation of the API to determine if a product has been shipped.
File 127 |
class OrderApi < ActionWebService::API::Base |
|
api_method :is_order_shipped, |
|
:expects => [{:orderid => :int}], |
|
:returns => [:bool] |
|
end |
|
class OrderService < ActionWebService::Base |
web_service_api OrderApi
def is_order_shipped(orderid)
raise "No such order" unless order = Order.find_by_id(orderid) !order.shipped_at.nil?
end end
Implementing Delegated Dispatching
The implementation for delegated dispatching is identical to layered dispatching, except that we pass :delegated to web_service_dispatching_mode( ) rather than :layered.
|
20.5 Method Invocation Interception |
|
To avoid duplicating the same code in multiple methods, AWS allows us to |
|
perform invocation interception, allowing us to register callbacks that will |
|
be invoked before and/or after the web service request. |
|
AWS interception works similarly to Action Pack filters but includes addi- |
|
tional information about the web service request that is not available |
|
through Action Pack filters, such as the method name and its decoded |
|
parameters. |
|
For example, if we wanted to allow only remote callers with an acceptable |
|
API key to access our product searching web service, we could add an |
|
extra parameter to each method call. |
File 129 |
class ProductAuthApi < ActionWebService::API::Base |
|
api_method :find_all_products, |
|
:expects => [{:key=>:string}], |
|
:returns => [[:int]] |
|
api_method :find_product_by_id, |
|
:expects => [{:key=>:string}, {:id=>:int}], |
|
:returns => [Product] |
|
end |
|
And then create an invocation interceptor that validates this parameter |
|
without putting the code in every method. |
Prepared exclusively for Rida Al Barazi
Report erratum

METHOD INVOCATION INTERCEPTION 422
File 131 |
class BackendAuthController < ApplicationController |
wsdl_service_name 'Backend'
web_service_api ProductAuthApi web_service_scaffold :invoke
before_invocation :authenticate
def find_all_products(key) Product.find(:all).map{ |product| product.id }
end
def find_product_by_id(key, id) Product.find(id)
end protected
def authenticate(name, args)
raise "Not authenticated" unless args[0] == 'secret' end
end
Like with Action Pack, if a before interceptor returns false, the method is never invoked, and an appropriate error message is sent back to the caller as an exception. If a before interceptor raises an exception, invocation of the web service method will also be aborted.
AWS interceptors are defined using the methods before_invocation( ) and after_invocation( ).
before_invocation(interceptor, options={}) after_invocation(interceptor, options={})
An interceptor can be a symbol, in which case it is expected to refer to an instance method. It can also be a block or an object instance. When it’s an object instance, it is expected to have an intercept( ) method.
Instance method before interceptors receive two parameters when called, the method name of the intercepted method and its parameters as an array.
def interceptor(method_name, method_params) false
end
Block and object instance before interceptors receive three parameters. The first is the object containing the web service method, the second the the intercepted method name, and the third its parameters as an array.
before_invocation do |obj, method_name, method_params| false
end
After interceptors receive the same initial parameters as before interceptors but receive an additional parameter at the end. This contains the intercepted method return value, since after interceptors execute after the intercepted method has completed.
Prepared exclusively for Rida Al Barazi
Report erratum

TESTING WEB SERVICES 423
The before_invocation( ) and after_invocation( ) methods support the :except and :only options. These options take as argument an array of symbols identifying the method names to limit interceptions to.
before_invocation :intercept_before, :except => [:some_method]
The previous example applies the :intercept_before interceptor to all web service methods except the :some_method method.
20.6 Testing Web Services
Action Web Service integrates with the Rails testing framework, so we can use the standard Rails testing idioms to ensure our web services are working correctly.
When we used the web_service generator for the first example, a skeleton functional test was created for us in test/functional/backend_api_test.rb.
This is our functional test, modified to pass on the parameters expected by the example on page 412.
File 135 |
require File.dirname(__FILE__) + '/../test_helper' |
|
|
require 'backend_controller' |
|
|
class BackendController |
|
|
def rescue_action(e) |
|
|
raise e |
|
|
end |
|
|
end |
|
|
class BackendControllerApiTest < Test::Unit::TestCase |
|
|
fixtures :products |
|
|
def setup |
|
|
@controller = BackendController.new |
|
|
@request |
= ActionController::TestRequest.new |
|
@response |
= ActionController::TestResponse.new |
|
end |
|
def test_find_all_products
result = invoke :find_all_products assert result[0].is_a?(Integer)
end
def test_find_product_by_id
product = invoke :find_product_by_id, 2 assert_equal 'Product 2', product.description
end end
This tests the web service methods in BackendController. It performs a complete Action Pack request/response cycle, emulating how our web service will get called in the real world.
The tests use invoke(method_name, *args) to call the web service. The paramater method_name is a symbol identifying the method to invoke, and *args
Prepared exclusively for Rida Al Barazi
Report erratum

TESTING WEB SERVICES 424
are zero or more parameters to be passed to that method.
The invoke( ) method can be used to test controllers using direct dispatching only. For layered and delegated dispatching, we use invoke_layered( ) and invoke_delegated( ) to perform the test invocations. They have identical signatures.
invoke_layered(service_name, method_name, *args) invoke_delegated(service_name, method_name, *args)
In both cases, the service_name parameter refers to the first parameter passed to web_service( ) when declaring the service in the controller.
External Client Applications (SOAP)
When we want to test with external applications on platforms that have a SOAP stack, we’ll want to create our clients from the WSDL that AWS can generate.
The WSDL file AWS generates declares our web service to use RPC-encoded messages, as this gives us stronger typing. These are also the only type of message AWS supports: Document/Literal messages are not supported.
The default Rails config/routes.rb file creates a route named service.wsdl on our controller. To get the WSDL for that controller, we’d download the file
http://my.app.com/CONTROLLER/service.wsdl
and use an IDE such as Visual Studio or the appropriate command-line tools like wsdl.exe to generate the client class files. Should we remove the service.wsdl route, an action named wsdl( ) will still exist in the controller.
External Client Applications (XML-RPC)
If our web service uses XML-RPC instead, we have to know what the endpoint URL for it is going to be, as XML-RPC does not have a WSDL equivalent with information on where to send protocol requests. For direct and layered dispatching, the endpoint URL is
http://my.app.com/PATH/TO/CONTROLLER/api
For delegated dispatching, the endpoint URL is
http://my.app.com/PATH/TO/CONTROLLER/SERVICE_NAME
In this case, SERVICE_NAME refers to the name given as the first parameter to web_service( ) in the controller.
Prepared exclusively for Rida Al Barazi
Report erratum