- •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
Leon Breedt, the author of this chapter and the Action Web Service code, is an analyst/developer originally from the city of Cape Town, South Africa.
Chapter 25
Web Services on Rails
(This chapter is mildly controversial. With the advent of REST support in Rails, the core team is less interested in XML-RPC–based and SOAP-based web services. Action Web Service will be removed from the Rails core and made into a plugin for Rails 2.0. For new applications where you aren’t constrained by external interfaces, you might want to consider using a lighter-weight REST approach. However, if you need to interface to existing web services, this chapter should give you the information you need.)
With the Depot application up and running, we may want to let other developers write their own applications that can talk to it using standard web service protocols. To do that, we’ll need to get acquainted with Action Web Service (which we’ll call AWS from now on).
In this chapter, we’ll discuss how AWS is structured. We’ll see how to declare an API, write the code to implement it, and then make sure it works by writing tests for it.
25.1What AWS Is (and What It Isn’t)
AWS provides support for the SOAP and XML-RPC protocols in Rails application. It converts incoming method invocation requests into method calls on our web services and takes care of sending back the responses. This lets us focus on the work of writing the application-specific methods to service the requests.
AWS does not implement every facet of the W3C specifications for SOAP and WSDL or provide every possible feature of XML-RPC. Instead, it focuses on the functionality we can reasonably expect to use regularly in our web services.
•Arbitrarily nested structured types
•Typed arrays
•Sending of exceptions and traces back over the wire when web service methods raise exceptions
THE API DEFINITION 584
Action Web Service lets us be liberal in the input we accept from remote callers, and strict in the output we emit,1 by coercing input and output values into the correct types.
Using Action Web Service, we could
•add support for the Blogger or metaWeblog APIs to a Rails application,
•implement our own custom API and have .NET developers be able to generate a class to use it from the Action Web Service–generated WSDL, and
•support both SOAP and XML-RPC backends with the same code.
25.2The API Definition
The first step in creating a web services application is deciding the functionality we want to provide to remote callers and how much information we’re going to expose to them.
Ideally, it would then be enough to simply write a class implementing this functionality and make it available for invocation. However, this causes problems when we want to interoperate with languages that aren’t as dynamic as Ruby. A Ruby method can return an object of any type. This can cause things to blow up spectacularly when our remote callers get back something they didn’t expect.
AWS deals with this problem by performing type coercion. If a method parameter or return value is not of the correct type, AWS tries to convert it. This makes remote callers happy but also stops us from having to jump through hoops to get input parameters into the correct type if we have remote callers sending us bogus values, such as strings instead of proper integers.
Since Ruby can’t use method definitions to determine the expected method parameter types and return value types, we have to help it by creating an API definition class. Think of the API definition class as similar to a Java or C# interface: It contains no implementation code and cannot be instantiated. It just describes the API.
Enough talk, let’s see an example. We’ll use the generator to get started. We’ll create a web service that has two methods: one to return a list of all products and the other to return details of a particular product.
depot> ruby script/generate web_service Backend find_all_products find_product_by_id exists app/apis/
exists test/functional/
create app/apis/backend_api.rb
create app/controllers/backend_controller.rb create test/functional/backend_api_test.rb
1. To paraphrase Jon Postel (and, later, Larry Wall)
Report erratum
THE API DEFINITION 585
This generates a stub API definition.
Download e1/depot_ws/app/apis/backend_api_generated.rb
class BackendApi < ActionWebService::API::Base api_method :find_all_products
api_method :find_product_by_id end
It generates a skeleton controller.
Download e1/depot_ws/app/controllers/backend_controller_generated.rb
class BackendController < ApplicationController wsdl_service_name 'Backend'
def find_all_products end
def find_product_by_id end
end
And it generates a sample functional test that we’ll cover in Section 25.6,
Testing Web Services, on page 594.
We’ll need to finish off the API definition. We’ll change its name to ProductApi and its filename to app/apis/product_api.rb.
Download e1/depot_ws/app/apis/product_api.rb
class ProductApi < ActionWebService::API::Base api_method :find_all_products,
:returns => [[:int]] api_method :find_product_by_id, :expects => [:int], :returns => [Product]
end
Since we changed the API definition name, the automatic loading of the API definition BackendApi (because it shares a prefix with the controller) will no longer work. So, we’ll add a web_service_api call to the controller to attach it to the controller explicitly. We also add some code to the method bodies and make the signatures match up with the API.
Download e1/depot_ws/app/controllers/backend_controller.rb
class BackendController < ApplicationController wsdl_service_name 'Backend'
web_service_api ProductApi web_service_scaffold :invoke
def find_all_products
Product.find(:all).map{ |product| product.id } end
def find_product_by_id(id) Product.find(id)
end end
Report erratum
THE API DEFINITION 586
Figure 25.1: Web Service Scaffolding Lets You Test APIs
There are a couple of important points to note in this example controller. The wsdl_service_name method associates a name with the service that will be used in generated Web Services Definition Language (WSDL). It is not necessary, but setting it is recommended. The web_service_scaffold call acts like the standard Action Pack scaffolding. This provides a way to execute web service methods from a web browser while in development and is something we will want to remove in production.
Now that we’ve implemented the service and the scaffolding is in place, we can test it by navigating to the scaffold action (we passed its name as the first parameter to web_service_scaffold). Figure 25.1, shows the result of navigating to the scaffold in a browser.
Method Signatures
AWS API declarations use api_method to declare each method in the web service interface. These declarations use signatures to specify the method’s calling convention and return type.
A signature is an array containing one or more parameter specifiers. The
Report erratum
THE API DEFINITION 587
parameter specifier tells AWS what type of value to expect for the corresponding parameter and, optionally, the name of the parameter.
api_method accepts the :expects and :returns options for specifying signatures. The :expects option indicates the type (and optionally the name) of each of our method’s parameters. The :returns option gives the type of the method’s return value.
If we omit :expects, AWS will raise an error if remote callers attempt to supply parameters. If we omit :returns, AWS will discard the method return value, returning nothing to the caller. The presence of either option will cause AWS to perform casting to ensure the following.
•The method input parameters are of the correct type by the time the method executes.
•The value returned by the method body is of the correct type before returning it to the remote caller.
Format of Parameter Specifiers
Parameter specifiers are one of the following.
•A symbol or a string identifying one of the Action Web Service base types
•The Class object of a custom structured type (such as an ActionWebService::Struct or ActiveRecord::Base; see Section 25.2, Structured Parameter Types, on the following page)
•A single-element array containing an item from (1) or (2)
•A single-element hash containing as a key the name of parameter and one of (1), (2), or (3) as a value
For example, the following are valid signatures.
[[:string]]
A string array parameter
[:bool]
A boolean parameter
[Person]
A Person structured-type parameter
[{:lastname=>:string}]
A string parameter, with a name of lastname in generated WSDL
[:int, :int]
Two integer parameters
Report erratum
THE API DEFINITION 588
Parameter Names
Notice that we didn’t name the method parameters in the :expects signature for the example ProductApi. Naming the parameters in :expects is not necessary, but without names, the generated WSDL will not have descriptive parameter names, making it less useful to external developers.
Base Parameter Types
For simple types such as numbers, strings, booleans, dates, and times, AWS defines a set of names that can be used to refer to the type in a signature instead of using the possibly ambiguous Class object.
We can use either a symbol or a string as a parameter specifier.
:int
An integer number parameter.
:string
A string value.
:base64
Use this to receive binary data. When the remote caller supplies a value using the protocol’s Base64 type and :base64 was used in the signature, the value will be decoded to binary by the time our method sees it.
:bool
A boolean value.
:float
A floating-point number.
:time
A time stamp value, containing both date and time. Coerced into the Ruby Time type.
:datetime
A time stamp value, containing both date and time. Coerced into the Ruby DateTime type.
:date
A date value, containing just the date. Coerced into the Ruby Date type.
Structured Parameter Types
In addition to the base types, AWS lets us use the Class objects of ActionWebService::Struct or ActiveRecord::Base in signatures.
Report erratum