Created
January 24, 2010 23:08
-
-
Save lazyatom/285502 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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