aiaio-harvest 0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/HISTORY +3 -0
  2. data/LICENSE +23 -0
  3. data/README.rdoc +146 -0
  4. data/Rakefile +44 -0
  5. data/lib/harvest.rb +36 -0
  6. data/lib/harvest/base.rb +71 -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_teardown.rb +12 -0
  25. data/test/integration/task_integration.rb +19 -0
  26. data/test/integration/task_teardown.rb +12 -0
  27. data/test/test_helper.rb +51 -0
  28. data/test/unit/base_test.rb +71 -0
  29. data/test/unit/resources/client_test.rb +82 -0
  30. data/test/unit/resources/expense_category_test.rb +49 -0
  31. data/test/unit/resources/expense_test.rb +14 -0
  32. data/test/unit/resources/person_test.rb +151 -0
  33. data/test/unit/resources/project_test.rb +154 -0
  34. data/test/unit/resources/task_assignment_test.rb +72 -0
  35. data/test/unit/resources/task_test.rb +82 -0
  36. data/test/unit/resources/user_assignment_test.rb +71 -0
  37. metadata +106 -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,146 @@
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.
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
+ ==== User and Task Assignments
88
+
89
+ @project.users.find(:all)
90
+ @project.users.find(:first)
91
+
92
+ @project.tasks.find(:all)
93
+ @project.tasks.find(:first)
94
+
95
+ === Reports
96
+
97
+ @project.entries(:from => Time.now, :to => Time.now)
98
+ @project.entries(:from => Time.now, :to => Time.now, :user_id => @person.id)
99
+
100
+ @person.entries(:from => Time.now, :to => Time.now)
101
+ @person.expenses(:from => Time.now, :to => Time.now)
102
+
103
+ == FEATURES/PROBLEMS:
104
+
105
+ Supports most of the Harvest Api (http://www.getharvest.com/api).
106
+
107
+ 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>
108
+
109
+ The following Api features are incomplete due to incompatibilities with ActiveResource conventions (CRUD actions in parentheses):
110
+
111
+ 1. People (CUD)
112
+ 2. User & Task Assignments (CUD)
113
+ 3. Expenses (CUD)
114
+
115
+ In addition, the following have not been implemented at all due to time constraints:
116
+
117
+ 1. Time Tracking Api
118
+ 2. Invoices, Invoice Payments, Invoice Categories, and Invoice Messages
119
+
120
+ Expect a more complete library in the near future. Again, contributions are welcome.
121
+
122
+ == TEST SUITE
123
+
124
+ This library includes a fairly thorough set of unit and integration tests. The unit tests can be run with the "rake" command.
125
+
126
+ The integration tests require a Harvest account. It is best to use a trial account when running these tests.
127
+
128
+ The credentials may be entered in test/test_helper.rb. To run the integration suite:
129
+
130
+ rake test:integration
131
+
132
+ 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.
133
+
134
+ == REQUIREMENTS:
135
+
136
+ Requires active_resource >= 2.1 and activeresource_throttle >= 1.0.
137
+
138
+ active_resource_throttle should install automaticallt, but just in case you need to install manually:
139
+
140
+ gem sources -a http://gems.github.com
141
+ sudo gem install aiaio-active_resource_throttle
142
+
143
+ == INSTALL:
144
+
145
+ gem sources -a http://gems.github.com
146
+ 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,36 @@
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 "rubygems"
16
+ require "activeresource"
17
+ require "active_resource_throttle"
18
+
19
+ # Plugins
20
+ PluginPath = File.join(File.dirname(__FILE__), "harvest", "plugins")
21
+ Harvest.load_all_ruby_files_from_path(PluginPath)
22
+
23
+ # Base
24
+ require File.join(File.dirname(__FILE__), "harvest", "base")
25
+ require File.join(File.dirname(__FILE__), "harvest", "harvest_resource")
26
+
27
+ # Shortcut for Harvest::Base.new
28
+ #
29
+ # Example:
30
+ # Harvest(:email => "jack@exampe.com",
31
+ # :password => "secret",
32
+ # :sub_domain => "frenchie",
33
+ # :headers => {"User-Agent => "Harvest Rubygem"})
34
+ def Harvest(options={})
35
+ Harvest::Base.new(options)
36
+ end
@@ -0,0 +1,71 @@
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)
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
+ configure_base_resource
14
+ end
15
+
16
+ # Below is a series of proxies allowing for easy
17
+ # access to the various resources.
18
+
19
+ # Clients
20
+ def clients
21
+ Harvest::Resources::Client
22
+ end
23
+
24
+ # Expenses.
25
+ def expenses
26
+ Harvest::Resources::Expense
27
+ end
28
+
29
+ # Expense categories.
30
+ def expense_categories
31
+ Harvest::Resources::ExpenseCategory
32
+ end
33
+
34
+ # People.
35
+ # Also provides access to time entries.
36
+ def people
37
+ Harvest::Resources::Person
38
+ end
39
+
40
+ # Projects.
41
+ # Provides access to the assigned users and tasks
42
+ # along with reports for entries on the project.
43
+ def projects
44
+ Harvest::Resources::Project
45
+ end
46
+
47
+ # Tasks.
48
+ def tasks
49
+ Harvest::Resources::Task
50
+ end
51
+
52
+ private
53
+
54
+ # Configure resource base class so that
55
+ # inherited classes can access the api.
56
+ def configure_base_resource
57
+ HarvestResource.site = "http://#{@sub_domain}.#{Harvest::ApiDomain}"
58
+ HarvestResource.user = @email
59
+ HarvestResource.password = @password
60
+ HarvestResource.headers.update(@headers) if @headers.is_a?(Hash)
61
+ load_resources
62
+ end
63
+
64
+ # Load the classes representing the various resources.
65
+ def load_resources
66
+ resource_path = File.join(File.dirname(__FILE__), "resources")
67
+ Harvest.load_all_ruby_files_from_path(resource_path)
68
+ end
69
+
70
+ end
71
+ 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
@@ -0,0 +1,22 @@
1
+ module Harvest
2
+ module Resources
3
+ class Expense < Harvest::HarvestResource
4
+
5
+ self.element_name = "expense"
6
+
7
+ class << self
8
+
9
+ def person_id=(id)
10
+ @person_id = id
11
+ self.site = self.site + "/people/#{@person_id}"
12
+ end
13
+
14
+ def person_id
15
+ @person_id
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end