spohlenz-freshbooksrb 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
1
+ require 'httparty'
2
+ require 'builder'
3
+
4
+ module FreshBooks
5
+ class APIError < Exception; end
6
+
7
+ class API
8
+ include HTTParty
9
+
10
+ format :xml
11
+
12
+ def self.call(method, params={})
13
+ result = post('/xml-in', :body => build_request(method, params))['response']
14
+
15
+ if result['status'] == 'ok'
16
+ result
17
+ else
18
+ raise APIError.new(result['error'])
19
+ end
20
+ end
21
+
22
+ def self.configure(url, token)
23
+ self.base_uri "#{url}:443/api/2.1"
24
+ self.basic_auth token, 'X'
25
+ end
26
+
27
+ private
28
+ def self.build_request(method, params)
29
+ returning '' do |result|
30
+ xml = Builder::XmlMarkup.new(:target => result)
31
+ xml.instruct!
32
+ xml.request(:method => method) { build_xml(xml, params) }
33
+ end
34
+ end
35
+
36
+ def self.build_xml(xml, val={})
37
+ val.each do |k, v|
38
+ if v.is_a?(Hash)
39
+ xml.tag!(k) { build_xml(xml, v) }
40
+ elsif v.is_a?(Array)
41
+ xml.tag!(k) {
42
+ v.each { |i| xml.tag!(k.to_s.singularize, i) }
43
+ }
44
+ else
45
+ xml.tag!(k, v)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,105 @@
1
+ module FreshBooks
2
+ class Base
3
+ def initialize(attributes={})
4
+ @attributes = attributes.inject({}) { |result, pair|
5
+ result[pair.first] = self.class.normalize(*pair)
6
+ result
7
+ }
8
+ end
9
+
10
+ def method_missing(method, *args, &block)
11
+ if @attributes.has_key?(method.to_s)
12
+ @attributes[method.to_s]
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def id
19
+ @attributes[self.class.id_field]
20
+ end
21
+
22
+ def self.map(mappings={})
23
+ @mappings ||= {}
24
+ @mappings.merge!(mappings)
25
+ end
26
+
27
+ # API methods
28
+
29
+ def self.create(params={})
30
+ call_api('create', params)[id_field]
31
+ end
32
+
33
+ def self.update(id, params={})
34
+ call_api_with_id('update', id, params)
35
+ end
36
+
37
+ def self.get(id)
38
+ result = call_api_with_id('get', id)
39
+ new(result[prefix]) if result[prefix]
40
+ end
41
+
42
+ def self.delete(id)
43
+ call_api_with_id('delete', id)
44
+ end
45
+
46
+ def self.list(params={})
47
+ result = call_api('list', params)
48
+ if result[prefix.pluralize] && result[prefix]
49
+ result[prefix.pluralize][prefix].map { |p| new(p) }
50
+ else
51
+ []
52
+ end
53
+ end
54
+
55
+ def self.has_many(model)
56
+ class_eval <<-EOF
57
+ def #{model}(params={})
58
+ FreshBooks::#{model.to_s.classify}.list(params.merge(self.class.id_field => id))
59
+ end
60
+ EOF
61
+ end
62
+
63
+ def self.belongs_to(model)
64
+ class_eval <<-EOF
65
+ def #{model}
66
+ FreshBooks::#{model.to_s.classify}.get(#{model}_id)
67
+ end
68
+ EOF
69
+ end
70
+
71
+ private
72
+ def self.call_api_with_id(method, id, params={})
73
+ call_api(method, params.merge(id_field => id))
74
+ end
75
+
76
+ def self.call_api(method, params={})
77
+ API.call("#{prefix}.#{method}", params)
78
+ end
79
+
80
+ def self.normalize(key, value)
81
+ return value.to_i if key.ends_with?('_id')
82
+
83
+ case @mappings[key.to_sym]
84
+ when :integer
85
+ value.to_i
86
+ when :float
87
+ value.to_f
88
+ when :date
89
+ Date.parse(value)
90
+ when :boolean
91
+ value == '1'
92
+ else
93
+ value
94
+ end
95
+ end
96
+
97
+ def self.prefix
98
+ self.name.demodulize.underscore
99
+ end
100
+
101
+ def self.id_field
102
+ "#{prefix}_id"
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,117 @@
1
+ require File.dirname(__FILE__) + '/freshbooks/api'
2
+ require File.dirname(__FILE__) + '/freshbooks/base'
3
+
4
+ module FreshBooks
5
+
6
+ # Must be called before invoking API methods
7
+
8
+ def self.setup(url, token)
9
+ @url = url
10
+ API.configure(url, token)
11
+ end
12
+
13
+ def self.url
14
+ @url
15
+ end
16
+
17
+ # Models exposed by FreshBooks API
18
+
19
+ class Client < Base
20
+ has_many :recurrings
21
+ has_many :invoices
22
+ has_many :estimates
23
+ has_many :payments
24
+ end
25
+
26
+ class Invoice < Base
27
+ map :date => :date,
28
+ :amount => :float,
29
+ :discount => :float
30
+
31
+ has_many :payments
32
+ belongs_to :client
33
+
34
+ def send_by_email
35
+ self.class.send_by_email(invoice_id)
36
+ end
37
+
38
+ def send_by_snail_mail
39
+ self.class.send_by_snail_mail(invoice_id)
40
+ end
41
+
42
+ def self.send_by_email(id)
43
+ call_api_with_id('sendByEmail', id)
44
+ end
45
+
46
+ def self.send_by_snail_mail(id)
47
+ call_api_with_id('sendBySnailMail', id)
48
+ end
49
+ end
50
+
51
+ class Estimate < Base
52
+ map :date => :date,
53
+ :amount => :float,
54
+ :discount => :float
55
+
56
+ belongs_to :client
57
+
58
+ def send_by_email
59
+ self.class.send_by_email(invoice_id)
60
+ end
61
+
62
+ def self.send_by_email(id)
63
+ call_api_with_id('sendByEmail', id)
64
+ end
65
+ end
66
+
67
+ class Recurring < Base
68
+ map :date => :date,
69
+ :discount => :float,
70
+ :send_email => :boolean,
71
+ :send_snail_mail => :boolean
72
+
73
+ has_many :invoices
74
+ belongs_to :client
75
+ end
76
+
77
+ class Item < Base
78
+ map :unit_cost => :float,
79
+ :quantity => :integer,
80
+ :inventory => :integer
81
+ end
82
+
83
+ class Payment < Base
84
+ map :date => :date,
85
+ :amount => :float
86
+
87
+ belongs_to :client
88
+ belongs_to :invoice
89
+
90
+ def self.delete(id)
91
+ raise 'Payments may not be deleted'
92
+ end
93
+ end
94
+
95
+ class TimeEntry < Base
96
+ map :hours => :float,
97
+ :date => :date
98
+
99
+ belongs_to :project
100
+ belongs_to :task
101
+ end
102
+
103
+ class Project < Base
104
+ map :rate => :float
105
+
106
+ has_many :time_entries
107
+ has_many :tasks
108
+ end
109
+
110
+ class Task < Base
111
+ map :rate => :float,
112
+ :billable => :boolean
113
+
114
+ has_many :time_entries
115
+ end
116
+
117
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spohlenz-freshbooksrb
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.3"
5
+ platform: ruby
6
+ authors:
7
+ - Sam Pohlenz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: builder
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.1.2
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: activesupport
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 2.1.0
32
+ version:
33
+ - !ruby/object:Gem::Dependency
34
+ name: httparty
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.2
41
+ version:
42
+ description: FreshBooksRb is a Ruby library for integrating your apps with the FreshBooks API (http://developers.freshbooks.com/overview/)
43
+ email: sam@sampohlenz.com
44
+ executables: []
45
+
46
+ extensions: []
47
+
48
+ extra_rdoc_files: []
49
+
50
+ files:
51
+ - lib/freshbooks/base.rb
52
+ - lib/freshbooks/api.rb
53
+ - lib/freshbooksrb.rb
54
+ has_rdoc: false
55
+ homepage: http://github.com/spohlenz/freshbooksrb
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.2.0
77
+ signing_key:
78
+ specification_version: 2
79
+ summary: Ruby client for FreshBooks
80
+ test_files: []
81
+