bgeek.net

Behaviour Driven Development

Posted by Owen Evans on Thursday, October 22nd, 2009

Last night I had a lot of fun presenting at the Wellington .net User Group on Behaviour Driven Development.

Below are the slides from the talk.

Bahaviour Driven DevelopmentView more presentations from buildmaster. Sphere: Related Content

continue reading

TechEd 2009 Day 2

Posted by Owen Evans on Tuesday, September 15th, 2009

Day 2 seems a bit thinner on the ground for dev talks.

Challenging the role of Software Architect by Kevin Fancis Not so much challenging as saying we need them more, which I disagree with, had personal disagreements with the content of the talk and for the first time at TechEd I felt compelled to leave the [...]

continue reading

TechEd 2009 Day 1

Posted by Owen Evans on Monday, September 14th, 2009

Well day one is coming to a close, so before I forget I need to get my thought’s down on paper…. well virtual paper..

keynote

I really think MS need to rethink inviting politicians to TechEd, it really adds very little value for people in the audience, in the end it’s a gathering of IT professionals and [...]

continue reading

Connecting to the Xero API: Part 1 [Ruby, GET, Contacts]

Posted by Owen Evans on Wednesday, July 30th, 2008

Hey hey hey, so I finally get an excuse to post some ruby code, yay!

As part of my foray into the Xero API I wanted to get a feel for how easy it would be to connect from non .net languages and as my Ruby was getting a bit rusty I thought I’d pull out the old text editor and write up some simple methods and models for interacting with the API, so here’s the first part. How to connect to the GET methods for contacts.

The first step was to write down the specifications in RSpec, I’m glad to say they’re all green and for your reading pleasure here they are:

RSpec Results

26 examples, 0 failures

Finished in 3.567 seconds

Address
should have correct country
should have correct postal code
should have correct region
should have correct city
should have correct address line 4
should have correct address line 3
should have correct address line 2
should have correct address line 1
should have correct type
Phone
should have correct country code
should have correct area code
should have correct number
should have correct type
Contact fetch without specifying a contact id should fetch all
should fetch the correct number of contacts
Contact fetch, specifying an id
should populate phone numbers
should populate addresses
should fetch the correct contact
Contact
should populate phone data correctly
should have the correct number of phone numbers
should have the correct address
should have the correct number of addresses
should have the correct updated date
should have the correct email address
should have the correct status
should have the correct xero id
should have the correct name

Ok once those were defined it was time to build up the models, I cheated and used Rails to create some ActiveRecord classes really easily, the migrations look like this:

Phones Migration

class CreatePhones < ActiveRecord::Migration
  def self.up
    createtable :phones do |t|
      t.string :phonetype
      t.string :number
      t.string :areacode
      t.string :countrycode

  t.timestamps
end

end

def self.down drop_table :phones end end

Addresses Migration

class CreateAddresses < ActiveRecord::Migration
  def self.up
    create_table :addresses do |t|
      t.string :addresstype
      t.string :line1
      t.string :line2
      t.string :line3
      t.string :line4
      t.string :city
      t.string :region
      t.string :postal_code
      t.string :country

      t.timestamps
    end
  end

  def self.down
    drop_table :addresses
  end
end

Contacts Migration

class CreateContacts < ActiveRecord::Migration
  def self.up
    create_table :contacts do |t|
      t.string :name
      t.string :x ero_id
      t.string :status
      t.string :email_address
      t.datetime :updated_date_utc

      t.timestamps
    end
  end

  def self.down
    drop_table :contacts
  end
end

 

this should give you your 3 models, but of course we don’t want them to be populated from the database we want them to be populated from the xero api, as such each model has a from_xml method defined, which takes in the xml for the contact/address/phone as appropriate

Also the contact model has a fetch method which actually goes away and gets the xml stream either for an individual contact (if :x ero_id is passed in) otherwise for all contacts

address.rb

require 'hpricot'
class Address < ActiveRecord::Base
  include XmlHelper
  has_one :contact
   def self.from_xml(address_xml)
    doc = Hpricot(address_xml)
    mappings = {
    :addresstype => "/address/addresstype",
    :line1 => "/address/addressline1",
    :line2 => "/address/addressline2",
    :line3 => "/address/addressline3",
    :line4 => "/address/addressline4",
    :city => "/address/city",
    :region => "/address/region",
    :postal_code => "/address/postalcode",
    :country => "/address/country",
    }
    attr = XmlHelper.populate_from_xml(doc,mappings)
    return Address.new(attr)
  end
end

phone.rb

require "hpricot"
class Phone < ActiveRecord::Base
  include XmlHelper
  has_one :contact
  def self.from_xml(phone_xml)
    doc = Hpricot(phone_xml)
    mappings = {
    :phone_type => "/phone/phonetype",
    :number => "/phone/phonenumber",
    :area_code => "/phone/phoneareacode",
    :country_code => "/phone/phonecountrycode",
    }
    attr=XmlHelper.populate_from_xml(doc,mappings)
    return Phone.new(attr)
  end
end

contact.rb

require 'hpricot'
require 'open-uri'
class Contact < ActiveRecord::Base
  include XmlHelper
  
  has_many :addresses
  has_many :phones
  def self.from_xml(contact_xml)
    doc = Hpricot(contact_xml)
    mappings = {
    :name=>"/contact/name",
    :x ero_id=>"/contact/contactid",
    :status=>"/contact/contactstatus",
    :email_address=>"/contact/emailaddress",
    :updated_date_utc=>"/contact/updateddateutc"
    }
    attr = XmlHelper.populate_from_xml(doc,mappings)
    contact =  Contact.new(attr)
    (doc/"/contact/addresses/address").each do |address_xml|
      contact.addresses << Address.from_xml(address_xml.to_html) 
    end
    (doc/"/contact/phones/phone").each do |phone_xml|
      contact.phones << Phone.from_xml(phone_xml.to_html)
    end
    return contact
  end
  def self.fetch(params)
    if(params[:xero_id]!=nil)
      response=Hpricot(open("#{params[:url]}/contact?contactID=#{params[:xero_id]}&apiKey=#{params[:api_key]}&xeroKey=#{params[:customer_key]}"))
      return Contact.from_xml(response.at("/response/contact").to_html)
    else
      contacts = Array.new
      response=Hpricot(open("#{params[:url]}/contacts?apiKey=#{params[:api_key]}&xeroKey=#{params[:customer_key]}"))
      (response/"/response/contacts/contact").each do |contact_xml|
        contacts << Contact.from_xml(contact_xml.to_html)
      end
      return contacts
    end
  end
end

There’s also a helper module that takes in a hash of property names mapped to the xml path for the property, and defaults to an empty string if the xml isn’t found

xml_helper.rb

module XmlHelper
  def self.populate_from_xml(xml,mappings) 
    params={}
   
    mappings.each { |key,value| 
      if(!xml.at(value).nil?)
        params[key]=xml.at(value).inner_text
      else
        params[key]=""
      end
    }
    return params
  end
end

And here’s the specs that should show how everything is used, and glues together (the xml is contained in fixture files to remove the need to go to the api each time)

address_spec.rb

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Address do
  before(:each) do
    @xml = File.read(RAILS_ROOT+"/spec/fixtures/xero_xml_objects/address.xml")
    @address = Address.from_xml(@xml)
  end

  it "should have correct type" do
    @address.addresstype.should=="POBOX"
  end
  it "should have correct address line 1" do
    @address.line1.should=="line 1"
  end
  it "should have correct address line 2" do
    @address.line2.should=="line 2"
  end
  it "should have correct address line 3" do
    @address.line3.should=="line 3 text"
  end
  it "should have correct address line 4" do
    @address.line4.should=="line 4"
  end
  it  "should have correct city" do
    @address.city.should=="Wellington"
  end
  it "should have correct region" do
    @address.region.should=="Region"
  end
  it "should have correct postal code" do
    @address.postal_code.should=="6011"
  end
  it "should have correct country" do
    @address.country.should=="COUNTRY/NZ"
  end
  
end

phone_spec.rb

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Phone do
  
   before(:each) do
    @xml = File.read(RAILS_ROOT+"/spec/fixtures/xero_xml_objects/phone.xml")
    @phone = Phone.from_xml(@xml)
  
  end

  it "should have correct type" do
    @phone.phone_type.should == "DDI"
  end
  it "should have correct number" do
    @phone.number.should =="1223 (2304)"
  end
  it "should have correct area code" do
    @phone.area_code.should == "(1023)"
  end
  it "should have correct country code" do
    @phone.country_code.should == "10"
  end
end

contact_spec.rb

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Contact do
  before(:each) do
    @xml = File.read(RAILS_ROOT+"/spec/fixtures/xero_xml_objects/contact.xml")
    @contact = Contact.from_xml(@xml)
  end

  it "should have the correct name" do
    @contact.name.should == "A. Dutchess" 
  end
  it "should have the correct xero id" do
    @contact.xero_id.should == "bd2270c3-8706-4c11-9cfb-000b551c3f51"
  end
  it "should have the correct status" do
    @contact.status.should == "ACTIVE"
  end
  it "should have the correct email address" do
    @contact.email_address.should == "user@domain.com"
  end
  it "should have the correct updated date" do
    @contact.updated_date_utc.should == DateTime.parse("2008-07-12T01:44:26.747")
  end
  it "should have the correct number of addresses" do
    @contact.addresses.size.should == 1
  end
  it "should have the correct address" do
    @contact.addresses[0].line1.should == "P O Box 123"
  end
  it "should have the correct number of phone numbers" do
    @contact.phones.size.should == 4
  end
  it "should populate phone data correctly" do
    @contact.phones[0].number.should == "12345690"
  end
  describe "fetch, specifying an id" do
    before(:each) do
      @xero_contact_id="bd2270c3-8706-4c11-9cfb-000b551c3f51"
      @base_url="http://networkurl"
      @api_key="B5920D82BC0A40C89C16E8E2217875"
      @customer_key = "BF7BC56EE7D9457BB6F7685B00A955"
      @params={
      :x ero_id => @xero_contact_id,
      :url => @base_url,
      :api_key=>@api_key,
      :customer_key=>@customer_key
      }
      xml=File.read(RAILS_ROOT+"/spec/fixtures/xero_api_responses/get_contact_response.xml")
      Contact.should_receive(:open).with("http://networkurl/contact?contactID=bd2270c3-8706-4c11-9cfb-000b551c3f51&apiKey=B5920D82BC0A40C89C16E8E2217875&xeroKey=BF7BC56EE7D9457BB6F7685B00A955").and_return(xml)
      @contact = Contact.fetch(@params)
      
    end
    it "should fetch the correct contact" do
      @contact.xero_id.should == "bd2270c3-8706-4c11-9cfb-000b551c3f51"
    end
    it "should populate addresses" do
      @contact.addresses.size.should == 1
    end
    it "should populate phone numbers" do
      @contact.phones.size.should == 4
    end
    
  end
  describe "fetch without specifying a contact id should fetch all" do
     before(:each) do
      @base_url="http://networkurl"
      @api_key="B5920D82BC0A40C89C16E8E2217875"
      @customer_key = "BF7BC56EE7D9457BB6F7685B00A955"
      @params={
      :url => @base_url,
      :api_key=>@api_key,
      :customer_key=>@customer_key
      }
      xml=File.read(RAILS_ROOT+"/spec/fixtures/xero_api_responses/get_contacts_response.xml")
      Contact.should_receive(:open).with("http://networkurl/contacts?apiKey=B5920D82BC0A40C89C16E8E2217875&xeroKey=BF7BC56EE7D9457BB6F7685B00A955").and_return(xml)
      @contacts = Contact.fetch(@params)
    end
    it "should fetch the correct number of contacts" do
      @contacts.size.should == 63
    end
  end
end

Wow that’s a lot of code for one post and may not make sense, as such i’m going to see if Xero will let me borrow some equipment and record a screen cast to show how i got to this point from the beginning.

Attached is the full rails project for this so you can peruse at your leisure.

If you’re interested in becoming a Xero Network partner and have a compelling solution, check out http://network.xero.com and apply to become part of our beta program.

Next I’ll go through modifying and adding a new contact to Xero

then the same treatment for invoices.

Also I want to try this out in other languages, might have to dust of my Java as it’s been a while, are there any other examples people want?

Source Files

Sphere: Related Content

Posted in: [||].

kick it on DotNetKicks.com

Shout it

  • I really don't have time to look much deeper, but you may be missing some foreign keys in your migrations.
  • true phone and addresses need to hold the contact id.

    Anyway in my follow up post with HTTParty you don't need to rely on these migrations and the API is much much easier.
  • Hey, thanks for this. I look forward to making use of it once we're ready to integrate with Xero.

    Cheers, Galen
  • Tim
    I've taken a slightly different approach and written a xero_gateway GEM.

    You can do stuff like
    result xero_gateway.get_invoice("INV-0001")
    if result.success?
    result.invoice.phone.number
    end

    The GEM can be found here
    http://github.com/tlconnor/xero_gateway
blog comments powered by Disqus