Jason Fox

Icon

programming, products, and pontifications…

XML-RPC, SOAP and Polymorphism

According to the XML-RPC specification a XML_RPC request may only contain scalar <value>s or non-scalar <struct>s. The specification unfortunately does not provide any standard for encoding the type of data encoded in the <struct>s. This has the side effect of not being able to support polymorphism in service method parameters as it leaves the sever no choice but to rely on the method signature in the API declaration when trying to determine what to instantiate for a given <struct> in the XML-RPC request.

Let’s say you have the following declarations:

class SubscriptionsApi < ActionWebService::API::Base
  api_method(
    :create_subscription,
    :expects => [
      { :customer => Logical::Customer },
      { :payment_method => Logical::PaymentMethod }
    ]
  )
end
module Logical
  class PaymentMethod < ActionWebService::Struct
  end
  class CreditCard < PaymentMethod
    member :card_number, :string
    # ...
  end
  class PayPal < PaymentMethod
    member :login, :string
    # ...
  end
end

Now you want to make a call to the service method and pass either a CreditCard or a PayPal. XML-RPC will encode the request like so:

<methodCall>
  <methodName>create_subscription</methodName>
  <param>
    <struct>
      <member>
        <name>card_number</name>
        <value>4111-1111-1111-1111</value>
      </member>
    </struct>
  </param>
</methodCall>

This provides no type information to the server so the server will attempt to instantiate a Logical::PaymentMethod which will of course not have a card_number member as it’s specific to the CreditCard subclass. SOAP, on the other hand, does encode the parameter types allowing you to utilize this type of polymorphism in your service parameters. Here’s the same request encoded in SOAP.

<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
  <n1:CreateSubscription xmlns:n1="urn:ActionWebService" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <payment_method xmlns:n2="http://www.ruby-lang.org/xmlns/ruby/type/custom" xsi:type="n2:Logical..CreditCard">
      <card_number xsi:type="xsd:string">1</card_number>
    </payment_method>
  </n1:CreateSubscription>
 </env:Body>
</env:Envelope>

The current implementation of ActionWebService resurrected by datanoise did not support this type of polymorphism in SOAP requests. However, I submitted a patch recently which provides for this functionality. Hopefully it’s accepted. :)

MySQL Allows NULLs Where They Are Not Welcome

I recently came across an annoying bug in MySQL v5.1 (also in 6.0 apparently) that bit me hard, so, I thought I’d post on it in case you are being bitten by the same bug.

If you attempt to update a column that does not allow NULL to NULL, MySQL will set the column’s value to the default value for that column’s data type.  This is true only when you are not running MySQL strict mode.  Here’s an example to illustrate.

mysql> create table null_test (id int not null unique(id), name varchar(25)
null default null);
Query OK, 0 rows affected (0.01 sec)

mysql> show create table null_test;
+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table     | Create Table                                                                                                                                           |
+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| null_test | CREATE TABLE `null_test` (
  `id` int(11) NOT NULL,
  `name` varchar(25) default NULL,
  UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> insert into null_test (id, name) values (1, 'Jane');
Query OK, 1 row affected (0.00 sec)

mysql> update null_test set id = null;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> select * from null_test where name = 'Jane';
+----+------+
| id | name |
+----+------+
|  0 | Jane |
+----+------+
1 row in set (0.00 sec)

More information about this bug can be found in the bug report submitted Janurary 4, 2008.

Check Constraints and MySQL

Unfortunately, MySQL does not support check constraints out of the box.  This makes the task of enforcing business logic in the database layer difficult, but not impossible.  I recently found this approach to implementing check constraints in MySQL.  It’s not as pretty and clean as I’d like, but, it’s the best approach that I’ve found so far.

Now. why would you want to encode business logic in the database?  Can’t you make due with your ActiveRecord::Validations?

Well, have you ever updated the database directly?  Have you ever called update_attribute on an object?  How about save_with_validation(false)?   Yeah, I thought so.  Read more about why you should treat your database as a fortress in Dan Chak’s recently released book, Enterprise Rails (review coming soon).

poop car

img_05721

Rewriting Sub-selects as Joins

Your RDBMS will usually rewrite your sub-selects behind the scenes as joins.  However, there are times where you’ll want to do this yourself.  For example, past versions of MySQL did not play well with sub-selects.  Here are a couple examples of how to rewrite sub-selects as joins.

select *
from   table_a
where  id not in (select a_id from table_b);
-- can be rewritten as...
select *
from   table_a
left   outer join table_b on table_a.id = table_b.a_id
where  table_b.a_id is null;

select *
from   table_a
where  id in (select a_id from table_b);
-- can be rewritten as...
select *
from   table_a
left outer join table_b on table_a.id = table_b.a_id
where  table_b.a_id is not null;