harvest 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/HISTORY +3 -0
  2. data/LICENSE +23 -0
  3. data/README.rdoc +173 -0
  4. data/Rakefile +44 -0
  5. data/lib/harvest.rb +35 -0
  6. data/lib/harvest/base.rb +77 -0
  7. data/lib/harvest/harvest_resource.rb +15 -0
  8. data/lib/harvest/plugins/active_resource_inheritable_headers.rb +36 -0
  9. data/lib/harvest/plugins/toggleable.rb +12 -0
  10. data/lib/harvest/resources/client.rb +8 -0
  11. data/lib/harvest/resources/entry.rb +34 -0
  12. data/lib/harvest/resources/expense.rb +22 -0
  13. data/lib/harvest/resources/expense_category.rb +7 -0
  14. data/lib/harvest/resources/person.rb +44 -0
  15. data/lib/harvest/resources/project.rb +51 -0
  16. data/lib/harvest/resources/task.rb +8 -0
  17. data/lib/harvest/resources/task_assignment.rb +27 -0
  18. data/lib/harvest/resources/user_assignment.rb +27 -0
  19. data/test/integration/client_integration.rb +17 -0
  20. data/test/integration/client_teardown.rb +11 -0
  21. data/test/integration/expense_category_integration.rb +16 -0
  22. data/test/integration/expense_category_teardown.rb +12 -0
  23. data/test/integration/harvest_integration_test.rb +47 -0
  24. data/test/integration/project_integration.rb +19 -0
  25. data/test/integration/project_teardown.rb +12 -0
  26. data/test/integration/task_integration.rb +19 -0
  27. data/test/integration/task_teardown.rb +12 -0
  28. data/test/test_helper.rb +51 -0
  29. data/test/unit/base_test.rb +88 -0
  30. data/test/unit/resources/client_test.rb +82 -0
  31. data/test/unit/resources/expense_category_test.rb +49 -0
  32. data/test/unit/resources/expense_test.rb +14 -0
  33. data/test/unit/resources/person_test.rb +150 -0
  34. data/test/unit/resources/project_test.rb +154 -0
  35. data/test/unit/resources/task_assignment_test.rb +72 -0
  36. data/test/unit/resources/task_test.rb +82 -0
  37. data/test/unit/resources/user_assignment_test.rb +71 -0
  38. metadata +111 -0
data/HISTORY ADDED
@@ -0,0 +1,3 @@
1
+ 0.8 (December 10, 2008)
2
+ First version
3
+
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2008, Kyle Banker, Alexander Interactive, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ Except as contained in this notice, the name(s) of the above copyright holders
14
+ shall not be used in advertising or otherwise to promote the sale, use or other
15
+ dealings in this Software without prior written authorization.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
@@ -0,0 +1,173 @@
1
+ = harvest
2
+
3
+ A ruby library wrapping (most of) the Harvest Api.
4
+
5
+ == DESCRIPTION:
6
+
7
+ This library uses ActiveResource and activeresource_throttle to provide simple, reliable access to the Harvest Api. Good for building reporting scripts and simple apps.
8
+
9
+ Note that this library does not support the complete Harvest Api at this time. See Features/Issues below for details.
10
+
11
+ == USAGE:
12
+
13
+ === Creating a new harvest object:
14
+
15
+ All calls to the Harvest API will originate from a Harvest object. Initialize it like so:
16
+
17
+ @harvest = Harvest(:email => "joe@example.com",
18
+ :password => "secret",
19
+ :sub_domain => "joeandcompany",
20
+ :headers => {"User-Agent" => "MyCompany"})
21
+
22
+ The _headers_ argument is optional. Use <b>:ssl => true</b> if your account requires HTTPS.
23
+
24
+ === Clients
25
+
26
+ Index
27
+ @harvest.clients.find(:all)
28
+ Show
29
+ @harvest.clients.find(43235)
30
+ Create
31
+ @client = @harvest.clients.new
32
+ @client.attributes = {:name => "Company, LLC", :details => "New York, NY"}
33
+ @client.save
34
+ Update
35
+ @client.first_name = "Smith"
36
+ @client.save
37
+ Destroy
38
+ @client.destroy
39
+
40
+ Toggle (Active/Inactive)
41
+ @client.toggle
42
+
43
+ === Tasks
44
+
45
+ Index
46
+ @harvest.tasks.find(:all)
47
+ Show
48
+ @harvest.tasks.find(123)
49
+ Create
50
+ @task = @harvest.tasks.new
51
+ @task.attributes = {:name => "Meeting", :billable_by_default => false,
52
+ :is_default => false, :default_hourly_rate => 100}
53
+ @task.save
54
+ Update
55
+ @task.name = "Client Meeting"
56
+ @task.save
57
+ Destroy
58
+ @task.destroy
59
+
60
+ === People
61
+
62
+ Index
63
+ @harvest.people.find(:all)
64
+ Show
65
+ @harvest.people.find(123)
66
+
67
+ === Projects
68
+
69
+ Index
70
+ @harvest.projects.find(:all)
71
+ Show
72
+ @harvest.projects.find(123)
73
+ Create
74
+ @project = @harvest.projects.new
75
+ @project.attributes = {:name => "Refactor", :active => true,
76
+ :bill_by => "None", :client_id => @client.id, }
77
+ @project.save
78
+ Update
79
+ @project.name = "Mega-Refactor"
80
+ @project.save
81
+ Destroy
82
+ @project.destroy
83
+
84
+ Toggle (Active/Inactive)
85
+ @project.toggle
86
+
87
+ === Invoices
88
+
89
+ Index
90
+ @invoices = @harvest.invoices.find(:all)
91
+
92
+ Show
93
+ @invoice = @harvest.invoices.find(1)
94
+
95
+ Find By Invoice Number
96
+ @invoice = @harvest.invoices.find_by_number('200')
97
+
98
+ Create
99
+ @invoice = @harvest.invoices.new
100
+ @invoice.attributes = {:amount => '250.32', :client_id => @client.id}
101
+ @invoice.save
102
+
103
+ Update
104
+ @invoice.notes = "Pending payment"
105
+ @invoice.save
106
+
107
+ Destroy
108
+ @invoice.destroy
109
+
110
+ Return parsed csv line items
111
+ @invoice.parsed_csv_line_items
112
+
113
+ ==== User and Task Assignments
114
+
115
+ @project.users.find(:all)
116
+ @project.users.find(:first)
117
+
118
+ @project.tasks.find(:all)
119
+ @project.tasks.find(:first)
120
+
121
+ === Reports
122
+
123
+ @project.entries(:from => Time.now, :to => Time.now)
124
+ @project.entries(:from => Time.now, :to => Time.now, :user_id => @person.id)
125
+
126
+ @person.entries(:from => Time.now, :to => Time.now)
127
+ @person.expenses(:from => Time.now, :to => Time.now)
128
+
129
+
130
+ == FEATURES/PROBLEMS:
131
+
132
+ Supports most of the Harvest Api (http://www.getharvest.com/api).
133
+
134
+ Though RESTful, the Harvest Api does not entirely follow ActiveResource conventions. In order to support the complete API, certain customizations will be required. <b>We welcome any contributions/modifications to help complete this Api.</b>
135
+
136
+ The following Api features are incomplete due to incompatibilities with ActiveResource conventions (CRUD actions in parentheses):
137
+
138
+ 1. People (CUD)
139
+ 2. User & Task Assignments (CUD)
140
+ 3. Expenses (CUD)
141
+
142
+ In addition, the following have not been implemented at all due to time constraints:
143
+
144
+ 1. Time Tracking Api
145
+ 2. Invoices, Invoice Payments, Invoice Categories, and Invoice Messages
146
+
147
+ Expect a more complete library in the near future. Again, contributions are welcome.
148
+
149
+ == TEST SUITE
150
+
151
+ This library includes a fairly thorough set of unit and integration tests. The unit tests can be run with the "rake" command.
152
+
153
+ The integration tests require a Harvest account. It is best to use a trial account when running these tests.
154
+
155
+ The credentials may be entered in test/test_helper.rb. To run the integration suite:
156
+
157
+ rake test:integration
158
+
159
+ Note that the integration test structure is somewhat unorthodox, as it requires tests to run in a particular order. The main test file is test/integration/harvest_integration_test.rb. Examine this to see how the integration suite works.
160
+
161
+ == REQUIREMENTS:
162
+
163
+ Requires active_resource >= 2.1 and activeresource_throttle >= 1.0.
164
+
165
+ active_resource_throttle should install automaticallt, but just in case you need to install manually:
166
+
167
+ gem sources -a http://gems.github.com
168
+ sudo gem install aiaio-active_resource_throttle
169
+
170
+ == INSTALL:
171
+
172
+ gem sources -a http://gems.github.com
173
+ gem install aiaio-harvest
@@ -0,0 +1,44 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ require './lib/harvest.rb'
7
+
8
+ desc 'Default: run all tests.'
9
+ task :default => :test_all
10
+
11
+ namespace :test do
12
+
13
+ Rake::TestTask.new(:resources) do |t|
14
+ t.libs << 'lib'
15
+ t.pattern = 'test/unit/resources/*_test.rb'
16
+ t.verbose = false
17
+ end
18
+
19
+ Rake::TestTask.new(:base) do |t|
20
+ t.libs << 'lib'
21
+ t.pattern = 'test/unit/*_test.rb'
22
+ t.verbose = false
23
+ end
24
+
25
+ Rake::TestTask.new(:integration) do |t|
26
+ t.libs << 'lib'
27
+ t.pattern = 'test/integration/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+ end
32
+
33
+ desc "Test everything."
34
+ task :test_all do
35
+ errors = %w(test:resources test:base).collect do |task|
36
+ begin
37
+ Rake::Task[task].invoke
38
+ nil
39
+ rescue => e
40
+ task
41
+ end
42
+ end.compact
43
+ abort "Errors running #{errors.to_sentence}!" if errors.any?
44
+ end
@@ -0,0 +1,35 @@
1
+ module Harvest
2
+ VERSION = "0.8"
3
+ ApiDomain = "harvestapp.com"
4
+
5
+ # Class method to load all ruby files from a given path.
6
+ def self.load_all_ruby_files_from_path(path)
7
+ Dir.foreach(path) do |file|
8
+ require File.join(path, file) if file =~ /\.rb$/
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ # Gems
15
+ require "activeresource"
16
+ require "active_resource_throttle"
17
+
18
+ # Plugins
19
+ PluginPath = File.join(File.dirname(__FILE__), "harvest", "plugins")
20
+ Harvest.load_all_ruby_files_from_path(PluginPath)
21
+
22
+ # Base
23
+ require File.join(File.dirname(__FILE__), "harvest", "base")
24
+ require File.join(File.dirname(__FILE__), "harvest", "harvest_resource")
25
+
26
+ # Shortcut for Harvest::Base.new
27
+ #
28
+ # Example:
29
+ # Harvest(:email => "jack@exampe.com",
30
+ # :password => "secret",
31
+ # :sub_domain => "frenchie",
32
+ # :headers => {"User-Agent => "Harvest Rubygem"})
33
+ def Harvest(options={})
34
+ Harvest::Base.new(options)
35
+ end
@@ -0,0 +1,77 @@
1
+ module Harvest
2
+ class Base
3
+
4
+ # Requires a sub_domain, email, and password.
5
+ # Specifying headers is optional, but useful for setting a user agent.
6
+ def initialize(options={})
7
+ options.assert_valid_keys(:email, :password, :sub_domain, :headers, :ssl)
8
+ options.assert_required_keys(:email, :password, :sub_domain)
9
+ @email = options[:email]
10
+ @password = options[:password]
11
+ @sub_domain = options[:sub_domain]
12
+ @headers = options[:headers]
13
+ @ssl = options[:ssl]
14
+ configure_base_resource
15
+ end
16
+
17
+ # Below is a series of proxies allowing for easy
18
+ # access to the various resources.
19
+
20
+ # Clients
21
+ def clients
22
+ Harvest::Resources::Client
23
+ end
24
+
25
+ # Expenses.
26
+ def expenses
27
+ Harvest::Resources::Expense
28
+ end
29
+
30
+ # Expense categories.
31
+ def expense_categories
32
+ Harvest::Resources::ExpenseCategory
33
+ end
34
+
35
+ # People.
36
+ # Also provides access to time entries.
37
+ def people
38
+ Harvest::Resources::Person
39
+ end
40
+
41
+ # Projects.
42
+ # Provides access to the assigned users and tasks
43
+ # along with reports for entries on the project.
44
+ def projects
45
+ Harvest::Resources::Project
46
+ end
47
+
48
+ # Tasks.
49
+ def tasks
50
+ Harvest::Resources::Task
51
+ end
52
+
53
+ # Invoices
54
+ def invoices
55
+ Harvest::Resources::Invoice
56
+ end
57
+
58
+ private
59
+
60
+ # Configure resource base class so that
61
+ # inherited classes can access the api.
62
+ def configure_base_resource
63
+ HarvestResource.site = "http#{'s' if @ssl}://#{@sub_domain}.#{Harvest::ApiDomain}"
64
+ HarvestResource.user = @email
65
+ HarvestResource.password = @password
66
+ HarvestResource.headers.update(@headers) if @headers.is_a?(Hash)
67
+ load_resources
68
+ end
69
+
70
+ # Load the classes representing the various resources.
71
+ def load_resources
72
+ resource_path = File.join(File.dirname(__FILE__), "resources")
73
+ Harvest.load_all_ruby_files_from_path(resource_path)
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,15 @@
1
+ module Harvest
2
+ # This is the base class from which all resource
3
+ # classes inherit. Site and authentication params
4
+ # are loaded into this class when a Harvest::Base
5
+ # object is initialized.
6
+ class HarvestResource < ActiveResource::Base
7
+ include ActiveResourceThrottle
8
+ include Harvest::Plugins::ActiveResourceInheritableHeaders
9
+
10
+ # The harvest api will block requests in excess
11
+ # of 40 / 15 seconds. Adds a throttle (with cautious settings).
12
+ # Throttle feature provided by activeresource_throttle gem.
13
+ self.throttle(:requests => 30, :interval => 15, :sleep_interval => 60)
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ # Allows headers in an ActiveResource::Base class
2
+ # to be inherited by subclasses. Useful for setting
3
+ # a User-Agent used by all resources.
4
+ module Harvest
5
+ module Plugins
6
+ module ActiveResourceInheritableHeaders
7
+
8
+ module ClassMethods
9
+
10
+ # If headers are not defined in a
11
+ # given subclass, then obtain headers
12
+ # from the superclass.
13
+ def inheritable_headers
14
+ if defined?(@headers)
15
+ @headers
16
+ elsif superclass != Object && superclass.headers
17
+ superclass.headers
18
+ else
19
+ @headers ||= {}
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ def self.included(klass)
26
+ klass.instance_eval do
27
+ klass.extend ClassMethods
28
+ class << self
29
+ alias_method :headers, :inheritable_headers
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ # Adds toggability to a harvest resource.
2
+ module Harvest
3
+ module Plugins
4
+ module Toggleable
5
+
6
+ def toggle
7
+ put(:toggle)
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module Harvest
2
+ module Resources
3
+ class Client < Harvest::HarvestResource
4
+ include Harvest::Plugins::Toggleable
5
+
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ # The entry resource is never accessed directly;
2
+ # rather, it is manipulated through an instance
3
+ # of Project or Person.
4
+ module Harvest
5
+ module Resources
6
+ class Entry < Harvest::HarvestResource
7
+
8
+ self.element_name = "entry"
9
+
10
+ class << self
11
+
12
+ def project_id=(id)
13
+ @project_id = id
14
+ self.site = self.site + "/projects/#{@project_id}"
15
+ end
16
+
17
+ def project_id
18
+ @project_id
19
+ end
20
+
21
+ def person_id=(id)
22
+ @person_id = id
23
+ self.site = self.site + "/people/#{@person_id}"
24
+ end
25
+
26
+ def person_id
27
+ @person_id
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
34
+ end