harvested 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +3 -0
- data/.gitignore +2 -0
- data/README.md +4 -3
- data/Rakefile +10 -1
- data/VERSION +1 -1
- data/examples/basics.rb +1 -1
- data/examples/clear_account.rb +1 -1
- data/examples/task_assignments.rb +1 -1
- data/examples/user_assignments.rb +1 -1
- data/harvested.gemspec +159 -0
- data/lib/harvest/api/account.rb +5 -0
- data/lib/harvest/api/base.rb +3 -3
- data/lib/harvest/api/clients.rb +1 -1
- data/lib/harvest/api/contacts.rb +1 -1
- data/lib/harvest/api/projects.rb +16 -1
- data/lib/harvest/api/users.rb +7 -1
- data/lib/harvest/base.rb +199 -0
- data/lib/harvest/base_model.rb +43 -4
- data/lib/harvest/behavior/activatable.rb +11 -1
- data/lib/harvest/behavior/crud.rb +27 -1
- data/lib/harvest/client.rb +13 -1
- data/lib/harvest/contact.rb +13 -0
- data/lib/harvest/project.rb +36 -2
- data/lib/harvest/rate_limit_status.rb +12 -0
- data/lib/harvest/task.rb +10 -0
- data/lib/harvest/timezones.rb +1 -2
- data/lib/harvest/user.rb +29 -3
- data/lib/harvested.rb +32 -1
- data/spec/spec_helper.rb +1 -1
- metadata +26 -5
data/.document
ADDED
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -8,14 +8,14 @@ This is a Ruby wrapper for the [Harvest API](http://www.getharvest.com/).
|
|
8
8
|
|
9
9
|
## Examples
|
10
10
|
|
11
|
-
harvest = Harvest.client('yoursubdomain', 'yourusername', 'yourpassword'
|
11
|
+
harvest = Harvest.client('yoursubdomain', 'yourusername', 'yourpassword')
|
12
12
|
harvest.projects() # list out projects
|
13
13
|
|
14
14
|
client = Harvest::Client.new(:name => "Billable Company LTD.")
|
15
15
|
client = harvest.clients.create(client)
|
16
16
|
harvest.clients.find(client.id) # returns a Harvest::Client
|
17
17
|
|
18
|
-
You can find more examples in `/examples`
|
18
|
+
You can find more examples in `/examples` and in the documentation for Harvest::Base
|
19
19
|
|
20
20
|
## Hardy Client
|
21
21
|
|
@@ -23,11 +23,12 @@ The guys at Harvest built a great API, but there are always dangers in writing c
|
|
23
23
|
|
24
24
|
Using `Harvested#client` your code needs to handle all these situations. However you can also use `Harvested#hardy_client` which will retry errors and wait for Rate Limit resets.
|
25
25
|
|
26
|
-
harvest = Harvest.hardy_client('yoursubdomain', 'yourusername', 'yourpassword'
|
26
|
+
harvest = Harvest.hardy_client('yoursubdomain', 'yourusername', 'yourpassword')
|
27
27
|
harvest.projects() # This will wait for the Rate Limit reset if you have gone over your limit
|
28
28
|
|
29
29
|
## Links
|
30
30
|
|
31
|
+
* [Harvested Documentation](http://rdoc.info/projects/zmoazeni/harvested)
|
31
32
|
* [Harvest API Documentation](http://www.getharvest.com/api)
|
32
33
|
* [Source Code for Harvested](http://github.com/zmoazeni/harvested)
|
33
34
|
* [Pivotal Tracker for Harvested](http://www.pivotaltracker.com/projects/63260)
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "harvested"
|
8
8
|
gem.summary = %Q{A Ruby Wrapper for the Harvest API http://www.getharvest.com/}
|
9
|
-
gem.description = %Q{Harvested wraps the Harvest API concisely without the use of Rails dependencies. More information about the Harvest API can be found
|
9
|
+
gem.description = %Q{Harvested wraps the Harvest API concisely without the use of Rails dependencies. More information about the Harvest API can be found on their website (http://www.getharvest.com/api). For support hit up the Mailing List (http://groups.google.com/group/harvested)}
|
10
10
|
gem.email = "zach.moazeni@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/zmoazeni/harvested"
|
12
12
|
gem.authors = ["Zach Moazeni"]
|
@@ -50,3 +50,12 @@ rescue LoadError
|
|
50
50
|
end
|
51
51
|
|
52
52
|
task :default => %w(spec features)
|
53
|
+
|
54
|
+
begin
|
55
|
+
require 'yard'
|
56
|
+
YARD::Rake::YardocTask.new
|
57
|
+
rescue LoadError
|
58
|
+
task :yardoc do
|
59
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
60
|
+
end
|
61
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.1
|
data/examples/basics.rb
CHANGED
@@ -4,7 +4,7 @@ subdomain = 'yoursubdomain'
|
|
4
4
|
username = 'yourusername'
|
5
5
|
password = 'yourpassword'
|
6
6
|
|
7
|
-
harvest = Harvest.hardy_client(subdomain, username, password
|
7
|
+
harvest = Harvest.hardy_client(subdomain, username, password)
|
8
8
|
|
9
9
|
# Print out all users, clients, and projects on the account
|
10
10
|
puts "Users:"
|
data/examples/clear_account.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# username = 'yourusername'
|
7
7
|
# password = 'yourpassword'
|
8
8
|
#
|
9
|
-
# api = Harvest.hardy_client(subdomain, username, password
|
9
|
+
# api = Harvest.hardy_client(subdomain, username, password)
|
10
10
|
#
|
11
11
|
# raise "Are you sure you want to do this? (comment out this exception if so)"
|
12
12
|
#
|
@@ -4,7 +4,7 @@ subdomain = 'yoursubdomain'
|
|
4
4
|
username = 'yourusername'
|
5
5
|
password = 'yourpassword'
|
6
6
|
|
7
|
-
harvest = Harvest.hardy_client(subdomain, username, password
|
7
|
+
harvest = Harvest.hardy_client(subdomain, username, password)
|
8
8
|
|
9
9
|
# Create a Client add a Project to that Client
|
10
10
|
|
@@ -4,7 +4,7 @@ subdomain = 'yoursubdomain'
|
|
4
4
|
username = 'userusername'
|
5
5
|
password = 'yourpassword'
|
6
6
|
|
7
|
-
harvest = Harvest.hardy_client(subdomain, username, password
|
7
|
+
harvest = Harvest.hardy_client(subdomain, username, password)
|
8
8
|
|
9
9
|
# Create a Client, Project, and a User then assign that User to that Project
|
10
10
|
|
data/harvested.gemspec
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{harvested}
|
8
|
+
s.version = "0.3.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Zach Moazeni"]
|
12
|
+
s.date = %q{2010-10-14}
|
13
|
+
s.description = %q{Harvested wraps the Harvest API concisely without the use of Rails dependencies. More information about the Harvest API can be found on their website (http://www.getharvest.com/api). For support hit up the Mailing List (http://groups.google.com/group/harvested)}
|
14
|
+
s.email = %q{zach.moazeni@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
".gitignore",
|
21
|
+
"HISTORY",
|
22
|
+
"MIT-LICENSE",
|
23
|
+
"README.md",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"examples/basics.rb",
|
27
|
+
"examples/clear_account.rb",
|
28
|
+
"examples/task_assignments.rb",
|
29
|
+
"examples/user_assignments.rb",
|
30
|
+
"features/account.feature",
|
31
|
+
"features/client_contacts.feature",
|
32
|
+
"features/clients.feature",
|
33
|
+
"features/errors.feature",
|
34
|
+
"features/expense_categories.feature",
|
35
|
+
"features/expenses.feature",
|
36
|
+
"features/hardy_client.feature",
|
37
|
+
"features/projects.feature",
|
38
|
+
"features/reporting.feature",
|
39
|
+
"features/step_definitions/account_steps.rb",
|
40
|
+
"features/step_definitions/assignment_steps.rb",
|
41
|
+
"features/step_definitions/contact_steps.rb",
|
42
|
+
"features/step_definitions/debug_steps.rb",
|
43
|
+
"features/step_definitions/error_steps.rb",
|
44
|
+
"features/step_definitions/expenses_steps.rb",
|
45
|
+
"features/step_definitions/harvest_steps.rb",
|
46
|
+
"features/step_definitions/model_steps.rb",
|
47
|
+
"features/step_definitions/people_steps.rb",
|
48
|
+
"features/step_definitions/report_steps.rb",
|
49
|
+
"features/step_definitions/time_entry_steps.rb",
|
50
|
+
"features/support/env.rb",
|
51
|
+
"features/support/error_helpers.rb",
|
52
|
+
"features/support/fixtures/empty_clients.xml",
|
53
|
+
"features/support/fixtures/over_limit.xml",
|
54
|
+
"features/support/fixtures/receipt.png",
|
55
|
+
"features/support/fixtures/under_limit.xml",
|
56
|
+
"features/support/harvest_credentials.example.yml",
|
57
|
+
"features/support/harvest_helpers.rb",
|
58
|
+
"features/support/inflections.rb",
|
59
|
+
"features/task_assignment.feature",
|
60
|
+
"features/tasks.feature",
|
61
|
+
"features/time_tracking.feature",
|
62
|
+
"features/user_assignments.feature",
|
63
|
+
"features/users.feature",
|
64
|
+
"harvested.gemspec",
|
65
|
+
"lib/harvest/api/account.rb",
|
66
|
+
"lib/harvest/api/base.rb",
|
67
|
+
"lib/harvest/api/clients.rb",
|
68
|
+
"lib/harvest/api/contacts.rb",
|
69
|
+
"lib/harvest/api/expense_categories.rb",
|
70
|
+
"lib/harvest/api/expenses.rb",
|
71
|
+
"lib/harvest/api/projects.rb",
|
72
|
+
"lib/harvest/api/reports.rb",
|
73
|
+
"lib/harvest/api/task_assignments.rb",
|
74
|
+
"lib/harvest/api/tasks.rb",
|
75
|
+
"lib/harvest/api/time.rb",
|
76
|
+
"lib/harvest/api/user_assignments.rb",
|
77
|
+
"lib/harvest/api/users.rb",
|
78
|
+
"lib/harvest/base.rb",
|
79
|
+
"lib/harvest/base_model.rb",
|
80
|
+
"lib/harvest/behavior/activatable.rb",
|
81
|
+
"lib/harvest/behavior/crud.rb",
|
82
|
+
"lib/harvest/client.rb",
|
83
|
+
"lib/harvest/contact.rb",
|
84
|
+
"lib/harvest/credentials.rb",
|
85
|
+
"lib/harvest/errors.rb",
|
86
|
+
"lib/harvest/expense.rb",
|
87
|
+
"lib/harvest/expense_category.rb",
|
88
|
+
"lib/harvest/hardy_client.rb",
|
89
|
+
"lib/harvest/project.rb",
|
90
|
+
"lib/harvest/rate_limit_status.rb",
|
91
|
+
"lib/harvest/task.rb",
|
92
|
+
"lib/harvest/task_assignment.rb",
|
93
|
+
"lib/harvest/time_entry.rb",
|
94
|
+
"lib/harvest/timezones.rb",
|
95
|
+
"lib/harvest/user.rb",
|
96
|
+
"lib/harvest/user_assignment.rb",
|
97
|
+
"lib/harvested.rb",
|
98
|
+
"spec/harvest/base_spec.rb",
|
99
|
+
"spec/harvest/credentials_spec.rb",
|
100
|
+
"spec/harvest/expense_spec.rb",
|
101
|
+
"spec/harvest/task_assignment_spec.rb",
|
102
|
+
"spec/harvest/time_entry_spec.rb",
|
103
|
+
"spec/harvest/user_assignment_spec.rb",
|
104
|
+
"spec/harvest/user_spec.rb",
|
105
|
+
"spec/spec.default.opts",
|
106
|
+
"spec/spec_helper.rb"
|
107
|
+
]
|
108
|
+
s.homepage = %q{http://github.com/zmoazeni/harvested}
|
109
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
110
|
+
s.require_paths = ["lib"]
|
111
|
+
s.rubygems_version = %q{1.3.7}
|
112
|
+
s.summary = %q{A Ruby Wrapper for the Harvest API http://www.getharvest.com/}
|
113
|
+
s.test_files = [
|
114
|
+
"spec/harvest/base_spec.rb",
|
115
|
+
"spec/harvest/credentials_spec.rb",
|
116
|
+
"spec/harvest/expense_spec.rb",
|
117
|
+
"spec/harvest/task_assignment_spec.rb",
|
118
|
+
"spec/harvest/time_entry_spec.rb",
|
119
|
+
"spec/harvest/user_assignment_spec.rb",
|
120
|
+
"spec/harvest/user_spec.rb",
|
121
|
+
"spec/spec_helper.rb",
|
122
|
+
"examples/basics.rb",
|
123
|
+
"examples/clear_account.rb",
|
124
|
+
"examples/task_assignments.rb",
|
125
|
+
"examples/user_assignments.rb"
|
126
|
+
]
|
127
|
+
|
128
|
+
if s.respond_to? :specification_version then
|
129
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
130
|
+
s.specification_version = 3
|
131
|
+
|
132
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
133
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
134
|
+
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
135
|
+
s.add_development_dependency(%q<ruby-debug>, [">= 0"])
|
136
|
+
s.add_development_dependency(%q<fakeweb>, [">= 0"])
|
137
|
+
s.add_runtime_dependency(%q<httparty>, [">= 0"])
|
138
|
+
s.add_runtime_dependency(%q<happymapper>, [">= 0"])
|
139
|
+
s.add_runtime_dependency(%q<builder>, [">= 0"])
|
140
|
+
else
|
141
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
142
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
143
|
+
s.add_dependency(%q<ruby-debug>, [">= 0"])
|
144
|
+
s.add_dependency(%q<fakeweb>, [">= 0"])
|
145
|
+
s.add_dependency(%q<httparty>, [">= 0"])
|
146
|
+
s.add_dependency(%q<happymapper>, [">= 0"])
|
147
|
+
s.add_dependency(%q<builder>, [">= 0"])
|
148
|
+
end
|
149
|
+
else
|
150
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
151
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
152
|
+
s.add_dependency(%q<ruby-debug>, [">= 0"])
|
153
|
+
s.add_dependency(%q<fakeweb>, [">= 0"])
|
154
|
+
s.add_dependency(%q<httparty>, [">= 0"])
|
155
|
+
s.add_dependency(%q<happymapper>, [">= 0"])
|
156
|
+
s.add_dependency(%q<builder>, [">= 0"])
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
data/lib/harvest/api/account.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
module Harvest
|
2
2
|
module API
|
3
|
+
|
4
|
+
# API Methods to contain all account actions
|
3
5
|
class Account < Base
|
6
|
+
|
7
|
+
# Returns the current rate limit information
|
8
|
+
# @return [Harvest::RateLimitStatus]
|
4
9
|
def rate_limit_status
|
5
10
|
response = request(:get, credentials, '/account/rate_limit_status')
|
6
11
|
Harvest::RateLimitStatus.parse(response.body)
|
data/lib/harvest/api/base.rb
CHANGED
@@ -2,11 +2,11 @@ module Harvest
|
|
2
2
|
module API
|
3
3
|
class Base
|
4
4
|
attr_reader :credentials
|
5
|
-
|
5
|
+
|
6
6
|
def initialize(credentials)
|
7
7
|
@credentials = credentials
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
class << self
|
11
11
|
def api_model(klass)
|
12
12
|
class_eval <<-END
|
@@ -16,7 +16,7 @@ module Harvest
|
|
16
16
|
END
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
protected
|
21
21
|
def request(method, credentials, path, options = {})
|
22
22
|
response = HTTParty.send(method, "#{credentials.host}#{path}", :query => options[:query], :body => options[:body], :headers => {"Accept" => "application/xml", "Content-Type" => "application/xml; charset=utf-8", "Authorization" => "Basic #{credentials.basic_auth}", "User-Agent" => "Harvestable/#{Harvest::VERSION}"}.update(options[:headers] || {}), :format => :plain)
|
data/lib/harvest/api/clients.rb
CHANGED
data/lib/harvest/api/contacts.rb
CHANGED
data/lib/harvest/api/projects.rb
CHANGED
@@ -5,12 +5,23 @@ module Harvest
|
|
5
5
|
|
6
6
|
include Harvest::Behavior::Crud
|
7
7
|
|
8
|
+
# Creates and Assigns a task to the project
|
9
|
+
#
|
10
|
+
# == Examples
|
11
|
+
# project = harvest.projects.find(401)
|
12
|
+
# harvest.projects.create_task(project, 'Bottling Glue') # creates and assigns a task to the project
|
13
|
+
#
|
14
|
+
# @return [Harvest::Project]
|
8
15
|
def create_task(project, task_name)
|
9
16
|
response = request(:post, credentials, "/projects/#{project.to_i}/task_assignments/add_with_create_new_task", :body => task_xml(task_name))
|
10
17
|
id = response.headers["location"].first.match(/\/.*\/(\d+)\/.*\/(\d+)/)[1]
|
11
18
|
find(id)
|
12
19
|
end
|
13
20
|
|
21
|
+
# Deactivates the project. Does nothing if the project is already deactivated
|
22
|
+
#
|
23
|
+
# @param [Harvest::Project] project the project you want to deactivate
|
24
|
+
# @return [Harvest::Project] the deactivated project
|
14
25
|
def deactivate(project)
|
15
26
|
if project.active?
|
16
27
|
request(:put, credentials, "#{api_model.api_path}/#{project.to_i}/toggle", :headers => {'Content-Length' => '0'})
|
@@ -18,7 +29,11 @@ module Harvest
|
|
18
29
|
end
|
19
30
|
project
|
20
31
|
end
|
21
|
-
|
32
|
+
|
33
|
+
# Activates the project. Does nothing if the project is already activated
|
34
|
+
#
|
35
|
+
# @param [Harvest::Project] project the project you want to activate
|
36
|
+
# @return [Harvest::Project] the activated project
|
22
37
|
def activate(project)
|
23
38
|
if !project.active?
|
24
39
|
request(:put, credentials, "#{api_model.api_path}/#{project.to_i}/toggle", :headers => {'Content-Length' => '0'})
|
data/lib/harvest/api/users.rb
CHANGED
@@ -5,7 +5,13 @@ module Harvest
|
|
5
5
|
|
6
6
|
include Harvest::Behavior::Crud
|
7
7
|
include Harvest::Behavior::Activatable
|
8
|
-
|
8
|
+
|
9
|
+
# Triggers Harvest to reset the user's password and sends them an email to change it.
|
10
|
+
# @overload reset_password(id)
|
11
|
+
# @param [Integer] id the id of the user you want to reset the password for
|
12
|
+
# @overload reset_password(user)
|
13
|
+
# @param [Harvest::User] user the user you want to reset the password for
|
14
|
+
# @return [Harvest::User] the user you passed in
|
9
15
|
def reset_password(user)
|
10
16
|
request(:post, credentials, "#{api_model.api_path}/#{user.to_i}/reset_password")
|
11
17
|
user
|
data/lib/harvest/base.rb
CHANGED
@@ -2,48 +2,247 @@ module Harvest
|
|
2
2
|
class Base
|
3
3
|
attr_reader :request, :credentials
|
4
4
|
|
5
|
+
# @see Harvest.client
|
6
|
+
# @see Harvest.hardy_client
|
5
7
|
def initialize(subdomain, username, password, options = {})
|
6
8
|
options[:ssl] = true if options[:ssl].nil?
|
7
9
|
@credentials = Credentials.new(subdomain, username, password, options[:ssl])
|
8
10
|
raise InvalidCredentials unless credentials.valid?
|
9
11
|
end
|
10
12
|
|
13
|
+
# All API actions surrounding accounts
|
14
|
+
#
|
15
|
+
# == Examples
|
16
|
+
# harvest.account.rate_limit_status # Returns a Harvest::RateLimitStatus
|
17
|
+
#
|
18
|
+
# @return [Harvest::API::Account]
|
11
19
|
def account
|
12
20
|
@account ||= Harvest::API::Account.new(credentials)
|
13
21
|
end
|
14
22
|
|
23
|
+
# All API Actions surrounding Clients
|
24
|
+
#
|
25
|
+
# == Examples
|
26
|
+
# harvest.clients.all() # Returns all clients in the system
|
27
|
+
#
|
28
|
+
# harvest.clients.find(100) # Returns the client with id = 100
|
29
|
+
#
|
30
|
+
# client = Harvest::Client.new(:name => 'SuprCorp')
|
31
|
+
# saved_client = harvest.clients.create(client) # returns a saved version of Harvest::Client
|
32
|
+
#
|
33
|
+
# client = harvest.clients.find(205)
|
34
|
+
# client.name = 'SuprCorp LTD.'
|
35
|
+
# updated_client = harvest.clients.update(client) # returns an updated version of Harvest::Client
|
36
|
+
#
|
37
|
+
# client = harvest.clients.find(205)
|
38
|
+
# harvest.clients.delete(client) # returns 205
|
39
|
+
#
|
40
|
+
# client = harvest.clients.find(301)
|
41
|
+
# deactivated_client = harvest.clients.deactivate(client) # returns an updated deactivated client
|
42
|
+
# activated_client = harvest.clients.activate(client) # returns an updated activated client
|
43
|
+
#
|
44
|
+
# @see Harvest::Behavior::Crud
|
45
|
+
# @see Harvest::Behavior::Activatable
|
46
|
+
# @return [Harvest::API::Clients]
|
15
47
|
def clients
|
16
48
|
@clients ||= Harvest::API::Clients.new(credentials)
|
17
49
|
end
|
18
50
|
|
51
|
+
# All API Actions surrounding Client Contacts
|
52
|
+
#
|
53
|
+
# == Examples
|
54
|
+
# harvest.contacts.all() # Returns all contacts in the system
|
55
|
+
# harvest.contacts.all(10) # Returns all contacts for the client id=10 in the system
|
56
|
+
#
|
57
|
+
# harvest.contacts.find(100) # Returns the contact with id = 100
|
58
|
+
#
|
59
|
+
# contact = Harvest::Contact.new(:first_name => 'Jane', :last_name => 'Doe', :client_id => 10)
|
60
|
+
# saved_contact = harvest.contacts.create(contact) # returns a saved version of Harvest::Contact
|
61
|
+
#
|
62
|
+
# contact = harvest.contacts.find(205)
|
63
|
+
# contact.first_name = 'Jilly'
|
64
|
+
# updated_contact = harvest.contacts.update(contact) # returns an updated version of Harvest::Contact
|
65
|
+
#
|
66
|
+
# contact = harvest.contacts.find(205)
|
67
|
+
# harvest.contacts.delete(contact) # returns 205
|
68
|
+
#
|
69
|
+
# @see Harvest::Behavior::Crud
|
70
|
+
# @return [Harvest::API::Contacts]
|
19
71
|
def contacts
|
20
72
|
@contacts ||= Harvest::API::Contacts.new(credentials)
|
21
73
|
end
|
22
74
|
|
75
|
+
# All API Actions surrounding Projects
|
76
|
+
#
|
77
|
+
# == Examples
|
78
|
+
# harvest.projects.all() # Returns all projects in the system
|
79
|
+
#
|
80
|
+
# harvest.projects.find(100) # Returns the project with id = 100
|
81
|
+
#
|
82
|
+
# project = Harvest::Project.new(:name => 'SuprGlu' :client_id => 10)
|
83
|
+
# saved_project = harvest.projects.create(project) # returns a saved version of Harvest::Project
|
84
|
+
#
|
85
|
+
# project = harvest.projects.find(205)
|
86
|
+
# project.name = 'SuprSticky'
|
87
|
+
# updated_project = harvest.projects.update(project) # returns an updated version of Harvest::Project
|
88
|
+
#
|
89
|
+
# project = harvest.project.find(205)
|
90
|
+
# harvest.projects.delete(project) # returns 205
|
91
|
+
#
|
92
|
+
# project = harvest.projects.find(301)
|
93
|
+
# deactivated_project = harvest.projects.deactivate(project) # returns an updated deactivated project
|
94
|
+
# activated_project = harvest.projects.activate(project) # returns an updated activated project
|
95
|
+
#
|
96
|
+
# project = harvest.projects.find(401)
|
97
|
+
# harvest.projects.create_task(project, 'Bottling Glue') # creates and assigns a task to the project
|
98
|
+
#
|
99
|
+
# @see Harvest::Behavior::Crud
|
100
|
+
# @see Harvest::Behavior::Activatable
|
101
|
+
# @return [Harvest::API::Projects]
|
23
102
|
def projects
|
24
103
|
@projects ||= Harvest::API::Projects.new(credentials)
|
25
104
|
end
|
26
105
|
|
106
|
+
# All API Actions surrounding Tasks
|
107
|
+
#
|
108
|
+
# == Examples
|
109
|
+
# harvest.tasks.all() # Returns all tasks in the system
|
110
|
+
#
|
111
|
+
# harvest.tasks.find(100) # Returns the task with id = 100
|
112
|
+
#
|
113
|
+
# task = Harvest::Task.new(:name => 'Server Administration' :default => true)
|
114
|
+
# saved_task = harvest.tasks.create(task) # returns a saved version of Harvest::Task
|
115
|
+
#
|
116
|
+
# task = harvest.tasks.find(205)
|
117
|
+
# task.name = 'Server Administration'
|
118
|
+
# updated_task = harvest.tasks.update(task) # returns an updated version of Harvest::Task
|
119
|
+
#
|
120
|
+
# task = harvest.task.find(205)
|
121
|
+
# harvest.tasks.delete(task) # returns 205
|
122
|
+
#
|
123
|
+
# @see Harvest::Behavior::Crud
|
124
|
+
# @return [Harvest::API::Tasks]
|
27
125
|
def tasks
|
28
126
|
@tasks ||= Harvest::API::Tasks.new(credentials)
|
29
127
|
end
|
30
128
|
|
129
|
+
# All API Actions surrounding Users
|
130
|
+
#
|
131
|
+
# == Examples
|
132
|
+
# harvest.users.all() # Returns all users in the system
|
133
|
+
#
|
134
|
+
# harvest.users.find(100) # Returns the user with id = 100
|
135
|
+
#
|
136
|
+
# user = Harvest::User.new(:first_name => 'Edgar', :last_name => 'Ruth', :email => 'edgar@ruth.com', :password => 'mypassword', :password_confirmation => 'mypassword', :timezone => :cst, :admin => false, :telephone => '444-4444')
|
137
|
+
# saved_user = harvest.users.create(user) # returns a saved version of Harvest::User
|
138
|
+
#
|
139
|
+
# user = harvest.users.find(205)
|
140
|
+
# user.email = 'edgar@ruth.com'
|
141
|
+
# updated_user = harvest.users.update(user) # returns an updated version of Harvest::User
|
142
|
+
#
|
143
|
+
# user = harvest.users.find(205)
|
144
|
+
# harvest.users.delete(user) # returns 205
|
145
|
+
#
|
146
|
+
# user = harvest.users.find(301)
|
147
|
+
# deactivated_user = harvest.users.deactivate(user) # returns an updated deactivated user
|
148
|
+
# activated_user = harvest.users.activate(user) # returns an updated activated user
|
149
|
+
#
|
150
|
+
# user = harvest.users.find(401)
|
151
|
+
# harvest.users.reset_password(user) # will trigger the reset password feature of harvest and shoot the user an email
|
152
|
+
#
|
153
|
+
# @see Harvest::Behavior::Crud
|
154
|
+
# @see Harvest::Behavior::Activatable
|
155
|
+
# @return [Harvest::API::Users]
|
31
156
|
def users
|
32
157
|
@users ||= Harvest::API::Users.new(credentials)
|
33
158
|
end
|
34
159
|
|
160
|
+
# All API Actions surrounding assigning tasks to projects
|
161
|
+
#
|
162
|
+
# == Examples
|
163
|
+
# project = harvest.projects.find(101)
|
164
|
+
# harvest.task_assignments.all(project) # returns all tasks assigned to the project (as Harvest::TaskAssignment)
|
165
|
+
#
|
166
|
+
# project = harvest.projects.find(201)
|
167
|
+
# harvest.task_assignments.find(project, 5) # returns the task assignment with ID 5 that is assigned to the project
|
168
|
+
#
|
169
|
+
# project = harvest.projects.find(301)
|
170
|
+
# task = harvest.tasks.find(100)
|
171
|
+
# assignment = Harvest::TaskAssignment.new(:task_id => task.id, :project_id => project.id)
|
172
|
+
# saved_assignment = harvest.task_assignments.create(assignment) # returns a saved version of the task assignment
|
173
|
+
#
|
174
|
+
# project = harvest.projects.find(401)
|
175
|
+
# assignment = harvest.task_assignments.find(project, 15)
|
176
|
+
# assignment.hourly_rate = 150
|
177
|
+
# updated_assignment = harvest.task_assignments.update(assignment) # returns an updated assignment
|
178
|
+
#
|
179
|
+
# project = harvest.projects.find(501)
|
180
|
+
# assignment = harvest.task_assignments.find(project, 25)
|
181
|
+
# harvest.task_assignments.delete(assignment) # returns 25
|
182
|
+
#
|
183
|
+
# @return [Harvest::API::TaskAssignments]
|
35
184
|
def task_assignments
|
36
185
|
@task_assignments ||= Harvest::API::TaskAssignments.new(credentials)
|
37
186
|
end
|
38
187
|
|
188
|
+
# All API Actions surrounding assigning users to projects
|
189
|
+
#
|
190
|
+
# == Examples
|
191
|
+
# project = harvest.projects.find(101)
|
192
|
+
# harvest.user_assignments.all(project) # returns all users assigned to the project (as Harvest::UserAssignment)
|
193
|
+
#
|
194
|
+
# project = harvest.projects.find(201)
|
195
|
+
# harvest.user_assignments.find(project, 5) # returns the user assignment with ID 5 that is assigned to the project
|
196
|
+
#
|
197
|
+
# project = harvest.projects.find(301)
|
198
|
+
# user = harvest.users.find(100)
|
199
|
+
# assignment = Harvest::UserAssignment.new(:user_id => user.id, :project_id => project.id)
|
200
|
+
# saved_assignment = harvest.user_assignments.create(assignment) # returns a saved version of the user assignment
|
201
|
+
#
|
202
|
+
# project = harvest.projects.find(401)
|
203
|
+
# assignment = harvest.user_assignments.find(project, 15)
|
204
|
+
# assignment.project_manager = true
|
205
|
+
# updated_assignment = harvest.user_assignments.update(assignment) # returns an updated assignment
|
206
|
+
#
|
207
|
+
# project = harvest.projects.find(501)
|
208
|
+
# assignment = harvest.user_assignments.find(project, 25)
|
209
|
+
# harvest.user_assignments.delete(assignment) # returns 25
|
210
|
+
#
|
211
|
+
# @return [Harvest::API::UserAssignments]
|
39
212
|
def user_assignments
|
40
213
|
@user_assignments ||= Harvest::API::UserAssignments.new(credentials)
|
41
214
|
end
|
42
215
|
|
216
|
+
# All API Actions surrounding managing expense categories
|
217
|
+
#
|
218
|
+
# == Examples
|
219
|
+
# harvest.expense_categories.all() # Returns all expense categories in the system
|
220
|
+
#
|
221
|
+
# harvest.expense_categories.find(100) # Returns the expense category with id = 100
|
222
|
+
#
|
223
|
+
# category = Harvest::ExpenseCategory.new(:name => 'Mileage', :unit_price => 0.485)
|
224
|
+
# saved_category = harvest.expense_categories.create(category) # returns a saved version of Harvest::ExpenseCategory
|
225
|
+
#
|
226
|
+
# category = harvest.clients.find(205)
|
227
|
+
# category.name = 'Travel'
|
228
|
+
# updated_category = harvest.expense_categories.update(category) # returns an updated version of Harvest::ExpenseCategory
|
229
|
+
#
|
230
|
+
# category = harvest.expense_categories.find(205)
|
231
|
+
# harvest.expense_categories.delete(category) # returns 205
|
232
|
+
#
|
233
|
+
# @see Harvest::Behavior::Crud
|
234
|
+
# @return [Harvest::API::ExpenseCategories]
|
43
235
|
def expense_categories
|
44
236
|
@expense_categories ||= Harvest::API::ExpenseCategories.new(credentials)
|
45
237
|
end
|
46
238
|
|
239
|
+
# All API Actions surrounding expenses
|
240
|
+
#
|
241
|
+
# == Examples
|
242
|
+
# harvest.expenses.all() # Returns all expenses for the current week
|
243
|
+
# harvest.expenses.all(Time.parse('11/12/2009')) # returns all expenses for the week of 11/12/2009
|
244
|
+
#
|
245
|
+
# harvest.expenses.find(100) # Returns the expense with id = 100
|
47
246
|
def expenses
|
48
247
|
@expenses ||= Harvest::API::Expenses.new(credentials)
|
49
248
|
end
|
data/lib/harvest/base_model.rb
CHANGED
@@ -1,21 +1,58 @@
|
|
1
1
|
module Harvest
|
2
|
+
# The parent class of all Harvest models. Contains useful inheritable methods
|
2
3
|
class BaseModel
|
4
|
+
|
5
|
+
# Initializes the model. You can pass the attributes as a hash in a ActiveRecord-like style
|
6
|
+
#
|
7
|
+
# == Examples
|
8
|
+
# client = Harvest::Client.new(:name => 'John Doe')
|
9
|
+
# client.name # returns 'John Doe'
|
3
10
|
def initialize(attributes = {})
|
4
11
|
self.attributes = attributes
|
5
12
|
end
|
6
|
-
|
13
|
+
|
14
|
+
# Given a hash, sets the corresponding attributes
|
15
|
+
#
|
16
|
+
# == Examples
|
17
|
+
# client = Harvest::Client.new(:name => 'John Doe')
|
18
|
+
# client.name # returns 'John Doe'
|
19
|
+
# client.attributes = {:name => 'Terry Vaughn'}
|
20
|
+
# client.name # returns 'Terry Vaughn'
|
21
|
+
#
|
22
|
+
# @return [void]
|
7
23
|
def attributes=(attributes)
|
8
24
|
attributes.each {|k,v| send("#{k}=", v)}
|
9
25
|
end
|
10
|
-
|
26
|
+
|
27
|
+
# Checks the equality of another model based on the id
|
28
|
+
#
|
29
|
+
# == Examples
|
30
|
+
# client1 = Harvest::Client.new(:id => 1)
|
31
|
+
# client2 = Harvest::Client.new(:id => 1)
|
32
|
+
# client3 = Harvest::Client.new(:id => 2)
|
33
|
+
#
|
34
|
+
# client1 == client2 # returns true
|
35
|
+
# client1 == client3 # returns false
|
36
|
+
#
|
37
|
+
# @return [Boolean]
|
11
38
|
def ==(other)
|
12
39
|
id == other.id
|
13
40
|
end
|
14
|
-
|
41
|
+
|
42
|
+
# Returns the id of the model
|
43
|
+
#
|
44
|
+
# == Examples
|
45
|
+
# client = Harvest::Client.new(:id => 1)
|
46
|
+
# client.to_i # returns 1
|
47
|
+
#
|
48
|
+
# @return [Fixnum]
|
15
49
|
def to_i
|
16
50
|
id
|
17
51
|
end
|
18
|
-
|
52
|
+
|
53
|
+
# Builds the XML of the model. Primarily used to interact with the Harvest API.
|
54
|
+
#
|
55
|
+
# @return [String]
|
19
56
|
def to_xml
|
20
57
|
builder = Builder::XmlMarkup.new
|
21
58
|
builder.tag!(self.class.tag_name) do |c|
|
@@ -26,6 +63,8 @@ module Harvest
|
|
26
63
|
end
|
27
64
|
|
28
65
|
class << self
|
66
|
+
# This sets the API path so the API collections can use them in an agnostic way
|
67
|
+
# @return [void]
|
29
68
|
def api_path(path = nil)
|
30
69
|
@path ||= path
|
31
70
|
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module Harvest
|
2
2
|
module Behavior
|
3
|
+
|
4
|
+
# Activate/Deactivate behaviors that can be brought into API collections
|
3
5
|
module Activatable
|
6
|
+
# Deactivates the item. Does nothing if the item is already deactivated
|
7
|
+
#
|
8
|
+
# @param [Harvest::BaseModel] model the model you want to deactivate
|
9
|
+
# @return [Harvest::BaseModel] the deactivated model
|
4
10
|
def deactivate(model)
|
5
11
|
if model.active?
|
6
12
|
request(:post, credentials, "#{api_model.api_path}/#{model.to_i}/toggle")
|
@@ -8,7 +14,11 @@ module Harvest
|
|
8
14
|
end
|
9
15
|
model
|
10
16
|
end
|
11
|
-
|
17
|
+
|
18
|
+
# Activates the item. Does nothing if the item is already activated
|
19
|
+
#
|
20
|
+
# @param [Harvest::BaseModel] model the model you want to activate
|
21
|
+
# @return [Harvest::BaseModel] the activated model
|
12
22
|
def activate(model)
|
13
23
|
if !model.active?
|
14
24
|
request(:post, credentials, "#{api_model.api_path}/#{model.to_i}/toggle")
|
@@ -1,30 +1,56 @@
|
|
1
1
|
module Harvest
|
2
2
|
module Behavior
|
3
3
|
module Crud
|
4
|
+
# Retrieves all items
|
5
|
+
# @return [Array<Harvest::BaseModel>] an array of models depending on where you're calling it from (e.g. [Harvest::Client] from Harvest::Base#clients)
|
4
6
|
def all
|
5
7
|
response = request(:get, credentials, api_model.api_path)
|
6
8
|
api_model.parse(response.body)
|
7
9
|
end
|
8
10
|
|
11
|
+
# Retrieves an item by id
|
12
|
+
# @overload find(id)
|
13
|
+
# @param [Integer] the id of the item you want to retreive
|
14
|
+
# @overload find(id)
|
15
|
+
# @param [String] id the String version of the id
|
16
|
+
# @overload find(model)
|
17
|
+
# @param [Harvest::BaseModel] id you can pass a model and it will return a refreshed version
|
18
|
+
#
|
19
|
+
# @return [Harvest::BaseModel] the model depends on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
|
9
20
|
def find(id)
|
10
21
|
response = request(:get, credentials, "#{api_model.api_path}/#{id}")
|
11
22
|
api_model.parse(response.body, :single => true)
|
12
23
|
end
|
13
24
|
|
25
|
+
# Creates an item
|
26
|
+
# @param [Harvest::BaseModel] model the item you want to create
|
27
|
+
# @return [Harvest::BaseModel] the created model depending on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
|
14
28
|
def create(model)
|
15
29
|
response = request(:post, credentials, "#{api_model.api_path}", :body => model.to_xml)
|
16
30
|
id = response.headers["location"].first.match(/\/.*\/(\d+)/)[1]
|
17
31
|
find(id)
|
18
32
|
end
|
19
33
|
|
34
|
+
# Updates an item
|
35
|
+
# @param [Harvest::BaseModel] model the model you want to update
|
36
|
+
# @return [Harvest::BaseModel] the created model depending on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
|
20
37
|
def update(model)
|
21
38
|
request(:put, credentials, "#{api_model.api_path}/#{model.to_i}", :body => model.to_xml)
|
22
39
|
find(model.id)
|
23
40
|
end
|
24
41
|
|
42
|
+
# Deletes an item
|
43
|
+
# @overload delete(model)
|
44
|
+
# @param [Harvest::BaseModel] model the item you want to delete
|
45
|
+
# @overload delete(id)
|
46
|
+
# @param [Integer] id the id of the item you want to delete
|
47
|
+
# @overload delete(id)
|
48
|
+
# @param [String] id the String version of the id of the item you want to delete
|
49
|
+
#
|
50
|
+
# @return [Integer] the id of the item deleted
|
25
51
|
def delete(model)
|
26
52
|
request(:delete, credentials, "#{api_model.api_path}/#{model.to_i}")
|
27
|
-
model.
|
53
|
+
model.to_i
|
28
54
|
end
|
29
55
|
end
|
30
56
|
end
|
data/lib/harvest/client.rb
CHANGED
@@ -1,4 +1,15 @@
|
|
1
1
|
module Harvest
|
2
|
+
# The model that contains information about a client
|
3
|
+
#
|
4
|
+
# == Fields
|
5
|
+
# [+id+] (READONLY) the id of the client
|
6
|
+
# [+name+] (REQUIRED) the name of the client
|
7
|
+
# [+details+] the details of the client
|
8
|
+
# [+currency+] what type of currency is associated with the client
|
9
|
+
# [+currency_symbol+] what currency symbol is associated with the client
|
10
|
+
# [+active?+] true|false on whether the client is active
|
11
|
+
# [+highrise_id+] (READONLY) the highrise id associated with this client
|
12
|
+
# [+update_at+] (READONLY) the last modification timestamp
|
2
13
|
class Client < BaseModel
|
3
14
|
include HappyMapper
|
4
15
|
|
@@ -12,7 +23,8 @@ module Harvest
|
|
12
23
|
element :currency_symbol, String, :tag => "currency-symbol"
|
13
24
|
element :cache_version, Integer, :tag => "cache-version"
|
14
25
|
element :updated_at, Time, :tag => "updated-at"
|
15
|
-
|
26
|
+
element :highrise_id, Integer, :tag => 'highrise-id'
|
27
|
+
|
16
28
|
alias_method :active?, :active
|
17
29
|
end
|
18
30
|
end
|
data/lib/harvest/contact.rb
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
module Harvest
|
2
|
+
# The model that contains information about a client contact
|
3
|
+
#
|
4
|
+
# == Fields
|
5
|
+
# [+id+] (READONLY) the id of the contact
|
6
|
+
# [+client_id+] (REQUIRED) the id of the client this contact is associated with
|
7
|
+
# [+first_name+] (REQUIRED) the first name of the contact
|
8
|
+
# [+last_name+] (REQUIRED) the last name of the contact
|
9
|
+
# [+email+] the email of the contact
|
10
|
+
# [+title+] the title of the contact
|
11
|
+
# [+phone_office+] the office phone number of the contact
|
12
|
+
# [+phone_moble+] the moble phone number of the contact
|
13
|
+
# [+fax+] the fax number of the contact
|
2
14
|
class Contact < BaseModel
|
3
15
|
include HappyMapper
|
4
16
|
|
@@ -12,5 +24,6 @@ module Harvest
|
|
12
24
|
element :phone_office, String, :tag => "phone-office"
|
13
25
|
element :phone_mobile, String, :tag => "phone-mobile"
|
14
26
|
element :fax, String
|
27
|
+
element :title, String
|
15
28
|
end
|
16
29
|
end
|
data/lib/harvest/project.rb
CHANGED
@@ -1,4 +1,27 @@
|
|
1
1
|
module Harvest
|
2
|
+
|
3
|
+
# The model that contains information about a project
|
4
|
+
#
|
5
|
+
# == Fields
|
6
|
+
# [+id+] (READONLY) the id of the project
|
7
|
+
# [+name+] (REQUIRED) the name of the project
|
8
|
+
# [+client_id+] (REQUIRED) the client id of the project
|
9
|
+
# [+code+] the project code
|
10
|
+
# [+notes+] the project notes
|
11
|
+
# [+active?+] true|false whether the project is active
|
12
|
+
# [+billable?+] true|false where the project is billable
|
13
|
+
# [+budget_by+] how the budget is calculated for the project +project|project_cost|task|person|nil+
|
14
|
+
# [+budget+] what the budget is for the project (based on budget_by)
|
15
|
+
# [+bill_by+] how to bill the project +Tasks|People|Project|nil+
|
16
|
+
# [+hourly_rate+] what the hourly rate for the project is based on +bill_by+
|
17
|
+
# [+notify_when_over_budget?+] whether the project will send notifications when it goes over budget
|
18
|
+
# [+over_budget_notification_percentage+] what percentage of the budget the project has to be before it sends a notification. Based on +notify_when_over_budget?+
|
19
|
+
# [+show_budget_to_all?+] whether the project's budget is shown to employees and contractors
|
20
|
+
# [+basecamp_id+] (READONLY) the id of the basecamp project associated to the project
|
21
|
+
# [+highrise_deal_id+] (READONLY) the id of the highrise deal associated to the project
|
22
|
+
# [+active_task_assignments_count+] (READONLY) the number of active task assignments
|
23
|
+
# [+created_at+] (READONLY) when the project was created
|
24
|
+
# [+updated_at+] (READONLY) when the project was updated
|
2
25
|
class Project < BaseModel
|
3
26
|
include HappyMapper
|
4
27
|
|
@@ -12,11 +35,22 @@ module Harvest
|
|
12
35
|
element :fees, String
|
13
36
|
element :active, Boolean
|
14
37
|
element :billable, Boolean
|
15
|
-
element :budget,
|
16
|
-
element :budget_by,
|
38
|
+
element :budget, Float
|
39
|
+
element :budget_by, String, :tag => 'budget-by'
|
17
40
|
element :hourly_rate, Float, :tag => 'hourly-rate'
|
18
41
|
element :bill_by, String, :tag => 'bill-by'
|
42
|
+
element :created_at, Time, :tag => 'created-at'
|
43
|
+
element :updated_at, Time, :tag => 'updated-at'
|
44
|
+
element :notify_when_over_budget, Boolean, :tag => 'notify-when-over-budget'
|
45
|
+
element :over_budget_notification_percentage, Float, :tag => 'over-budget-notification-percentage'
|
46
|
+
element :show_budget_to_all, Boolean, :tag => 'show-budget-to-all'
|
47
|
+
element :basecamp_id, Integer, :tag => 'basecamp-id'
|
48
|
+
element :highrise_deal_id, Integer, :tag => 'highrise-deal-id'
|
49
|
+
element :active_task_assignments_count, Integer, :tag => 'active-task-assignments-count'
|
19
50
|
|
20
51
|
alias_method :active?, :active
|
52
|
+
alias_method :billable?, :billable
|
53
|
+
alias_method :notify_when_over_budget?, :notify_when_over_budget
|
54
|
+
alias_method :show_budget_to_all?, :show_budget_to_all
|
21
55
|
end
|
22
56
|
end
|
@@ -1,4 +1,13 @@
|
|
1
1
|
module Harvest
|
2
|
+
|
3
|
+
# The model that contains the information about the user's rate limit
|
4
|
+
#
|
5
|
+
# == Fields
|
6
|
+
# [+last_access_at+] The last registered request
|
7
|
+
# [+count+] The current number of requests registered
|
8
|
+
# [+timeframe_limit+] The amount of seconds before a rate limit refresh occurs
|
9
|
+
# [+max_calls+] The number of requests you can make within the +timeframe_limit+
|
10
|
+
# [+lockout_seconds+] If you exceed the rate limit, how long you will be locked out from Harvest
|
2
11
|
class RateLimitStatus < BaseModel
|
3
12
|
include HappyMapper
|
4
13
|
|
@@ -9,6 +18,9 @@ module Harvest
|
|
9
18
|
element :max_calls, Integer, :tag => 'max-calls'
|
10
19
|
element :lockout_seconds, Integer, :tag => 'lockout-seconds'
|
11
20
|
|
21
|
+
# Returns true if the user is over their rate limit
|
22
|
+
# @return [Boolean]
|
23
|
+
# @see http://www.getharvest.com/api
|
12
24
|
def over_limit?
|
13
25
|
count > max_calls
|
14
26
|
end
|
data/lib/harvest/task.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
module Harvest
|
2
|
+
|
3
|
+
# The model that contains information about a task
|
4
|
+
#
|
5
|
+
# == Fields
|
6
|
+
# [+id+] (READONLY) the id of the task
|
7
|
+
# [+name+] (REQUIRED) the name of the task
|
8
|
+
# [+billable+] whether the task is billable by default
|
9
|
+
# [+deactivated+] whether the task is deactivated
|
10
|
+
# [+hourly_rate+] what the default hourly rate for the task is
|
11
|
+
# [+default?+] whether to add this task to new projects by default
|
2
12
|
class Task < BaseModel
|
3
13
|
include HappyMapper
|
4
14
|
|
data/lib/harvest/timezones.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
# shamelessly ripped from Rails: http://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
|
2
|
-
|
3
1
|
module Harvest
|
2
|
+
# shamelessly ripped from Rails: http://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
|
4
3
|
module Timezones
|
5
4
|
MAPPING = {
|
6
5
|
"pacific/midway" => "International Date Line West",
|
data/lib/harvest/user.rb
CHANGED
@@ -1,7 +1,24 @@
|
|
1
1
|
module Harvest
|
2
|
+
|
3
|
+
# The model that contains information about a task
|
4
|
+
#
|
5
|
+
# == Fields
|
6
|
+
# [+id+] (READONLY) the id of the user
|
7
|
+
# [+email+] the email of the user
|
8
|
+
# [+first_name+] the first name for the user
|
9
|
+
# [+last_name+] the last name for the user
|
10
|
+
# [+telephone+] the telephone for the user
|
11
|
+
# [+department] the department for the user
|
12
|
+
# [+password|password_confirmation+] the password for the user (only used on create.)
|
13
|
+
# [+has_access_to_all_future_projects+] whether the user should be added to future projects by default
|
14
|
+
# [+hourly_rate+] what the default hourly rate for the user is
|
15
|
+
# [+admin?+] whether the user is an admin
|
16
|
+
# [+contractor?+] whether the user is a contractor
|
17
|
+
# [+contractor?+] whether the user is a contractor
|
18
|
+
# [+timezone+] the timezone for the user.
|
2
19
|
class User < BaseModel
|
3
20
|
include HappyMapper
|
4
|
-
|
21
|
+
|
5
22
|
api_path '/people'
|
6
23
|
element :id, Integer
|
7
24
|
element :email, String
|
@@ -13,14 +30,23 @@ module Harvest
|
|
13
30
|
element :admin, Boolean, :tag => 'is-admin'
|
14
31
|
element :contractor, Boolean, :tag => 'is-contractor'
|
15
32
|
element :telephone, String
|
33
|
+
element :department, String
|
16
34
|
element :timezone, String
|
17
35
|
element :password, String
|
18
36
|
element :password_confirmation, String, :tag => 'password-confirmation'
|
19
|
-
|
37
|
+
|
20
38
|
alias_method :active?, :active
|
21
39
|
alias_method :admin?, :admin
|
22
40
|
alias_method :contractor?, :contractor
|
23
|
-
|
41
|
+
|
42
|
+
# Sets the timezone for the user. This can be done in a variety of ways.
|
43
|
+
#
|
44
|
+
# == Examples
|
45
|
+
# user.timezone = :cst # the easiest way. CST, EST, MST, and PST are supported
|
46
|
+
#
|
47
|
+
# user.timezone = 'america/chicago' # a little more verbose
|
48
|
+
#
|
49
|
+
# user.timezone = 'Central Time (US & Canada)' # the most explicit way
|
24
50
|
def timezone=(timezone)
|
25
51
|
tz = timezone.to_s.downcase
|
26
52
|
case tz
|
data/lib/harvested.rb
CHANGED
@@ -16,13 +16,44 @@ require 'harvest/base'
|
|
16
16
|
%w(base account clients contacts projects tasks users task_assignments user_assignments expense_categories expenses time reports).each {|a| require "harvest/api/#{a}"}
|
17
17
|
|
18
18
|
module Harvest
|
19
|
-
VERSION = "0.3.
|
19
|
+
VERSION = "0.3.1".freeze
|
20
20
|
|
21
21
|
class << self
|
22
|
+
|
23
|
+
# Creates a standard client that will raise all errors it encounters
|
24
|
+
#
|
25
|
+
# == Options
|
26
|
+
# * +:ssl+ - Whether or not to use SSL when connecting to Harvest. This is dependent on whether your account supports it. Set to +true+ by default
|
27
|
+
# == Examples
|
28
|
+
# Harvest.client('mysubdomain', 'myusername', 'mypassword', :ssl => false)
|
29
|
+
#
|
30
|
+
# @return [Harvest::Base]
|
22
31
|
def client(subdomain, username, password, options = {})
|
23
32
|
Harvest::Base.new(subdomain, username, password, options)
|
24
33
|
end
|
25
34
|
|
35
|
+
# Creates a hardy client that will retry common HTTP errors it encounters and sleep() if it determines it is over your rate limit
|
36
|
+
#
|
37
|
+
# == Options
|
38
|
+
# * +:ssl+ - Whether or not to use SSL when connecting to Harvest. This is dependent on whether your account supports it. Set to +true+ by default
|
39
|
+
# * +:retry+ - How many times the hardy client should retry errors. Set to +5+ by default.
|
40
|
+
#
|
41
|
+
# == Examples
|
42
|
+
# Harvest.hardy_client('mysubdomain', 'myusername', 'mypassword', :ssl => true, :retry => 3)
|
43
|
+
#
|
44
|
+
# == Errors
|
45
|
+
# The hardy client will retry the following errors
|
46
|
+
# * Harvest::Unavailable
|
47
|
+
# * Harvest::InformHarvest
|
48
|
+
# * Net::HTTPError
|
49
|
+
# * Net::HTTPFatalError
|
50
|
+
# * Errno::ECONNRESET
|
51
|
+
#
|
52
|
+
# == Rate Limits
|
53
|
+
# The hardy client will make as many requests as it can until it detects it has gone over the rate limit. Then it will +sleep()+ for the how ever long it takes for the limit to reset. You can find more information about the Rate Limiting at http://www.getharvest.com/api
|
54
|
+
#
|
55
|
+
# @return [Harvest::HardyClient] a Harvest::Base wrapped in a Harvest::HardyClient
|
56
|
+
# @see Harvest::Base
|
26
57
|
def hardy_client(subdomain, username, password, options = {})
|
27
58
|
retries = options.delete(:retry)
|
28
59
|
Harvest::HardyClient.new(client(subdomain, username, password, options), (retries || 5))
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: harvested
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 17
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
9
|
+
- 1
|
10
|
+
version: 0.3.1
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Zach Moazeni
|
@@ -14,16 +15,18 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-10-14 00:00:00 -04:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: rspec
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
27
30
|
segments:
|
28
31
|
- 1
|
29
32
|
- 2
|
@@ -35,9 +38,11 @@ dependencies:
|
|
35
38
|
name: cucumber
|
36
39
|
prerelease: false
|
37
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
38
42
|
requirements:
|
39
43
|
- - ">="
|
40
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
41
46
|
segments:
|
42
47
|
- 0
|
43
48
|
version: "0"
|
@@ -47,9 +52,11 @@ dependencies:
|
|
47
52
|
name: ruby-debug
|
48
53
|
prerelease: false
|
49
54
|
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
50
56
|
requirements:
|
51
57
|
- - ">="
|
52
58
|
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
53
60
|
segments:
|
54
61
|
- 0
|
55
62
|
version: "0"
|
@@ -59,9 +66,11 @@ dependencies:
|
|
59
66
|
name: fakeweb
|
60
67
|
prerelease: false
|
61
68
|
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
62
70
|
requirements:
|
63
71
|
- - ">="
|
64
72
|
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
65
74
|
segments:
|
66
75
|
- 0
|
67
76
|
version: "0"
|
@@ -71,9 +80,11 @@ dependencies:
|
|
71
80
|
name: httparty
|
72
81
|
prerelease: false
|
73
82
|
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
74
84
|
requirements:
|
75
85
|
- - ">="
|
76
86
|
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
77
88
|
segments:
|
78
89
|
- 0
|
79
90
|
version: "0"
|
@@ -83,9 +94,11 @@ dependencies:
|
|
83
94
|
name: happymapper
|
84
95
|
prerelease: false
|
85
96
|
requirement: &id006 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
86
98
|
requirements:
|
87
99
|
- - ">="
|
88
100
|
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
89
102
|
segments:
|
90
103
|
- 0
|
91
104
|
version: "0"
|
@@ -95,15 +108,17 @@ dependencies:
|
|
95
108
|
name: builder
|
96
109
|
prerelease: false
|
97
110
|
requirement: &id007 !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
98
112
|
requirements:
|
99
113
|
- - ">="
|
100
114
|
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
101
116
|
segments:
|
102
117
|
- 0
|
103
118
|
version: "0"
|
104
119
|
type: :runtime
|
105
120
|
version_requirements: *id007
|
106
|
-
description: Harvested wraps the Harvest API concisely without the use of Rails dependencies. More information about the Harvest API can be found
|
121
|
+
description: Harvested wraps the Harvest API concisely without the use of Rails dependencies. More information about the Harvest API can be found on their website (http://www.getharvest.com/api). For support hit up the Mailing List (http://groups.google.com/group/harvested)
|
107
122
|
email: zach.moazeni@gmail.com
|
108
123
|
executables: []
|
109
124
|
|
@@ -112,6 +127,7 @@ extensions: []
|
|
112
127
|
extra_rdoc_files:
|
113
128
|
- README.md
|
114
129
|
files:
|
130
|
+
- .document
|
115
131
|
- .gitignore
|
116
132
|
- HISTORY
|
117
133
|
- MIT-LICENSE
|
@@ -156,6 +172,7 @@ files:
|
|
156
172
|
- features/time_tracking.feature
|
157
173
|
- features/user_assignments.feature
|
158
174
|
- features/users.feature
|
175
|
+
- harvested.gemspec
|
159
176
|
- lib/harvest/api/account.rb
|
160
177
|
- lib/harvest/api/base.rb
|
161
178
|
- lib/harvest/api/clients.rb
|
@@ -208,23 +225,27 @@ rdoc_options:
|
|
208
225
|
require_paths:
|
209
226
|
- lib
|
210
227
|
required_ruby_version: !ruby/object:Gem::Requirement
|
228
|
+
none: false
|
211
229
|
requirements:
|
212
230
|
- - ">="
|
213
231
|
- !ruby/object:Gem::Version
|
232
|
+
hash: 3
|
214
233
|
segments:
|
215
234
|
- 0
|
216
235
|
version: "0"
|
217
236
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
237
|
+
none: false
|
218
238
|
requirements:
|
219
239
|
- - ">="
|
220
240
|
- !ruby/object:Gem::Version
|
241
|
+
hash: 3
|
221
242
|
segments:
|
222
243
|
- 0
|
223
244
|
version: "0"
|
224
245
|
requirements: []
|
225
246
|
|
226
247
|
rubyforge_project:
|
227
|
-
rubygems_version: 1.3.
|
248
|
+
rubygems_version: 1.3.7
|
228
249
|
signing_key:
|
229
250
|
specification_version: 3
|
230
251
|
summary: A Ruby Wrapper for the Harvest API http://www.getharvest.com/
|