README.md in harvesting-0.3.0 vs README.md in harvesting-0.4.0
- old
+ new
@@ -1,7 +1,10 @@
# Harvesting
+[](https://travis-ci.org/ombulabs/harvesting)
+[](https://codeclimate.com/github/ombulabs/harvesting)
+
A Ruby gem to interact with the Harvest API v2.0 and forward.
## Installation
Add this line to your application's Gemfile:
@@ -37,91 +40,125 @@
* `ENV['HARVEST_ACCESS_TOKEN']`
* `ENV['HARVEST_ACCOUNT_ID']`
That means that you could build a client like this:
- # $ export HARVEST_ACCESS_TOKEN=abc
- # $ export HARVEST_ACCOUNT_ID=12345678
- client = Harvesting::Client.new
- client.me
- > => #<Harvesting::Models::User:0x007ff8830658f0 @attributes={"id"=>2108614, "first_name"=>"Ernesto", "last_name"=>"Tagwerker", ... >
+```ruby
+# $ export HARVEST_ACCESS_TOKEN=abc
+# $ export HARVEST_ACCOUNT_ID=12345678
+client = Harvesting::Client.new
+client.me
+# => #<Harvesting::Models::User:0x007ff8830658f0 @attributes={"id"=>2108614, "first_name"=>"Ernesto", "last_name"=>"Tagwerker", ... >
+```
If you don't specify a valid combination of token and account id, your code will
raise this error:
- client = Harvesting::Client.new(access_token: "foo", account_id: "bar")
- client.me
- > Harvesting::AuthenticationError: {"error":"invalid_token","error_description":"The access token provided is expired, revoked, malformed or invalid for other reasons."}
+```ruby
+client = Harvesting::Client.new(access_token: "foo", account_id: "bar")
+client.me
+# Harvesting::AuthenticationError: {"error":"invalid_token","error_description":"The access token provided is expired, revoked, malformed or invalid for other reasons."}
+```
If your personal token and account id are valid, you should see something like
this:
- client = Harvesting::Client.new(access_token: "<your token here>", account_id: "<your account id here>")
- user = client.me
- > => #<Harvesting::Models::User:0x007ff8830658f0 @attributes={"id"=>2108614, "first_name"=>"Ernesto", "last_name"=>"Tagwerker", ... >
+```ruby
+client = Harvesting::Client.new(access_token: "<your token here>", account_id: "<your account id here>")
+user = client.me
+# => #<Harvesting::Models::User:0x007ff8830658f0 @attributes={"id"=>2108614, "first_name"=>"Ernesto", "last_name"=>"Tagwerker", ... >
- user.id
- > => 2108614
+user.id
+# => 2108614
+```
### Clients
- client.clients
- > => [#<Harvesting::Models::Client:0x007ff718d65fd0 @attributes={"id"=>6760580, "name"=>"Toto", "is_active"=>true, "address"=>"" ... >
+```ruby
+client.clients
+# => [#<Harvesting::Models::Client:0x007ff718d65fd0 @attributes={"id"=>6760580, "name"=>"Toto", "is_active"=>true, "address"=>"" ... >
- client = client.clients.first
- > => #<Harvesting::Models::Client:0x007ff718cf5fc8 @attributes={"id"=>6760580, "name"=>"Toto",
- ... >
+client = client.clients.first
+# => #<Harvesting::Models::Client:0x007ff718cf5fc8 @attributes={"id"=>6760580, "name"=>"Toto",
+# ... >
+```
### Time Entries
- time_entries = client.time_entries
- > => #<Harvesting::Models::TimeEntries:0x007ff71913e3a0 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>14, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/time_entries?limit=1&page=1&per_page=100", "next"=>nil, "previous"=>nil, "last"=>"https://api.harvestapp.com/v2/time_entries?limit=1&page=1&per_page=100"}}, ... >
+```ruby
+time_entries = client.time_entries
+# => #<Harvesting::Models::TimeEntries:0x007ff71913e3a0 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>14, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/time_entries?limit=1&page=1&per_page=100", "next"=>nil, "previous"=>nil, "last"=>"https://api.harvestapp.com/v2/time_entries?limit=1&page=1&per_page=100"}}, ... >
- entry = time_entries.first
- > => #<Harvesting::Models::TimeEntry:0x007ff71913dfe0 @attributes={"id"=>792860513, "spent_date"=>"2018-05-14", "hours"=>1.0, "notes"=>"hacked the things", "is_locked"=>false, "locked_reason"=>nil, "is_closed"=>false, "is_billed"=>false, "timer_started_at"=>nil, "started_time"=>nil, "ended_time"=>nil, "is_running"=>false, "billable"=>true, "budgeted"=>false, "billable_rate"=>nil, "cost_rate ... >
+entry = time_entries.first
+# => #<Harvesting::Models::TimeEntry:0x007ff71913dfe0 @attributes={"id"=>792860513, "spent_date"=>"2018-05-14", "hours"=>1.0, "notes"=>"hacked the things", "is_locked"=>false, "locked_reason"=>nil, "is_closed"=>false, "is_billed"=>false, "timer_started_at"=>nil, "started_time"=>nil, "ended_time"=>nil, "is_running"=>false, "billable"=>true, "budgeted"=>false, "billable_rate"=>nil, "cost_rate ... >
+```
### Tasks
- tasks = client.tasks
- > => #<Harvesting::Models::Tasks:0x007ff718897990 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>6, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/tasks?page=1&per_page=100", "next"=>nil, ... >
+```ruby
+tasks = client.tasks
+# => #<Harvesting::Models::Tasks:0x007ff718897990 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>6, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/tasks?page=1&per_page=100", "next"=>nil, ... >
+```
### Projects
- projects = client.projects
- > => #<Harvesting::Models::Projects:0x007ff718e1c8e8 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>1, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/projects?page=1&per_page=100", ... >
+```ruby
+projects = client.projects
+# => #<Harvesting::Models::Projects:0x007ff718e1c8e8 @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>1, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/projects?page=1&per_page=100", ... >
- project = projects.first
- > => #<Harvesting::Models::Project:0x007ff718e1c618 @attributes={"id"=>17367712, "name"=>"Foo", "code"=>"", "is_active"=>true, "is_billable"=>true, "is_fixed_fee"=>false, "bill_by"=>"none", "budget"=>nil, "budget_by"=>"none", "budget_is_monthly"=>false, "notify_when_over_budget"=>false, "over_budget_notification_percentage"=>80.0, "show_budget_to_all"=>false, "created_at"=>"2018-05-13T03:30:06Z", ... >
+project = projects.first
+# => #<Harvesting::Models::Project:0x007ff718e1c618 @attributes={"id"=>17367712, "name"=>"Foo", "code"=>"", "is_active"=>true, "is_billable"=>true, "is_fixed_fee"=>false, "bill_by"=>"none", "budget"=>nil, "budget_by"=>"none", "budget_is_monthly"=>false, "notify_when_over_budget"=>false, "over_budget_notification_percentage"=>80.0, "show_budget_to_all"=>false, "created_at"=>"2018-05-13T03:30:06Z", ... >
+```
+### Invoices
+```ruby
+invoices = client.invoices
+# => #<Harvesting::Models::Invoices:0x00007fc8905671f0 @models={}, @attributes={"per_page"=>100, "total_pages"=>1, "total_entries"=>3, "next_page"=>nil, "previous_page"=>nil, "page"=>1, "links"=>{"first"=>"https://api.harvestapp.com/v2/invoices?page=1&per_page=100", ... >
+
+invoice = invoices.first
+# => #<Harvesting::Models::Invoice:0x00007f8c37eb6d18 @models={}, @attributes={"id"=>23831208, "client_key"=>"73688e97a43ed497ace45939eb76db6b18427b80", "number"=>"3", "purchase_order"=>"", "amount"=>750.0, "due_amount"=>750.0, "tax"=>nil, "tax_amount"=>0.0, "tax2"=>nil, "tax2_amount"=>0.0, "discount"=>nil, "discount_amount"=>0.0, "subject"=>"", "notes"=>"", "state"=>"draft", "period_start"=>nil, ... >
+```
+
+An invoice can have many line items:
+```ruby
+line_items = invoice.line_items
+# => [#<Harvesting::Models::LineItem:0x00007f92617ce8e0 @models={}, @attributes={"id"=>109677268, "kind"=>"Service", "description"=>"", "quantity"=>3.0, "unit_price"=>250.0, "amount"=>750.0, "taxed"=>false, "taxed2"=>false, "project"=>{"id"=>24566828, "name"=>"Harvest Billing Automation", "code"=>""}}, ... >]
+```
+
+You can filter invoices by various attributes. E.g. `client.invoices(state: "draft")` only returns invoices in a draft state.
+
### Nested Attributes
The Harvest v2 API embeds some data in JSON objects. You can access nested attributes quite naturally.
For example, to access the user id for a time entry instance, `entry`, use:
- entry.user.id
+```ruby
+entry.user.id
+```
Or to access the name of the client on a project instance, `project`:
+```ruby
+project.client.name
+```
- project.client.name
-
## Tips
### Deleting All Items
When you need to delete all items, care needs to be taken, because the API uses pagination. The following code will only delete data from _every other_ page.
-```
+```ruby
# WARNING - only deletes every other page
client.time_entries.each do |time_entry|
time_entry.delete
end
```
While iterating over items from the first page, all of those items will be deleted. This will result in moving items from the second page onto the first page. If there are only two pages, then the second page will be empty. If there are more than two pages, then the second page will now contain items which would have previously appeared on the third page. Deleting those items will move the items from the fourth page on to the third page, and so on.
Instead you need to make sure you get access to all of the time entry objects before you try to delete any of them. The easiest way to do this is to convert the `Enumerable` instance into an `Array`, by calling `#to_a`, before you iterate over it.
-```
+```ruby
# GOOD - This should do what you want
client.time_entries.to_a.each do |time_entry|
time_entry.delete
end
```