Wednesday, March 28, 2007

NetSuite and Ruby, the final chapter

NetSuite requires a maintainable session for subsequent requests after the initial login by way of several cookies they return upon successful authentication. The standard Net::HTTP library requires mucking with headers by hand in order to perpetuate cookie information. We were easily able to override SOAP::NetHttpClient::Response and SOAP::HTTPStreamHandler in the soap4r gem in order to provide the functionality for maintaining the NetSuite session.

However
, NaHi's http-access2 library already provides cookie management (even persistence if needed). After installing visnu's gem it's simply a matter of turning off the SSLConfig#verify_mode and everything works flawlessly. The final test code:
require 'rubygems'
require_gem 'soap4r'
require 'defaultDriver' #generated by soap4r
require 'pp'

driver = NetSuitePortType.new
driver.wiredump_dev = STDOUT
driver.proxy.streamhandler.client.ssl_config.verify_mode = nil

passport = Passport.new
passport.email = '123123123@123123123.com'
passport.password = '123123123'
passport.account ="123123"
passport.role = RecordRef.new
passport.role.xmlattr_internalId = '123123123'

driver.login(LoginRequest.new(passport))

record = RecordRef.new()
record.xmlattr_internalId = '123123123'
record.xmlattr_type = RecordType::Customer

pp driver.get(GetRequest.new(record))

Everything said and done, this has been a good learning experience. But more than anything it has exposed one of the Ruby community's greatest weaknesses: documentation. I've known this since the beginning of my Ruby days (2004) and have grown accustomed to reading code. Nevertheless, that takes time, and usually more time than scanning decently-documented APIs. The other side of this story is that I understand the Japanese/English language barrier which may intimidate or otherwise prevent library maintainers from providing more documentation. In the end, Ruby is a community-driven language, which is not sponsored by massive corporate dollars (cough, Java, cough, .Net) and its beginnings have solid Japanese roots. Here's to hoping that documentation improves as more libraries become available and the language matures. In the mean time, thank goodness for Google, mailing lists, blogs and IRC. I believe that documentation practices exhibited by 37Signals and the Rails community are the guiding example, they're not perfect, but the situation continues to progress.

soap4r and NetSuite revisited

In the last post I mentioned that the generated file (default.rb) contained redundant object definitions. I also ended with the concern about the RecordRef element not appearing to be correct. That led me to look at the generator and as it turns out I was invoking the wrong one. Funny thing, the soap4r gem ships with wsdl2ruby.rb and places them in two different locations. The first (in OSX) was in /opt/local/bin where the second was in /opt/local/lib/ruby/gems/1.8/gems/soap4r-1.5.5.20061022/bin/. Yesterday we explictly invoked the one in the gem, disregarding the the executable wrapper in /opt/local/bin. That was the mistake, as the results can be easily reproduced with executing
ruby /opt/local/lib/ruby/gems/1.8/gems/soap4r-1.5.5.20061022/bin/wsdl2ruby.rb --wsdl https://webservices.netsuite.com/wsdl/v2_5_0/netsuite.wsdl --type client
The problem being that the wrapper adds the necessary "requires" for referencing the gem's code (correct results) and not the standard (incorrect results).

Using the correct wsdl2ruby the following test works without any modifications to the generated code:


require 'rubygems'
require_gem 'soap4r'
require 'defaultDriver'

driver = NetSuitePortType.new
driver.wiredump_dev = STDOUT

passport = Passport.new
passport.email = 'test'
passport.password = 'test'
passport.account ="test"

role = RecordRef.new
role.xmlattr_internalId = '123123123'

passport.role = role

response = driver.login(LoginRequest.new(passport))

pp "successful login: #{response.sessionResponse.status.xmlattr_isSuccess}"

Tuesday, March 27, 2007

adventures in soap4r and NetSuites webservices

Spent most of the day becoming familiar with soap4r in order to communicate with NetSuite. Turns out that the soap4r shipping with Ruby 1.8.5 is outdated and quite buggy compared to its gem counterpart. In fact, we would've saved a couple hours if we knew that was an option before we even started setting breakpoints.

WSDL2Ruby worked decently enough with NetSuite's WSDL but the generated file containing the classes (default.rb) had redundant entries, like 35 definitions for the Passport object. Next, there was an issue with some of the classes being incomplete. For example, RecordRef was:

class RecordRef
@@schema_type = "RecordRef"
@@schema_ns = "urn:core_2_5.platform.webservices.netsuite.com"
@@schema_element = []

def initialize
end
end
which is incorrect as it is defined as:

<complextype name="RecordRef">
<complexcontent>
<extension base="platformCore:BaseRef">
<attribute name="internalId" type="xsd:string">
<attribute name="externalId" type="xsd:string">
<attribute name="type" type="platformCoreTyp:RecordType">
<!-- primary record internalId -->
<!-- record type -->
</attribute>
</attribute>
</attribute>
</extension></complexcontent></complextype>

The object we created to get the request working correctly is

class RecordRef
@@schema_type = "RecordRef"
@@schema_ns = "urn:core_2_5.platform.webservices.netsuite.com"

attr_accessor :internalId
attr_accessor :externalId
attr_accessor :type

def initialize(internalId = nil, externalId = nil, type = nil)
@internalId = internalId
@externalId = externalId
@type = type
end
end


Finally, calling

response = driver.login(LoginRequest.new(passport))
produced a successful response from NetSuite. However, something still seems awry with the request as the role element is not properly formed:

<n1:login xmlns:n1="urn:messages_2_5.platform.webservices.netsuite.com" xsi:type="n1:LoginRequest">
<n1:passport xmlns:n2="urn:core_2_5.platform.webservices.netsuite.com" xsi:type="n2:Passport">
<n2:email>test </n2:email>
<n2:password>test</n2:password>
<n2:account>test</n2:account>
<n2:role xsi:type="n2:RecordRef">
</n2:role>
</n1:passport>
</n1:login>

Friday, March 09, 2007

Geocoding service findings and comparison

Google Maps:

  • requires an API key

  • http geocode service that'll return results in XML or JSON

  • lots of documentation on javascript implementation

  • issues in resolving bad addresses by suggesting alternatives and flagging them with the best accuracy-level

  • comma separated values on the query string

  • 50k per day limit


Yahoo MapService:

  • http geocode service return results in XML or PHP

  • cleaner documentation

  • ApplicationID required

  • address suggestion behaves more as expected and accuracy seems better

  • query string attributes expected in request

  • 5k per day limit


Note regarding accuracy statements above: I tested both services with variations on my home address which can be identified strictly by number coordinates or with the street name (Valley Sage Drive). Google's service did not find my address in any format I tried but continued to respond with varied suggestions none of which were applicable (though it stated they were the highest level of accuracy). As for yahoo, it found my address in every variation and for those that it suggested it clearly indicated a warning as an attribute of the ResultSet. Odd that Google Maps works for my address, but the geocoding service does not (and they're supposed to be integrated).


Both services provide city and state values when given a zip.

Determining valid addresses may prove to be a bit difficult given that both services "suggest" what the closest match could be.

Thursday, March 08, 2007

a more efficient top for OS X

echo "alias top=\"top -d -o cpu\"" >> ~/.profile


The "-d" bypasses the strain on Mach"-o cpu" and will sort by procs consuming the most CPU.

Wednesday, March 07, 2007

HttpUtility.UrlEncode, fails again

Bitten a couple months ago by the fact that System.Web.HttpUtility.UrlEncode does NOT encode ticks/single-quotes ('). How lovely. I filed a bug, someone at MS closed it out almost a month later with no explanation. It's current state is "won't fix".

Bitten again by another bug in the same function. What a shocking surprise. UrlEncode does not escape/encode the plus (+). It should encode it as %2B, but no, it doesn't, it just leaves it in the string.

Hosers.