Skip to content

Instantly share code, notes, and snippets.

@lazyatom
Created January 24, 2010 23:08
Show Gist options
  • Save lazyatom/285502 to your computer and use it in GitHub Desktop.
Save lazyatom/285502 to your computer and use it in GitHub Desktop.
require 'wrest'
module FreeAgent
module Associations
HEADERS = {"Content-Type" => "application/xml", "Accept" => "application/xml"}
def self.included(base)
base.extend(ClassMethods)
end
def get(path, kind, klass)
attributes = uri[path].get({}, HEADERS).deserialise[kind]
klass.new(attributes, base)
end
def get_collection(path, collection_name, klass)
members = uri[path].get({}, HEADERS).deserialise[collection_name]
instances = members.map { |i| klass.new(i, self) }
p = Proxy.new(instances)
p.uri = uri[path]
p.klass = klass
p.base = self
p
end
class Proxy < Array
attr_accessor :uri, :klass, :base
def with_id(id)
find { |i| i.id == id }
end
def create(attributes)
attributes.keys.each { |k| attributes[k] = attributes[k].to_s.humanize if attributes[k].is_a?(Symbol) }
instance = klass.new(attributes, base)
response = uri.post(instance.to_xml, HEADERS).deserialise
if response[klass]
instance
end
end
module ClassMethods
def has_many(collection, options={})
define_method(collection) do
klass = "FreeAgent::#{collection.to_s.classify}".constantize
get_collection("#{root}#{collection}", collection.to_s, klass)
end
end
def belongs_to(thing, options={})
key = options[:key] || :"#{thing}_id"
define_method(thing) do
klass = "FreeAgent::#{thing.to_s.classify}".constantize
get("/#{thing}s/#{@attributes[key]}", thing.to_s, klass)
end
end
end
end
class Base
include Wrest::Components::Container
include Associations
attr_reader :base, :attributes
always_has :id
typecast :id => as_integer
def initialize(attributes, base)
@base = base
super(attributes)
end
protected
def root
# script out 'FreeAgent::'
path = self.class.name.split("::")[1..-1].join("::").underscore.pluralize
"/#{path}/#{id}/"
end
def uri
@uri || @base.uri
end
end
class Company < Base
def root
"/"
end
has_many :contacts
has_many :projects
has_many :invoices
has_many :timeslips
has_many :attachments
has_many :users
# has_many :bills
def initialize(options)
@site = options.delete(:site)
@uri = "https://#{@site}.freeagentcentral.com".to_uri(options)
end
def invoice_timeline
get_collection('/company/invoice_timeline', 'invoice_timeline_items', Invoice)
end
def tax_timeline
get_collection('/company/tax_timeline', 'timeline_items', TaxItem)
end
end
class TaxItem < Base
end
class Invoice < Base
has_many :invoice_items
belongs_to :project
belongs_to :contact
end
class InvoiceItem < Base
end
class Bill < Base
belongs_to :contact
belongs_to :project, :key => :rebilled_to_project_id
def invoice_item
project.invoice_item[rebilled_on_invoice_item]
end
end
class Contact < Base
has_many :invoices
end
class Timeslip < Base
belongs_to :project
belongs_to :user
end
class Task < Base
belongs_to :project
end
class Project < Base
has_many :tasks
has_many :invoices
has_many :timeslips
belongs_to :contact
end
class Expenses < Base
belongs_to :user
end
class User < Base
has_many :expenses
end
class Attachment < Base
end
end
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. wrest lib])
require 'free_agent'
$:.unshift File.join(File.dirname(__FILE__), *%w[.. webmock lib])
require 'webmock/test_unit'
require 'rubygems'
require 'shoulda'
Wrest.logger = Logger.new("/dev/null")
class FreeAgentTest < Test::Unit::TestCase
include WebMock
context "Given a FreeRange company" do
setup do
@config = {:site => 'mycompany'}
@company = FreeAgent::Company.new(@config)
end
context "when creating a new project" do
should "work" do
new_project_xml = %{
<?xml version="1.0" encoding="UTF-8"?>
<project>
<contact-id type="integer">26</contact-id>
<name>Website redesign</name>
<billing-basis type="decimal">1</billing-basis>
<budget type="integer">0</budget>
<budget-units>Hours</budget-units>
<status>Active</status>
</project>
}
stub_http("https://mycompany.freeagentcentral.com/projects", projects_xml([]))
stub_http("https://mycompany.freeagentcentral.com/projects", new_project_xml, :post)
project = @company.projects.create(:contact_id => 26, :name => "Website redesign", :billing_basis => 1,
:budget_units => :hours, :status => :active)
assert_kind_of FreeAgent::Project, project
end
end
context "when loading projects" do
setup do
stub_http("https://mycompany.freeagentcentral.com/projects", projects_xml([{:id => 123}]))
end
should "return all projects" do
projects = @company.projects
assert_kind_of FreeAgent::Project, projects.first
assert_equal 123, projects.first.id
end
should "be able to load project invoices" do
stub_http("https://mycompany.freeagentcentral.com/projects/123/invoices", invoices_xml)
invoices = @company.projects.first.invoices
assert_kind_of FreeAgent::Invoice, invoices.first
end
end
end
private
def stub_http(uri, body, method = :get)
stub_request(method, uri).with(:headers => {"Content-Type" => "application/xml", "Accept" => "application/xml"}).to_return(:body => body, :headers => {"Content-Type" => "application/xml"})
end
def projects_xml(attribute_array=[{}])
%{<?xml version="1.0" encoding="UTF-8"?>
<projects type="array">
#{attribute_array.map { |a| project_xml(a) }}
</projects>}
end
def project_xml(attributes)
%{<project>
<id type="integer">#{attributes[:id] || 20}</id>
<contact-id type="integer">26</contact-id>
<name>Website redesign</name>
<billing-basis type="decimal">1</billing-basis>
<budget type="integer">0</budget>
<budget-units>Hours</budget-units>
<invoicing-reference></invoicing-reference>
<is-ir35 type="boolean">false</is-ir35>
<normal-billing-rate type="decimal">0.0</normal-billing-rate>
<starts-on type="datetime" nil="true">2006-09-01T00:00:00Z</starts-on>
<ends-on type="datetime" nil="true">2006-12-31T00:00:00Z</ends-on>
<status>Active</status>
<uses-project-invoice-sequence type="boolean">false</uses-project-invoice-sequence>
</project>}
end
def invoices_xml
%{<invoices type="array">
<invoice>
<id type="integer">85</id>
<contact-id type="integer">10</contact-id>
<project-id type="integer">16</project-id>
<dated-on type="datetime">2008-02-17T00:00:00Z</dated-on>
<reference>Abstract Web Design 001</reference>
<net-value type="decimal">1000.0</net-value>
<sales-tax-value type="decimal">0.0</sales-tax-value>
<status>Sent</status>
<comments></comments>
<discount-percent type="decimal" nil="true"></discount-percent>
<omit-header type="boolean">false</omit-header>
<payment-terms></payment-terms>
<written-off-date type="datetime" nil="true"></written-off-date>
<invoice-items type="array">
<invoice-item>
<id type="integer">607</id>
<invoice-id type="integer">85</invoice-id>
<project-id type="integer" nil="true"></project-id>
<item-type>Services</item-type>
<description>Setting up hosting</description>
<price type="decimal">160.0</price>
<quantity type="decimal">1.0</quantity>
<sales-tax-rate type="decimal">17.5</sales-tax-rate>
<sales-tax-value type="float">175.0</sales-tax-value>
</invoice-item>
<invoice-item>
<id type="integer">611</id>
<invoice-id type="integer">85</invoice-id>
<project-id type="integer" nil="true"></project-id>
<item-type>Hours</item-type>
<description>Coding the templates</description>
<price type="decimal">40</price>
<quantity type="decimal">30</quantity>
<sales-tax-rate type="decimal">17.5</sales-tax-rate>
<sales-tax-value type="float">0.0</sales-tax-value>
</invoice-item>
</invoice-items>
</invoice>
</invoices>}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment