- •Contents
- •Preface to the Second Edition
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •The Architecture of Rails Applications
- •Models, Views, and Controllers
- •Active Record: Rails Model Support
- •Action Pack: The View and Controller
- •Installing Rails
- •Your Shopping List
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Linux
- •Development Environments
- •Rails and Databases
- •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 A3: Validate!
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B4: Linking to the Cart
- •Task C: Cart Creation
- •Sessions
- •Iteration C1: Creating a Cart
- •Iteration C2: A Smarter Cart
- •Iteration C3: Handling Errors
- •Iteration C4: Finishing the Cart
- •Task D: Add a Dash of AJAX
- •Iteration D1: Moving the Cart
- •Iteration D3: Highlighting Changes
- •Iteration D4: Hide an Empty Cart
- •Iteration D5: Degrading If Javascript Is Disabled
- •What We Just Did
- •Task E: Check Out!
- •Iteration E1: Capturing an Order
- •Task F: Administration
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Iteration F4: A Sidebar, More Administration
- •Task G: One Last Wafer-Thin Change
- •Generating the XML Feed
- •Finishing Up
- •Task T: Testing
- •Tests Baked Right In
- •Unit Testing of Models
- •Functional Testing of Controllers
- •Integration Testing of Applications
- •Performance Testing
- •Using Mock Objects
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Logging in Rails
- •Debugging Hints
- •Active Support
- •Generally Available Extensions
- •Enumerations and Arrays
- •String Extensions
- •Extensions to Numbers
- •Time and Date Extensions
- •An Extension to Ruby Symbols
- •with_options
- •Unicode Support
- •Migrations
- •Creating and Running Migrations
- •Anatomy of a Migration
- •Managing Tables
- •Data Migrations
- •Advanced Migrations
- •When Migrations Go Bad
- •Schema Manipulation Outside Migrations
- •Managing Migrations
- •Tables and Classes
- •Columns and Attributes
- •Primary Keys and IDs
- •Connecting to the Database
- •Aggregation and Structured Data
- •Miscellany
- •Creating Foreign Keys
- •Specifying Relationships in Models
- •belongs_to and has_xxx Declarations
- •Joining to Multiple Tables
- •Acts As
- •When Things Get Saved
- •Preloading Child Rows
- •Counters
- •Validation
- •Callbacks
- •Advanced Attributes
- •Transactions
- •Action Controller: Routing and URLs
- •The Basics
- •Routing Requests
- •Action Controller and Rails
- •Action Methods
- •Cookies and Sessions
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Using Helpers
- •How Forms Work
- •Forms That Wrap Model Objects
- •Custom Form Builders
- •Working with Nonmodel Fields
- •Uploading Files to Rails Applications
- •Layouts and Components
- •Caching, Part Two
- •Adding New Templating Systems
- •Prototype
- •Script.aculo.us
- •RJS Templates
- •Conclusion
- •Action Mailer
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Secure and Deploy Your Application
- •Securing Your Rails Application
- •SQL Injection
- •Creating Records Directly from Form Parameters
- •Avoid Session Fixation Attacks
- •File Uploads
- •Use SSL to Transmit Sensitive Information
- •Knowing That It Works
- •Deployment and Production
- •Starting Early
- •How a Production Server Works
- •Repeatable Deployments with Capistrano
- •Setting Up a Deployment Environment
- •Checking Up on a Deployed Application
- •Production Application Chores
- •Moving On to Launch and Beyond
- •Appendices
- •Introduction to Ruby
- •Classes
- •Source Code
- •Resources
- •Index
- •Symbols
TESTING WEB SERVICES 594
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 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.
The before_invocation and after_invocation methods support the :except and :only options. These options take as an 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.
25.6Testing Web Services
AWS 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 584.
Download e1/depot_ws/test/functional/backend_api_test.rb
require File.dirname(__FILE__) + '/../test_helper' require 'backend_controller'
class BackendController def rescue_action(e)
raise e
Report erratum
TESTING WEB SERVICES 595
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 parameter method_name is a symbol identifying the method to invoke, and *args is zero or more parameters to be passed to that method.
The invoke method can test controllers using direct dispatching only. For layered and delegated dispatching, 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 should create clients from the WSDL that AWS can generate.
The WSDL file AWS generates declares our web service to use RPC-encoded messages, because 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
Report erratum
TESTING WEB SERVICES 596
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="Backend" xmlns:typens="urn:ActionWebService" . . .
<types>
<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema" . . .
<xsd:complexType name="Product" > <xsd:all>
<xsd:element name="id" type="xsd:int" /> <xsd:element name="title" type="xsd:string" />
<xsd:element name="description" type="xsd:string" /> <xsd:element name="image_url" type="xsd:string" />
<xsd:element name="price" type="xsd:double" />
<xsd:element name="date_available" type="xsd:dateTime" />
</xsd:all>
</xsd:complexType>
<xsd:complexType name="IntegerArray"> <xsd:complexContent>
<xsd:restriction base="soapenc:Array" >
<xsd:attribute wsdl:arrayType="xsd:int[]" ref="soapenc:arrayType"/> </xsd:restriction>
</xsd:complexContent> </xsd:complexType>
</xsd:schema>
</types>
<message name="FindAllProducts" >
</message>
<message name="FindAllProductsResponse" >
<part name="return" type="typens:IntegerArray"/>
</message>
. . .
Figure 25.4: WSDL Generated by AWS
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, because 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
Report erratum
PROTOCOL CLIENTS 597
In this case, SERVICE_NAME refers to the name given as the first parameter to web_service in the controller.
Having two different URLs for these different cases may seem arbitrary, but there is a reason. For delegated and layered dispatching, the information telling us which service object the invocation should be routed to is embedded in the request. For delegated dispatching we rely on the controller action name to determine which service it should go to.
Note that these URLs are used as both the SOAP and XML-RPC message endpoints; AWS is able to determine the type of message from the request.
25.7Protocol Clients
Action Web Service includes some client classes for accessing remote web services. These classes understand Action Web Service API definitions, so if we have the API definition of a remote service, we can access that service with type conversion to and from the correct types occurring automatically for us.
However, these are not general-purpose clients. If our client application is not tightly coupled to the server, it may make more sense to use Ruby’s native SOAP and XML-RPC clients.
If we want to access a remote web service API from inside a controller with the AWS clients, use the web_client_api helper function.
class MyController < ApplicationController web_client_api :product,
:soap,
"http://my.app.com/backend/api"
def list
@products = product.find_all_products.map do |id| product.find_product_by_id(id)
end end
end
The web_client_api declaration creates a protected method named product in the controller. This uses the ProductApi class we created in the first example. Calling the product method returns a client object with all the methods of ProductApi available for execution.
We can also invoke the web service API directly by creating an instance of the client for the relevant protocol (either ActionWebService::Client::Soap or ActionWebService::Client::XmlRpc). We’ll then be able to invoke API methods on this instance.
shop = ActionWebService::Client::Soap.new(ProductApi,
"http://my.app.com/backend/api" ) product = shop.find_product_by_id(5)
Report erratum