turingstudio-freshbooks-rb 3.0.1 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/lib/freshbooks.rb +4 -0
- data/lib/freshbooks/base.rb +109 -183
- data/lib/freshbooks/category.rb +3 -3
- data/lib/freshbooks/client.rb +2 -2
- data/lib/freshbooks/estimate.rb +19 -24
- data/lib/freshbooks/expense.rb +2 -2
- data/lib/freshbooks/invoice.rb +2 -7
- data/lib/freshbooks/item.rb +1 -1
- data/lib/freshbooks/line.rb +1 -1
- data/lib/freshbooks/payment.rb +2 -2
- data/lib/freshbooks/project.rb +2 -7
- data/lib/freshbooks/recurring.rb +5 -9
- data/lib/freshbooks/staff.rb +5 -5
- data/lib/freshbooks/task.rb +2 -2
- data/lib/freshbooks/time_entry.rb +2 -2
- data/lib/freshbooks/version.rb +1 -1
- data/spec/lib/freshbooks/base_spec.rb +59 -26
- data/spec/lib/freshbooks/time_entry_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- metadata +5 -6
data/README.rdoc
CHANGED
@@ -10,7 +10,7 @@ A Ruby interface to the FreshBooks API. It exposes easy-to-use classes and metho
|
|
10
10
|
|
11
11
|
Initialization:
|
12
12
|
|
13
|
-
FreshBooks::Base.
|
13
|
+
FreshBooks::Base.establish_connection!('sample.freshbooks.com', 'mytoken')
|
14
14
|
|
15
15
|
Updating a client name:
|
16
16
|
|
data/lib/freshbooks.rb
CHANGED
@@ -24,6 +24,7 @@ require 'rexml/document'
|
|
24
24
|
require 'activesupport'
|
25
25
|
|
26
26
|
require 'freshbooks/version'
|
27
|
+
require 'freshbooks/connection'
|
27
28
|
require 'freshbooks/response'
|
28
29
|
require 'freshbooks/base'
|
29
30
|
require 'freshbooks/category'
|
@@ -33,9 +34,12 @@ require 'freshbooks/expense'
|
|
33
34
|
require 'freshbooks/invoice'
|
34
35
|
require 'freshbooks/item'
|
35
36
|
require 'freshbooks/line'
|
37
|
+
require 'freshbooks/links'
|
38
|
+
require 'freshbooks/list_proxy'
|
36
39
|
require 'freshbooks/payment'
|
37
40
|
require 'freshbooks/project'
|
38
41
|
require 'freshbooks/recurring'
|
39
42
|
require 'freshbooks/staff'
|
40
43
|
require 'freshbooks/task'
|
41
44
|
require 'freshbooks/time_entry'
|
45
|
+
require 'freshbooks/xml_serializer'
|
data/lib/freshbooks/base.rb
CHANGED
@@ -4,140 +4,82 @@ module FreshBooks
|
|
4
4
|
class AuthenticationError < Exception; end;
|
5
5
|
class UnknownSystemError < Exception; end;
|
6
6
|
class InvalidParameterError < Exception; end;
|
7
|
+
class InvalidAccountUrlError < Exception; end;
|
7
8
|
|
8
|
-
attr_accessor :resp
|
9
|
-
|
10
9
|
def initialize(data = {})
|
11
|
-
@old_values = {}
|
12
10
|
@changed_fields = []
|
11
|
+
self.class.attributes.each do |name, options|
|
12
|
+
send("#{name}=", []) if options[:type] == :array
|
13
|
+
end
|
13
14
|
data.each do |name, value|
|
14
15
|
send("#{name}=", value) if self.class.attributes[name]
|
15
16
|
end
|
17
|
+
#@changed_fields = [] # reset changed fields
|
16
18
|
end
|
17
|
-
|
18
|
-
# Convert an instance of this class to an XML element
|
19
|
+
|
19
20
|
def to_xml(elem_name = nil)
|
20
21
|
root = REXML::Element.new(node_name)
|
21
|
-
|
22
22
|
self.class.attributes.each do |name, options|
|
23
|
-
next if options[:read_only] || !send("#{name}_changed?")
|
24
|
-
|
25
23
|
value = send(name)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
node.add_element(item.to_xml)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
elsif !value.nil?
|
37
|
-
root.add_element(name.to_s).text = value
|
24
|
+
next if options[:read_only] || !send("#{name}_changed?") || value.nil?
|
25
|
+
|
26
|
+
if options[:to_xml]
|
27
|
+
send(options[:to_xml], root)
|
28
|
+
else
|
29
|
+
element = XmlSerializer.to_node(name.to_s, value, options[:type])
|
30
|
+
root.add_element(element) if element != nil
|
38
31
|
end
|
39
32
|
end
|
40
|
-
|
41
33
|
root
|
42
34
|
end
|
43
35
|
|
44
|
-
def create
|
45
|
-
self.class.ensure_allowed! :create
|
46
|
-
resp = Base.call_api("#{node_name}.create", node_name => self)
|
47
|
-
if resp.success?
|
48
|
-
self.send("#{node_name}_id=", resp.elements[1].text.to_i)
|
49
|
-
end
|
50
|
-
resp.success? ? self.send("#{node_name}_id") : nil
|
51
|
-
end
|
52
|
-
|
53
|
-
def update
|
54
|
-
self.class.ensure_allowed! :update
|
55
|
-
resp = Base.call_api("#{node_name}.update", node_name => self)
|
56
|
-
resp.success?
|
57
|
-
end
|
58
|
-
|
59
|
-
def delete
|
60
|
-
self.class.delete(self.send("#{node_name}_id"))
|
61
|
-
end
|
62
|
-
|
63
|
-
def send_by_email
|
64
|
-
self.class.send_by_email(self.send("#{node_name}_id"))
|
65
|
-
end
|
66
|
-
|
67
|
-
def send_by_snail_mail
|
68
|
-
self.class.send_by_snail_mail(self.send("#{node_name}_id"))
|
69
|
-
end
|
70
|
-
|
71
|
-
def node_name
|
72
|
-
self.class.node_name
|
73
|
-
end
|
74
|
-
|
75
36
|
class << self
|
76
|
-
|
77
|
-
@@account_url = account_url
|
78
|
-
@@auth_token = auth_token
|
79
|
-
@@request_headers = request_headers
|
80
|
-
@@response = nil
|
81
|
-
end
|
37
|
+
attr_reader :connection
|
82
38
|
|
83
|
-
def
|
84
|
-
|
85
|
-
resp = Base.call_api("#{node_name}.list", options)
|
86
|
-
return nil unless resp.success?
|
87
|
-
|
88
|
-
elems = resp.elements[1].elements
|
89
|
-
elems.map { |elem| self.new_from_xml(elem) }
|
39
|
+
def establish_connection!(account_url, auth_token, request_headers = {})
|
40
|
+
@connection = Connection.new(account_url, auth_token, request_headers)
|
90
41
|
end
|
91
42
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
43
|
+
def new_from_xml(xml_root)
|
44
|
+
object = self.new
|
45
|
+
attributes.each do |name, options|
|
46
|
+
node = xml_root.elements[name.to_s]
|
47
|
+
next if node.nil?
|
48
|
+
|
49
|
+
value = FreshBooks::XmlSerializer.to_value(node, options[:type])
|
50
|
+
object.send("#{name}=", value)
|
51
|
+
end
|
52
|
+
object
|
96
53
|
end
|
97
54
|
|
98
|
-
def delete(id)
|
99
|
-
ensure_allowed! :delete
|
100
|
-
resp = Base.call_api("#{node_name}.delete", "#{node_name}_id" => id)
|
101
|
-
resp.success?
|
102
|
-
end
|
103
|
-
|
104
|
-
def send_by_email(id)
|
105
|
-
ensure_allowed! :send_by_email
|
106
|
-
resp = Base.call_api("#{node_name}.sendByEmail", "#{node_name}_id" => id)
|
107
|
-
resp.success?
|
108
|
-
end
|
109
|
-
|
110
|
-
def send_by_snail_mail(id)
|
111
|
-
ensure_allowed! :send_by_snail_mail
|
112
|
-
resp = Base.call_api("#{node_name}.sendBySnailMail", "#{node_name}_id" => id)
|
113
|
-
resp.success?
|
114
|
-
end
|
115
|
-
|
116
55
|
def node_name
|
117
56
|
self.to_s.split('::').last.underscore
|
118
57
|
end
|
119
58
|
|
120
59
|
#
|
121
|
-
# Defines structure attribute. Generates attr_accessor and #{name}_changed? method
|
60
|
+
# Defines structure attribute. Generates attr_accessor and #{name}_changed? method.
|
61
|
+
# Options are:
|
62
|
+
# :read_only - specifies that attribute is read-only and attr_writer is protected
|
63
|
+
# :to_xml - overrides xml generation for this attribute
|
122
64
|
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
65
|
+
# class Test < Base
|
66
|
+
# attribute :test_id, :integer
|
67
|
+
# end
|
126
68
|
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
69
|
+
# test = Test.new
|
70
|
+
# test.test_id_changed? # false
|
71
|
+
# test.test_id = 1
|
72
|
+
# test.test_id_changed? # true
|
73
|
+
# test.test_id # 1
|
132
74
|
#
|
133
75
|
def attribute(name, type, options = {})
|
134
76
|
self.attributes[name] = options.update(:type => type)
|
135
77
|
|
136
78
|
define_method("#{name}=") do |value|
|
137
79
|
@changed_fields << name
|
138
|
-
@old_values[name] = send(name) if @old_values[name].nil?
|
139
80
|
instance_variable_set("@#{name}", value)
|
140
81
|
end
|
82
|
+
self.send(:protected, "#{name}=") if options[:read_only]
|
141
83
|
|
142
84
|
define_method("#{name}_changed?") do
|
143
85
|
@changed_fields.include?(name)
|
@@ -179,108 +121,92 @@ module FreshBooks
|
|
179
121
|
# Defines allowed API methods.
|
180
122
|
#
|
181
123
|
# class Test < Base
|
182
|
-
#
|
124
|
+
# actions :list
|
183
125
|
# end
|
184
126
|
#
|
185
127
|
# Test.list # [...]
|
186
128
|
# Test.get(1) # "Error: Method get is not allowed for Test"
|
187
129
|
#
|
188
|
-
def
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
|
-
def methods
|
193
|
-
@methods ||= []
|
194
|
-
end
|
195
|
-
|
196
|
-
def ensure_allowed!(name)
|
197
|
-
unless methods.include?(name)
|
198
|
-
raise "Error: Method #{name} is not allowed for #{self}"
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Create a new instance of this class from an XML element
|
203
|
-
def new_from_xml(xml_root)
|
204
|
-
object = self.new
|
205
|
-
attributes.each do |name, options|
|
206
|
-
node = xml_root.elements[name.to_s]
|
207
|
-
next if node.nil?
|
130
|
+
def define_class_method(name, &block)
|
131
|
+
self.class.send(:define_method, name, &block)
|
132
|
+
end
|
208
133
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
value = node.text.to_f
|
214
|
-
when :array
|
215
|
-
value = node.elements.map do |item|
|
216
|
-
FreshBooks::const_get(item.name.classify)::new_from_xml(item)
|
217
|
-
end
|
218
|
-
else
|
219
|
-
value = node.text.to_s
|
220
|
-
end
|
221
|
-
|
222
|
-
object.send("#{name}=", value)
|
223
|
-
end
|
224
|
-
object
|
225
|
-
end
|
226
|
-
|
227
|
-
def call_api(method, elems = [])
|
228
|
-
doc = REXML::Document.new '<?xml version="1.0" encoding="UTF-8"?>'
|
229
|
-
request = doc.add_element 'request'
|
230
|
-
request.attributes['method'] = method
|
134
|
+
def actions(*args)
|
135
|
+
args.each do |action|
|
136
|
+
method = action.to_s
|
137
|
+
api_method = method.camelize(:lower)
|
231
138
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
139
|
+
case method
|
140
|
+
when 'list'
|
141
|
+
define_class_method(method) do |*args|
|
142
|
+
api_list_action(api_method, *args)
|
143
|
+
end
|
144
|
+
when 'get'
|
145
|
+
define_class_method(method) do |id|
|
146
|
+
api_get_action(api_method, id)
|
147
|
+
end
|
148
|
+
when 'create'
|
149
|
+
define_method(method) do
|
150
|
+
api_create_action(api_method)
|
151
|
+
end
|
152
|
+
when 'update'
|
153
|
+
define_method(method) do
|
154
|
+
api_update_action(api_method)
|
155
|
+
end
|
156
|
+
when 'delete'
|
157
|
+
define_class_method(method) do |id|
|
158
|
+
api_delete_action(api_method, id)
|
159
|
+
end
|
160
|
+
define_method(method) do
|
161
|
+
self.class.api_delete_action(api_method, self.send("#{node_name}_id"))
|
162
|
+
end
|
236
163
|
else
|
237
|
-
|
164
|
+
define_class_method(method) do |id|
|
165
|
+
api_action(api_method, id)
|
166
|
+
end
|
167
|
+
define_method(method) do
|
168
|
+
self.class.api_action(api_method, self.send("#{node_name}_id"))
|
169
|
+
end
|
238
170
|
end
|
239
171
|
end
|
240
|
-
|
241
|
-
result = self.post(request.to_s)
|
242
172
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
# Failure
|
247
|
-
#
|
248
|
-
if @@response.fail?
|
249
|
-
error_msg = @@response.error_msg
|
250
|
-
|
251
|
-
raise InternalError.new, error_msg if error_msg =~ /not formatted correctly/
|
252
|
-
raise AuthenticationError.new, error_msg if error_msg =~ /[Aa]uthentication failed/
|
253
|
-
raise UnknownSystemError.new, error_msg if error_msg =~ /does not exist/
|
254
|
-
raise InvalidParameterError.new, error_msg if error_msg =~ /Invalid parameter: (.*)/
|
255
|
-
|
256
|
-
# Raise an exception for unexpected errors
|
257
|
-
raise error_msg
|
173
|
+
def api_list_action(method, options = {})
|
174
|
+
resp = Base.connection.call_api("#{node_name}.#{method}", options)
|
175
|
+
resp.success? ? ListProxy.new_from_xml(self, resp) : nil
|
258
176
|
end
|
259
177
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
def response
|
264
|
-
@@response
|
265
|
-
end
|
266
|
-
|
267
|
-
def post(body)
|
268
|
-
connection = Net::HTTP.new(@@account_url, 443)
|
269
|
-
connection.use_ssl = true
|
270
|
-
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
271
|
-
|
272
|
-
request = Net::HTTP::Post.new(FreshBooks::SERVICE_URL)
|
273
|
-
request.basic_auth @@auth_token, 'X'
|
274
|
-
request.body = body
|
275
|
-
request.content_type = 'application/xml'
|
276
|
-
@@request_headers.each_pair do |name, value|
|
277
|
-
request[name.to_s] = value
|
178
|
+
def api_get_action(method, id)
|
179
|
+
resp = Base.connection.call_api("#{node_name}.#{method}", "#{node_name}_id" => id)
|
180
|
+
resp.success? ? self.new_from_xml(resp.elements[1]) : nil
|
278
181
|
end
|
279
182
|
|
280
|
-
|
183
|
+
def api_delete_action(method, id)
|
184
|
+
resp = Base.connection.call_api("#{node_name}.#{method}", "#{node_name}_id" => id)
|
185
|
+
resp.success?
|
186
|
+
end
|
281
187
|
|
282
|
-
|
188
|
+
def api_action(method, id)
|
189
|
+
resp = Base.connection.call_api("#{node_name}.#{method}", "#{node_name}_id" => id)
|
190
|
+
resp.success?
|
191
|
+
end
|
283
192
|
end
|
284
193
|
end
|
194
|
+
|
195
|
+
protected
|
196
|
+
|
197
|
+
def node_name
|
198
|
+
self.class.node_name
|
199
|
+
end
|
200
|
+
|
201
|
+
def api_create_action(method)
|
202
|
+
resp = Base.connection.call_api("#{node_name}.#{method}", node_name => self)
|
203
|
+
self.send("#{node_name}_id=", resp.elements[1].text.to_i) if resp.success?
|
204
|
+
resp.success?
|
205
|
+
end
|
206
|
+
|
207
|
+
def api_update_action(method)
|
208
|
+
resp = Base.connection.call_api("#{node_name}.#{method}", node_name => self)
|
209
|
+
resp.success?
|
210
|
+
end
|
285
211
|
end
|
286
|
-
end
|
212
|
+
end
|
data/lib/freshbooks/category.rb
CHANGED
@@ -2,10 +2,10 @@ module FreshBooks
|
|
2
2
|
class Category < Base
|
3
3
|
attribute :category_id, :integer
|
4
4
|
attribute :name, :string
|
5
|
-
attribute :tax1, :
|
6
|
-
attribute :tax2, :
|
5
|
+
attribute :tax1, :float
|
6
|
+
attribute :tax2, :float
|
7
7
|
|
8
8
|
has_many :expenses
|
9
|
-
|
9
|
+
actions :list, :get, :create, :update, :delete
|
10
10
|
end
|
11
11
|
end
|
data/lib/freshbooks/client.rb
CHANGED
@@ -24,9 +24,9 @@ module FreshBooks
|
|
24
24
|
attribute :s_state, :string
|
25
25
|
attribute :s_country, :string
|
26
26
|
attribute :s_code, :string
|
27
|
-
attribute :
|
27
|
+
attribute :links, :object, :read_only => true
|
28
28
|
|
29
29
|
has_many :expenses, :estimates, :invoices, :payments, :projects, :recurrings
|
30
|
-
|
30
|
+
actions :list, :get, :create, :update, :delete
|
31
31
|
end
|
32
32
|
end
|
data/lib/freshbooks/estimate.rb
CHANGED
@@ -1,30 +1,25 @@
|
|
1
1
|
module FreshBooks
|
2
2
|
class Estimate < Base
|
3
|
-
attribute :estimate_id,
|
4
|
-
attribute :client_id,
|
5
|
-
attribute :status,
|
6
|
-
attribute :date,
|
7
|
-
attribute :po_number,
|
8
|
-
attribute :discount,
|
9
|
-
attribute :notes,
|
10
|
-
attribute :terms,
|
11
|
-
attribute :first_name,
|
12
|
-
attribute :last_name,
|
13
|
-
attribute :organization,
|
14
|
-
attribute :p_street1,
|
15
|
-
attribute :p_street2,
|
16
|
-
attribute :p_city,
|
17
|
-
attribute :p_state,
|
18
|
-
attribute :p_country,
|
19
|
-
attribute :p_code,
|
20
|
-
attribute :lines,
|
3
|
+
attribute :estimate_id, :integer
|
4
|
+
attribute :client_id, :integer
|
5
|
+
attribute :status, :string
|
6
|
+
attribute :date, :date
|
7
|
+
attribute :po_number, :integer
|
8
|
+
attribute :discount, :float
|
9
|
+
attribute :notes, :string
|
10
|
+
attribute :terms, :string
|
11
|
+
attribute :first_name, :string
|
12
|
+
attribute :last_name, :string
|
13
|
+
attribute :organization, :string
|
14
|
+
attribute :p_street1, :string
|
15
|
+
attribute :p_street2, :string
|
16
|
+
attribute :p_city, :string
|
17
|
+
attribute :p_state, :string
|
18
|
+
attribute :p_country, :string
|
19
|
+
attribute :p_code, :string
|
20
|
+
attribute :lines, :array
|
21
21
|
|
22
22
|
belongs_to :client
|
23
|
-
|
24
|
-
|
25
|
-
def initialize(data = {})
|
26
|
-
super
|
27
|
-
self.lines ||= []
|
28
|
-
end
|
23
|
+
actions :list, :get, :create, :update, :delete, :send_by_email
|
29
24
|
end
|
30
25
|
end
|
data/lib/freshbooks/expense.rb
CHANGED
@@ -6,11 +6,11 @@ module FreshBooks
|
|
6
6
|
attribute :project_id, :integer
|
7
7
|
attribute :client_id, :integer
|
8
8
|
attribute :amount, :float
|
9
|
-
attribute :date, :
|
9
|
+
attribute :date, :date
|
10
10
|
attribute :notes, :string
|
11
11
|
attribute :status, :string
|
12
12
|
|
13
13
|
belongs_to :staff, :category, :project, :client
|
14
|
-
|
14
|
+
actions :list, :get, :create, :update, :delete, :send_by_email
|
15
15
|
end
|
16
16
|
end
|
data/lib/freshbooks/invoice.rb
CHANGED
@@ -3,7 +3,7 @@ module FreshBooks
|
|
3
3
|
attribute :invoice_id, :integer
|
4
4
|
attribute :client_id, :integer
|
5
5
|
attribute :number, :string
|
6
|
-
attribute :date, :
|
6
|
+
attribute :date, :date
|
7
7
|
attribute :po_number, :integer
|
8
8
|
attribute :terms, :string
|
9
9
|
attribute :first_name, :string
|
@@ -24,11 +24,6 @@ module FreshBooks
|
|
24
24
|
|
25
25
|
belongs_to :client
|
26
26
|
has_many :payments
|
27
|
-
|
28
|
-
|
29
|
-
def initialize(data = {})
|
30
|
-
super
|
31
|
-
self.lines ||= []
|
32
|
-
end
|
27
|
+
actions :list, :get, :create, :update, :delete, :send_by_email, :send_by_snail_mail
|
33
28
|
end
|
34
29
|
end
|
data/lib/freshbooks/item.rb
CHANGED
data/lib/freshbooks/line.rb
CHANGED
data/lib/freshbooks/payment.rb
CHANGED
@@ -11,12 +11,12 @@ module FreshBooks
|
|
11
11
|
attribute :payment_id, :integer
|
12
12
|
attribute :client_id, :integer
|
13
13
|
attribute :invoice_id, :integer
|
14
|
-
attribute :date, :
|
14
|
+
attribute :date, :date
|
15
15
|
attribute :amount, :float
|
16
16
|
attribute :type, :string
|
17
17
|
attribute :notes, :string
|
18
18
|
|
19
19
|
belongs_to :client, :invoice
|
20
|
-
|
20
|
+
actions :list, :get, :create, :update
|
21
21
|
end
|
22
22
|
end
|
data/lib/freshbooks/project.rb
CHANGED
@@ -15,16 +15,11 @@ module FreshBooks
|
|
15
15
|
attribute :bill_method, :string
|
16
16
|
attribute :rate, :float
|
17
17
|
attribute :description, :string
|
18
|
-
attribute :tasks, :array
|
18
|
+
attribute :tasks, :array, :to_xml => :tasks_to_xml
|
19
19
|
|
20
20
|
belongs_to :client
|
21
21
|
has_many :expenses
|
22
|
-
|
23
|
-
|
24
|
-
def initialize(data = {})
|
25
|
-
super
|
26
|
-
self.tasks ||= []
|
27
|
-
end
|
22
|
+
actions :list, :get, :create, :update, :delete
|
28
23
|
|
29
24
|
protected
|
30
25
|
|
data/lib/freshbooks/recurring.rb
CHANGED
@@ -18,7 +18,7 @@ module FreshBooks
|
|
18
18
|
class Recurring < Base
|
19
19
|
attribute :recurring_id, :integer
|
20
20
|
attribute :client_id, :integer
|
21
|
-
attribute :date, :
|
21
|
+
attribute :date, :date
|
22
22
|
attribute :po_number, :integer
|
23
23
|
attribute :terms, :string
|
24
24
|
attribute :first_name, :string
|
@@ -37,15 +37,11 @@ module FreshBooks
|
|
37
37
|
attribute :notes, :string
|
38
38
|
attribute :occurrences, :integer
|
39
39
|
attribute :frequency, :string
|
40
|
-
attribute :
|
41
|
-
attribute :
|
40
|
+
attribute :stopped, :boolean
|
41
|
+
attribute :send_email, :boolean
|
42
|
+
attribute :send_snail_mail, :boolean
|
42
43
|
|
43
44
|
belongs_to :client
|
44
|
-
|
45
|
-
|
46
|
-
def initialize(data = {})
|
47
|
-
super
|
48
|
-
self.lines ||= []
|
49
|
-
end
|
45
|
+
actions :list, :get, :create, :update, :delete
|
50
46
|
end
|
51
47
|
end
|
data/lib/freshbooks/staff.rb
CHANGED
@@ -7,10 +7,10 @@ module FreshBooks
|
|
7
7
|
attribute :email, :string
|
8
8
|
attribute :business_phone, :string
|
9
9
|
attribute :mobile_phone, :string
|
10
|
-
attribute :rate, :
|
11
|
-
attribute :last_login, :
|
12
|
-
attribute :number_of_logins,:
|
13
|
-
attribute :signup_date, :
|
10
|
+
attribute :rate, :float
|
11
|
+
attribute :last_login, :date_time
|
12
|
+
attribute :number_of_logins,:integer
|
13
|
+
attribute :signup_date, :date_time
|
14
14
|
attribute :street1, :string
|
15
15
|
attribute :street2, :string
|
16
16
|
attribute :city, :string
|
@@ -19,6 +19,6 @@ module FreshBooks
|
|
19
19
|
attribute :code, :string
|
20
20
|
|
21
21
|
has_many :expenses
|
22
|
-
|
22
|
+
actions :list, :get
|
23
23
|
end
|
24
24
|
end
|
data/lib/freshbooks/task.rb
CHANGED
@@ -2,10 +2,10 @@ module FreshBooks
|
|
2
2
|
class Task < Base
|
3
3
|
attribute :task_id, :integer
|
4
4
|
attribute :name, :string
|
5
|
-
attribute :billable, :
|
5
|
+
attribute :billable, :boolean
|
6
6
|
attribute :rate, :float
|
7
7
|
attribute :description, :string
|
8
8
|
|
9
|
-
|
9
|
+
actions :list, :get, :create, :update, :delete
|
10
10
|
end
|
11
11
|
end
|
@@ -5,9 +5,9 @@ module FreshBooks
|
|
5
5
|
attribute :task_id, :integer
|
6
6
|
attribute :hours, :float
|
7
7
|
attribute :notes, :string
|
8
|
-
attribute :date, :
|
8
|
+
attribute :date, :date
|
9
9
|
|
10
10
|
belongs_to :project, :task
|
11
|
-
|
11
|
+
actions :list, :get, :create, :update, :delete
|
12
12
|
end
|
13
13
|
end
|
data/lib/freshbooks/version.rb
CHANGED
@@ -18,7 +18,7 @@ describe FreshBooks::Base do
|
|
18
18
|
|
19
19
|
class Test < FreshBooks::Base
|
20
20
|
attribute :test_id, :integer
|
21
|
-
|
21
|
+
actions :list, :get, :create, :update, :delete, :send_by_email, :send_by_snail_mail
|
22
22
|
end
|
23
23
|
|
24
24
|
class TestWithoutMethods < FreshBooks::Base
|
@@ -116,7 +116,7 @@ describe FreshBooks::Base do
|
|
116
116
|
it "should not include unchanged attributes in xml" do
|
117
117
|
@xml.to_s.should_not =~ Regexp.new(Regexp.escape('<amount>'))
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
it "should return correct xml and initialize correctly from xml" do
|
121
121
|
first = First.new_from_xml(@xml)
|
122
122
|
first.first_id.should == 1
|
@@ -136,11 +136,13 @@ describe FreshBooks::Base do
|
|
136
136
|
@elem = mock('XmlObj', :text => '123')
|
137
137
|
@resp = mock('XmlResp', :elements => [nil, @elem])
|
138
138
|
@resp.stub!(:success?).and_return(true)
|
139
|
-
|
139
|
+
@connection = mock('Connection')
|
140
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
141
|
+
@connection.stub!(:call_api).and_return(@resp)
|
140
142
|
end
|
141
143
|
|
142
144
|
it "should call api method" do
|
143
|
-
|
145
|
+
@connection.should_receive(:call_api).with('test.create', 'test' => @object).and_return(@resp)
|
144
146
|
@object.create
|
145
147
|
end
|
146
148
|
|
@@ -156,12 +158,12 @@ describe FreshBooks::Base do
|
|
156
158
|
end
|
157
159
|
|
158
160
|
it "should return id if call succeeded" do
|
159
|
-
@object.create.should
|
161
|
+
@object.create.should be_true
|
160
162
|
end
|
161
163
|
|
162
164
|
it "should return nil if call failed" do
|
163
165
|
@resp.stub!(:success?).and_return(false)
|
164
|
-
@object.create.should
|
166
|
+
@object.create.should be_false
|
165
167
|
end
|
166
168
|
end
|
167
169
|
|
@@ -169,11 +171,13 @@ describe FreshBooks::Base do
|
|
169
171
|
before(:each) do
|
170
172
|
@resp = mock('XmlResp')
|
171
173
|
@resp.stub!(:success?).and_return(true)
|
172
|
-
|
174
|
+
@connection = mock('Connection')
|
175
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
176
|
+
@connection.stub!(:call_api).and_return(@resp)
|
173
177
|
end
|
174
178
|
|
175
179
|
it "should call api method" do
|
176
|
-
|
180
|
+
@connection.should_receive(:call_api).with('test.update', 'test' => @object).and_return(@resp)
|
177
181
|
@object.update
|
178
182
|
end
|
179
183
|
|
@@ -193,11 +197,13 @@ describe FreshBooks::Base do
|
|
193
197
|
@object.test_id = 1
|
194
198
|
@resp = mock('XmlResp')
|
195
199
|
@resp.stub!(:success?).and_return(true)
|
196
|
-
|
200
|
+
@connection = mock('Connection')
|
201
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
202
|
+
@connection.stub!(:call_api).and_return(@resp)
|
197
203
|
end
|
198
204
|
|
199
205
|
it "should call api method" do
|
200
|
-
|
206
|
+
@connection.should_receive(:call_api).with('test.delete', 'test_id' => 1).and_return(@resp)
|
201
207
|
@object.delete
|
202
208
|
end
|
203
209
|
|
@@ -216,11 +222,13 @@ describe FreshBooks::Base do
|
|
216
222
|
before(:each) do
|
217
223
|
@resp = mock('XmlResp')
|
218
224
|
@resp.stub!(:success?).and_return(true)
|
219
|
-
|
225
|
+
@connection = mock('Connection')
|
226
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
227
|
+
@connection.stub!(:call_api).and_return(@resp)
|
220
228
|
end
|
221
229
|
|
222
230
|
it "should call api method" do
|
223
|
-
|
231
|
+
@connection.should_receive(:call_api).with('test.delete', 'test_id' => 1).and_return(@resp)
|
224
232
|
Test.delete(1)
|
225
233
|
end
|
226
234
|
|
@@ -239,12 +247,14 @@ describe FreshBooks::Base do
|
|
239
247
|
before(:each) do
|
240
248
|
@resp = mock('XmlResp', :elements => [])
|
241
249
|
@resp.stub!(:success?).and_return(true)
|
242
|
-
|
250
|
+
@connection = mock('Connection')
|
251
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
252
|
+
@connection.stub!(:call_api).and_return(@resp)
|
243
253
|
Test.stub!(:new_from_xml).and_return(true)
|
244
254
|
end
|
245
255
|
|
246
256
|
it "should call api method" do
|
247
|
-
|
257
|
+
@connection.should_receive(:call_api).with('test.get', 'test_id' => 1).and_return(@resp)
|
248
258
|
Test.get(1)
|
249
259
|
end
|
250
260
|
|
@@ -264,15 +274,25 @@ describe FreshBooks::Base do
|
|
264
274
|
before(:each) do
|
265
275
|
@elem1 = mock('XmlElement')
|
266
276
|
@elem2 = mock('XmlElement')
|
267
|
-
@elems = mock('XmlElements',
|
277
|
+
@elems = mock('XmlElements',
|
278
|
+
:elements => [@elem1, @elem2],
|
279
|
+
:attributes => {
|
280
|
+
'page' => 1,
|
281
|
+
'pages' => 10,
|
282
|
+
'per_page' => 30,
|
283
|
+
'total' => 300
|
284
|
+
}
|
285
|
+
)
|
268
286
|
@resp = mock('XmlResp', :elements => [nil, @elems])
|
269
287
|
@resp.stub!(:success?).and_return(true)
|
270
|
-
|
288
|
+
@connection = mock('Connection')
|
289
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
290
|
+
@connection.stub!(:call_api).and_return(@resp)
|
271
291
|
Test.stub!(:new_from_xml).and_return(true)
|
272
292
|
end
|
273
293
|
|
274
294
|
it "should call api method" do
|
275
|
-
|
295
|
+
@connection.should_receive(:call_api).with('test.list', :name => 'value').and_return(@resp)
|
276
296
|
Test.list(:name => 'value')
|
277
297
|
end
|
278
298
|
|
@@ -280,12 +300,17 @@ describe FreshBooks::Base do
|
|
280
300
|
@resp.stub!(:success?).and_return(false)
|
281
301
|
Test.list.should be_nil
|
282
302
|
end
|
303
|
+
|
304
|
+
it "should create a list proxy if request succeeded" do
|
305
|
+
@resp.stub!(:success?).and_return(true)
|
306
|
+
Test.list.should be_a(FreshBooks::ListProxy)
|
307
|
+
end
|
283
308
|
|
284
309
|
it "should build instances from response records if request succeeded" do
|
285
310
|
@resp.stub!(:success?).and_return(true)
|
286
311
|
Test.should_receive(:new_from_xml).with(@elem1).and_return(1)
|
287
312
|
Test.should_receive(:new_from_xml).with(@elem2).and_return(2)
|
288
|
-
Test.list.should == [1, 2]
|
313
|
+
Test.list.to_a.should == [1, 2]
|
289
314
|
end
|
290
315
|
end
|
291
316
|
|
@@ -294,11 +319,13 @@ describe FreshBooks::Base do
|
|
294
319
|
@object.test_id = 1
|
295
320
|
@resp = mock('XmlResp')
|
296
321
|
@resp.stub!(:success?).and_return(true)
|
297
|
-
|
322
|
+
@connection = mock('Connection')
|
323
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
324
|
+
@connection.stub!(:call_api).and_return(@resp)
|
298
325
|
end
|
299
326
|
|
300
327
|
it "should call api method" do
|
301
|
-
|
328
|
+
@connection.should_receive(:call_api).with('test.sendByEmail', 'test_id' => 1).and_return(@resp)
|
302
329
|
@object.send_by_email
|
303
330
|
end
|
304
331
|
|
@@ -317,11 +344,13 @@ describe FreshBooks::Base do
|
|
317
344
|
before(:each) do
|
318
345
|
@resp = mock('XmlResp')
|
319
346
|
@resp.stub!(:success?).and_return(true)
|
320
|
-
|
347
|
+
@connection = mock('Connection')
|
348
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
349
|
+
@connection.stub!(:call_api).and_return(@resp)
|
321
350
|
end
|
322
351
|
|
323
352
|
it "should call api method" do
|
324
|
-
|
353
|
+
@connection.should_receive(:call_api).with('test.sendByEmail', 'test_id' => 1).and_return(@resp)
|
325
354
|
Test.send_by_email(1)
|
326
355
|
end
|
327
356
|
|
@@ -341,11 +370,13 @@ describe FreshBooks::Base do
|
|
341
370
|
@object.test_id = 1
|
342
371
|
@resp = mock('XmlResp')
|
343
372
|
@resp.stub!(:success?).and_return(true)
|
344
|
-
|
373
|
+
@connection = mock('Connection')
|
374
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
375
|
+
@connection.stub!(:call_api).and_return(@resp)
|
345
376
|
end
|
346
377
|
|
347
378
|
it "should call api method" do
|
348
|
-
|
379
|
+
@connection.should_receive(:call_api).with('test.sendBySnailMail', 'test_id' => 1).and_return(@resp)
|
349
380
|
@object.send_by_snail_mail
|
350
381
|
end
|
351
382
|
|
@@ -364,11 +395,13 @@ describe FreshBooks::Base do
|
|
364
395
|
before(:each) do
|
365
396
|
@resp = mock('XmlResp')
|
366
397
|
@resp.stub!(:success?).and_return(true)
|
367
|
-
|
398
|
+
@connection = mock('Connection')
|
399
|
+
FreshBooks::Base.stub!(:connection).and_return(@connection)
|
400
|
+
@connection.stub!(:call_api).and_return(@resp)
|
368
401
|
end
|
369
402
|
|
370
403
|
it "should call api method" do
|
371
|
-
|
404
|
+
@connection.should_receive(:call_api).with('test.sendBySnailMail', 'test_id' => 1).and_return(@resp)
|
372
405
|
Test.send_by_snail_mail(1)
|
373
406
|
end
|
374
407
|
|
@@ -74,8 +74,8 @@ describe FreshBooks::TimeEntry do
|
|
74
74
|
|
75
75
|
# it "should update an existing time_entry" do
|
76
76
|
# time_entries = FreshBooks::TimeEntry.list
|
77
|
-
# @time_entry.hours
|
78
|
-
#
|
77
|
+
# time_entry = FreshBooks::TimeEntry.new(:time_entry_id => @time_entry.time_entry_id, :hours => 2)
|
78
|
+
# time_entry.update.should be_true
|
79
79
|
# FreshBooks::TimeEntry.get(@time_entry.time_entry_id).hours.should == 2
|
80
80
|
# FreshBooks::TimeEntry.list.size.should == time_entries.size
|
81
81
|
# end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,4 +8,4 @@ end
|
|
8
8
|
|
9
9
|
require File.dirname(__FILE__) + '/../lib/freshbooks.rb'
|
10
10
|
|
11
|
-
FreshBooks::Base.
|
11
|
+
FreshBooks::Base.establish_connection!('flatsourcingtest.freshbooks.com', 'a2e32a3dcb2444c2f0fc47a5ae7227d2')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turingstudio-freshbooks-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- The Turing Studio, Inc.
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-04 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -81,9 +81,8 @@ files:
|
|
81
81
|
has_rdoc: true
|
82
82
|
homepage: http://github.com/turingstudio/freshbooks-rb/
|
83
83
|
post_install_message:
|
84
|
-
rdoc_options:
|
85
|
-
|
86
|
-
- README.rdoc
|
84
|
+
rdoc_options: []
|
85
|
+
|
87
86
|
require_paths:
|
88
87
|
- lib
|
89
88
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -100,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
99
|
version:
|
101
100
|
requirements: []
|
102
101
|
|
103
|
-
rubyforge_project:
|
102
|
+
rubyforge_project:
|
104
103
|
rubygems_version: 1.2.0
|
105
104
|
signing_key:
|
106
105
|
specification_version: 2
|