zoho_hub 0.3.0 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +37 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -1
- data/README.md +285 -44
- data/bin/console +1 -1
- data/examples/models/potential.rb +2 -0
- data/lib/zoho_hub/auth.rb +1 -0
- data/lib/zoho_hub/base_record.rb +97 -2
- data/lib/zoho_hub/cli/callback_server.rb +5 -5
- data/lib/zoho_hub/cli/read_modules.rb +4 -4
- data/lib/zoho_hub/connection.rb +16 -3
- data/lib/zoho_hub/errors.rb +15 -0
- data/lib/zoho_hub/modules/attachment.rb +41 -0
- data/lib/zoho_hub/notifications.rb +54 -0
- data/lib/zoho_hub/response.rb +41 -14
- data/lib/zoho_hub/string_utils.rb +1 -1
- data/lib/zoho_hub/version.rb +1 -1
- data/lib/zoho_hub/with_attributes.rb +32 -5
- data/lib/zoho_hub/with_connection.rb +3 -3
- data/lib/zoho_hub.rb +3 -1
- data/zoho_hub.gemspec +3 -1
- metadata +48 -5
- data/.travis.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d69358949e8262ad9da808101b0bbb2da5d0eae262d2d34ab032e5097d58f5ba
|
4
|
+
data.tar.gz: a0b64a4714925fa5a6392e43ecd991aaf71e9b3991e4505a5d2703bca22f8269
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d02305e21aae61e11f09a010b8f22dab327af638b036ca075abb47e0943eaad1870355e976c208b00c9a5dfa87f05ff82f60754fd50f1eccee97e37a7e3c8967
|
7
|
+
data.tar.gz: 756fb2bd03d39e70f46fdc04eb34bea1310701b72ba363a3b6c79fab033ccf5defa2b615edcae9af6c86cae45dacf24bcefd59c3c17ff2a4c3b7456f0595b0ed
|
@@ -0,0 +1,37 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
lint:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- uses: ruby/setup-ruby@v1
|
11
|
+
with:
|
12
|
+
ruby-version: '2.5'
|
13
|
+
bundler-cache: true
|
14
|
+
- name: rubocop version
|
15
|
+
timeout-minutes: 1
|
16
|
+
run: bundle exec rubocop --version
|
17
|
+
- name: rubocop
|
18
|
+
timeout-minutes: 5
|
19
|
+
run: bundle exec rubocop -c .rubocop.yml
|
20
|
+
|
21
|
+
test:
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
continue-on-error: ${{ matrix.experimental }}
|
24
|
+
strategy:
|
25
|
+
fail-fast: false
|
26
|
+
matrix:
|
27
|
+
ruby: ['2.5', '2.6', '2.7', '3.0']
|
28
|
+
experimental: [false]
|
29
|
+
steps:
|
30
|
+
- uses: actions/checkout@v2
|
31
|
+
- uses: ruby/setup-ruby@v1
|
32
|
+
with:
|
33
|
+
ruby-version: ${{matrix.ruby}}
|
34
|
+
bundler-cache: true
|
35
|
+
- name: Run tests
|
36
|
+
timeout-minutes: 5
|
37
|
+
run: ${{matrix.env}} bundle exec rspec
|
data/.rubocop.yml
CHANGED
@@ -3,6 +3,13 @@ require: rubocop-rspec
|
|
3
3
|
AllCops:
|
4
4
|
TargetRubyVersion: 2.5.1
|
5
5
|
|
6
|
+
Gemspec/RequiredRubyVersion:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
Lint:
|
10
|
+
Exclude:
|
11
|
+
- bin/*
|
12
|
+
|
6
13
|
# Don't force top level comments in every class
|
7
14
|
Style/Documentation:
|
8
15
|
Enabled: false
|
@@ -25,6 +32,12 @@ Metrics/ClassLength:
|
|
25
32
|
Metrics/MethodLength:
|
26
33
|
Max: 20
|
27
34
|
|
35
|
+
Metrics/CyclomaticComplexity:
|
36
|
+
Max: 10
|
37
|
+
|
38
|
+
Metrics/PerceivedComplexity:
|
39
|
+
Max: 10
|
40
|
+
|
28
41
|
Metrics/AbcSize:
|
29
42
|
Max: 30
|
30
43
|
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.7.6
|
data/README.md
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# ZohoHub
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.com/rikas/zoho_hub.svg?branch=master)](https://travis-ci.com/rikas/zoho_hub)
|
4
3
|
[![Gem Version](https://badge.fury.io/rb/zoho_hub.svg)](https://badge.fury.io/rb/zoho_hub)
|
5
4
|
|
6
|
-
Simple wrapper around Zoho CRM version2, using
|
7
|
-
for authentication.
|
5
|
+
Simple wrapper around Zoho CRM version2, using
|
6
|
+
[OAuth 2.0 protocol](https://www.zoho.com/crm/help/developer/api/oauth-overview.html) for authentication.
|
8
7
|
|
9
8
|
This gem reads your Module configuration and builds the corresponding classes for you, using some
|
10
9
|
reflection mechanisms. You should then be able to use simple classes with an API close to
|
@@ -12,6 +11,38 @@ ActiveRecord, to do CRUD operations.
|
|
12
11
|
|
13
12
|
**NOTE: this gem is WIP, please try to use it and open an issue if you run into limitations / problems**
|
14
13
|
|
14
|
+
## Table of Contents
|
15
|
+
|
16
|
+
- [ZohoHub](#zohohub)
|
17
|
+
- [Table of Contents](#table-of-contents)
|
18
|
+
- [Installation](#installation)
|
19
|
+
- [Setup process](#setup-process)
|
20
|
+
- [1. Register your application](#1-register-your-application)
|
21
|
+
- [1.1 Zoho Accounts URL](#11-zoho-accounts-url)
|
22
|
+
- [1.2 Authorized Redirect URI](#12-authorized-redirect-uri)
|
23
|
+
- [2. Configure ZohoHub with your credentials](#2-configure-zohohub-with-your-credentials)
|
24
|
+
- [3. Authorization request](#3-authorization-request)
|
25
|
+
- [3.1 Redirection based authentication](#31-redirection-based-authentication)
|
26
|
+
- [3.2 Self-Client Authorization](#32-self-client-authorization)
|
27
|
+
- [3.3 More on scopes](#33-more-on-scopes)
|
28
|
+
- [3.4 Offline access](#34-offline-access)
|
29
|
+
- [4. Access token](#4-access-token)
|
30
|
+
- [5. Refresh token](#5-refresh-token)
|
31
|
+
- [6. Basic ZohoHub flow](#6-basic-zohohub-flow)
|
32
|
+
- [7. BaseRecord and record classes](#7-baserecord-and-record-classes)
|
33
|
+
- [7.1 Reflection](#71-reflection)
|
34
|
+
- [7.2 Subclassing BaseRecord](#72-subclassing-baserecord)
|
35
|
+
- [8 Notifications](#8-notifications)
|
36
|
+
- [8.1 Enable notifications](#81-enable-notifications)
|
37
|
+
- [8.2 List notifications](#82-list-notifications)
|
38
|
+
- [8.3 Caveats](#83-caveats)
|
39
|
+
- [Tips and suggestions](#tips-and-suggestions)
|
40
|
+
- [Examples](#examples)
|
41
|
+
- [Setup auth token and request CurrentUser](#setup-auth-token-and-request-currentuser)
|
42
|
+
- [Development](#development)
|
43
|
+
- [Contributing](#contributing)
|
44
|
+
- [License](#license)
|
45
|
+
|
15
46
|
## Installation
|
16
47
|
|
17
48
|
Add this line to your application's Gemfile:
|
@@ -35,19 +66,21 @@ Or install it yourself as:
|
|
35
66
|
If you want to access your Zoho CRM account from your application you first need to create your
|
36
67
|
application as described here: https://www.zoho.com/crm/help/developer/api/register-client.html.
|
37
68
|
|
38
|
-
This will give you a **Client ID** and a **secret**, that you'll use in
|
69
|
+
This will give you a **Client ID** and a **secret**, that you'll use in
|
70
|
+
[step 2](#2-configure-zohohub-with-your-credentials).
|
39
71
|
|
40
72
|
#### 1.1 Zoho Accounts URL
|
41
73
|
|
42
74
|
Registration and authorization requests are made to Zoho's domain-specific Accounts URL which
|
43
75
|
varies depending on your region:
|
44
76
|
|
45
|
-
|
46
|
-
|
47
|
-
|
77
|
+
- China: https://accounts.zoho.com.cn
|
78
|
+
- EU: https://accounts.zoho.eu
|
79
|
+
- India: https://accounts.zoho.in
|
80
|
+
- US: https://accounts.zoho.com
|
48
81
|
|
49
|
-
ZohoHub uses the EU Account URL by default, but this can be overriden in a `ZohoHub.configure`
|
50
|
-
|
82
|
+
ZohoHub uses the EU Account URL by default, but this can be overriden in a `ZohoHub.configure` block
|
83
|
+
via the `api_domain` method ([step 2](#2-configure-zohohub-with-your-credentials).)
|
51
84
|
|
52
85
|
#### 1.2 Authorized Redirect URI
|
53
86
|
|
@@ -55,17 +88,20 @@ Per Zoho's API documentation, providing a **redirect URI** is optional. Doing so
|
|
55
88
|
your application to be redirected back to your app (to the **redirect URI**) with a **grant token**
|
56
89
|
upon successful authentication.
|
57
90
|
|
58
|
-
If you don't provide a **redirect URI**, you'll need to use the
|
59
|
-
|
91
|
+
If you don't provide a **redirect URI**, you'll need to use the
|
92
|
+
[self-client option](https://www.zoho.com/crm/help/developer/api/auth-request.html#self-client) for
|
93
|
+
authorization (see [3.2](#32-self-client-authorization).)
|
94
|
+
|
95
|
+
---
|
60
96
|
|
61
97
|
### 2. Configure ZohoHub with your credentials
|
62
98
|
|
63
|
-
> **Note:** Treat these credentials like an important password. It is
|
64
|
-
> paste them anywhere in plain text. Do
|
99
|
+
> **Note:** Treat these credentials like an important password. It is _strongly_ recommended to not
|
100
|
+
> paste them anywhere in plain text. Do _not_ add them to version control; keep them out of your
|
65
101
|
> code directly by referencing them via environment variables. Use something like the dotenv gem or
|
66
102
|
> encrypted credentials in Rails to keep them as secret and secure as possible.
|
67
103
|
|
68
|
-
You need to have a configuration block like the one below (in
|
104
|
+
You need to have a configuration block like the one below (in Rails add a `zoho_hub.rb` in your
|
69
105
|
`config/initializers` directory):
|
70
106
|
|
71
107
|
```ruby
|
@@ -78,6 +114,8 @@ ZohoHub.configure do |config|
|
|
78
114
|
end
|
79
115
|
```
|
80
116
|
|
117
|
+
---
|
118
|
+
|
81
119
|
### 3. Authorization request
|
82
120
|
|
83
121
|
In order to access data in Zoho CRM you need to authorize ZohoHub to access your account. To do so
|
@@ -94,7 +132,8 @@ ZohoHub::Auth.auth_url
|
|
94
132
|
# => "https://accounts.zoho.eu/oauth/v2/auth?access_type=offline&client_id=&redirect_uri=&response_type=code&scope=ZohoCRM.modules.custom.all,ZohoCRM.settings.all,ZohoCRM.modules.contacts.all,ZohoCRM.modules.all"
|
95
133
|
```
|
96
134
|
|
97
|
-
If you request this generated URL you should see a screen like this one, where you have to click on
|
135
|
+
If you request this generated URL you should see a screen like this one, where you have to click on
|
136
|
+
"Accept":
|
98
137
|
|
99
138
|
![](https://duaw26jehqd4r.cloudfront.net/items/1h1i3C1N0k0i02092F0S/Screen%20Shot%202018-11-25%20at%2019.18.38.png)
|
100
139
|
|
@@ -109,7 +148,8 @@ as follows (the value after `code=` is the **grant token**):
|
|
109
148
|
|
110
149
|
If you don't have a **redirect URI** or you want your application to be able to authorize with Zoho
|
111
150
|
programmatically (without a user required to be present and click the "Accept" prompt), Zoho
|
112
|
-
provides a
|
151
|
+
provides a
|
152
|
+
[self-client option](https://www.zoho.com/crm/help/developer/api/auth-request.html#self-client)
|
113
153
|
for authentication which will provide a **grant token**.
|
114
154
|
|
115
155
|
#### 3.3 More on scopes
|
@@ -127,11 +167,13 @@ ZohoCRM.modules.all
|
|
127
167
|
To get the URL for a different scope you can provide a `scope` argument:
|
128
168
|
|
129
169
|
```ruby
|
130
|
-
ZohoHub::Auth.auth_url(
|
170
|
+
ZohoHub::Auth.auth_url(scopes: ['ZohoCRM.modules.custom.all', 'ZohoCRM.modules.all'])
|
131
171
|
# => "https://accounts.zoho.eu/oauth/v2/auth?access_type=offline&client_id=&redirect_uri=&response_type=code&scope=ZohoCRM.modules.custom.all,ZohoCRM.modules.all"
|
132
172
|
```
|
133
173
|
|
134
|
-
Refer to
|
174
|
+
Refer to
|
175
|
+
[Zoho's API documentation on scopes](https://www.zoho.com/crm/help/developer/api/oauth-overview.html#scopes)
|
176
|
+
for detailed information.
|
135
177
|
|
136
178
|
#### 3.4 Offline access
|
137
179
|
|
@@ -139,9 +181,9 @@ By design the **access tokens** returned by the OAuth flow expire after a period
|
|
139
181
|
default), as a safety mechanism. This means that any application that wants to work with a user's
|
140
182
|
data needs the user to have recently gone through the OAuth flow, aka be online.
|
141
183
|
|
142
|
-
When you request offline access the Zoho API returns a **refresh token**. **Refresh tokens** give
|
143
|
-
application the ability to request data on behalf of the user when the user is not present and
|
144
|
-
front of your application.
|
184
|
+
When you request offline access the Zoho API returns a **refresh token**. **Refresh tokens** give
|
185
|
+
your application the ability to request data on behalf of the user when the user is not present and
|
186
|
+
in front of your application.
|
145
187
|
|
146
188
|
**By default `ZohoHub::Auth.auth_url` will request offline access**
|
147
189
|
|
@@ -152,6 +194,8 @@ ZohoHub::Auth.auth_url(access_type: 'online')
|
|
152
194
|
# => "https://accounts.zoho.eu/oauth/v2/auth?access_type=online&client_id=&redirect_uri=&response_type=code&scope=ZohoCRM.modules.custom.all,ZohoCRM.settings.all,ZohoCRM.modules.contacts.all,ZohoCRM.modules.all"
|
153
195
|
```
|
154
196
|
|
197
|
+
---
|
198
|
+
|
155
199
|
### 4. Access token
|
156
200
|
|
157
201
|
See Zoho's API documentation for generating an initial **access token**:
|
@@ -163,10 +207,15 @@ To use an **access token** in a manual request, include it as a request header a
|
|
163
207
|
To use an **access token** with ZohoHub, pass it to the `ZohoHub.setup_connection` method as the
|
164
208
|
`access_token` parameter.
|
165
209
|
|
210
|
+
---
|
166
211
|
|
167
212
|
### 5. Refresh token
|
168
213
|
|
169
|
-
|
214
|
+
This gem automatically refresh the access token.
|
215
|
+
|
216
|
+
If you want automatic refresh, use the `refresh_token` argument as in the next chapter.
|
217
|
+
|
218
|
+
---
|
170
219
|
|
171
220
|
### 6. Basic ZohoHub flow
|
172
221
|
|
@@ -176,7 +225,8 @@ token**, setup a ZohoHub connection:
|
|
176
225
|
```ruby
|
177
226
|
ZohoHub.setup_connection access_token: 'ACCESS_TOKEN',
|
178
227
|
expires_in: 'EXPIRES_IN_SEC',
|
179
|
-
api_domain: 'API_DOMAIN'
|
228
|
+
api_domain: 'API_DOMAIN',
|
229
|
+
refresh_token: 'REFRESH_TOKEN'
|
180
230
|
```
|
181
231
|
|
182
232
|
Now you can issue requests to Zoho's API with the Connection object, e.g.:
|
@@ -186,7 +236,10 @@ Now you can issue requests to Zoho's API with the Connection object, e.g.:
|
|
186
236
|
ZohoHub.connection.get 'Leads'
|
187
237
|
```
|
188
238
|
|
189
|
-
A successful request will receive a response like the sample here:
|
239
|
+
A successful request will receive a response like the sample here:
|
240
|
+
https://www.zoho.com/crm/help/developer/api/get-records.html.
|
241
|
+
|
242
|
+
---
|
190
243
|
|
191
244
|
### 7. BaseRecord and record classes
|
192
245
|
|
@@ -208,7 +261,7 @@ inherits from `ZohoHub::BaseRecord`. For example, to build a class for the Leads
|
|
208
261
|
```ruby
|
209
262
|
# lead.rb
|
210
263
|
|
211
|
-
class Lead < BaseRecord
|
264
|
+
class Lead < ZohoHub::BaseRecord
|
212
265
|
...
|
213
266
|
end
|
214
267
|
```
|
@@ -218,44 +271,231 @@ Specify this module's fields as attributes:
|
|
218
271
|
```ruby
|
219
272
|
# lead.rb
|
220
273
|
|
221
|
-
class Lead < BaseRecord
|
222
|
-
attributes
|
274
|
+
class Lead < ZohoHub::BaseRecord
|
275
|
+
attributes :id, :first_name, :last_name, :phone, :email, :source, # etc.
|
223
276
|
end
|
224
277
|
```
|
225
278
|
|
226
|
-
|
279
|
+
Now you can issue requests more easily with your record class, e.g.:
|
227
280
|
|
228
281
|
```ruby
|
229
|
-
|
230
|
-
|
231
|
-
zoho_key = attr_to_zoho_key(attr)
|
282
|
+
# Request a (paginated) list of all Lead records
|
283
|
+
Lead.all
|
232
284
|
|
233
|
-
|
234
|
-
|
235
|
-
end
|
285
|
+
# Get the Lead instance with a specific ID
|
286
|
+
Lead.find('78265000003433063')
|
236
287
|
```
|
237
288
|
|
238
|
-
|
289
|
+
And even create new Lead entries in Zoho:
|
239
290
|
|
240
291
|
```ruby
|
241
|
-
|
242
|
-
|
292
|
+
lead = Lead.new(
|
293
|
+
first_name: 'First name',
|
294
|
+
last_name: 'Last name',
|
295
|
+
phone: '+35197736281',
|
296
|
+
email: 'myemail@gmail.com',
|
297
|
+
source: 'Homepage'
|
298
|
+
)
|
299
|
+
|
300
|
+
# Creates the new lead
|
301
|
+
lead.save
|
302
|
+
|
303
|
+
# Or in one step:
|
304
|
+
lead = Lead.create(first_name: 'First name', ...)
|
305
|
+
```
|
306
|
+
|
307
|
+
Updating records:
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
Lead.update(id: lead.id, first_name: "...", last_name: "...")
|
311
|
+
|
312
|
+
# Or
|
313
|
+
lead.update(first_name: "...", last_name: "...")
|
314
|
+
|
315
|
+
# Or update up to 100 records in one call:
|
316
|
+
leads = [{ id: id1, phone: "123" }, { id: id2, first_name: "..." }]
|
317
|
+
Lead.update_all(leads)
|
318
|
+
```
|
319
|
+
|
320
|
+
Blueprint transition:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
Lead.blueprint_transition(lead.id, transition_id)
|
324
|
+
|
325
|
+
# Or
|
326
|
+
lead.blueprint_transition(transition_id)
|
327
|
+
```
|
328
|
+
|
329
|
+
Adding notes:
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
Lead.add_note(id: lead.id, title: 'Note title', content: 'Note content')
|
333
|
+
```
|
334
|
+
|
335
|
+
Related records:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
Product.all_related(parent_module: 'Lead', parent_id: lead.id)
|
339
|
+
Product.add_related(
|
340
|
+
parent_module: 'Lead',
|
341
|
+
parent_id: lead.id,
|
342
|
+
related_id: product.id
|
343
|
+
)
|
344
|
+
Product.remove_related(
|
345
|
+
parent_module: 'Lead',
|
346
|
+
parent_id: lead.id,
|
347
|
+
related_id: product.id
|
348
|
+
)
|
349
|
+
Product.update_related(...)
|
350
|
+
```
|
351
|
+
|
352
|
+
Attachments (`ZohoHub::Attachment` is defined in the gem):
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
Lead.related_attachments(parent_id: lead.id)
|
356
|
+
# -> Array of Attachments
|
357
|
+
|
358
|
+
attachment = Lead.download_attachment(parent_id: lead.id, attachment_id:attachment.id)
|
359
|
+
# -> Attachment (attachment.file contains the file as a Tempfile)
|
360
|
+
|
361
|
+
#NB: Lead.upload_attachment not implemented yet
|
362
|
+
```
|
363
|
+
|
364
|
+
## 8 Notifications
|
365
|
+
|
366
|
+
Zoho allows you to receive a notification when a record of a module changes. Supported operation types are create, delete, edit, all.
|
367
|
+
|
368
|
+
### 8.1 Enable notifications
|
369
|
+
|
370
|
+
In order to receive notifications, you have to enable them first.
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
# Enable notifications for a given channel:
|
374
|
+
notification_url = 'https://example.org/api/notifications' # Zoho will send notifications by POST to this url
|
375
|
+
token = '123abc' # Zoho will send this token back to you, so you can ensure that the notification is from Zoho
|
376
|
+
channel_id = 1 # Choose a channel to handle the response
|
377
|
+
events = %w[Leads.create Deals.edit Contacts.delete Sales_Orders.all] # Which events to receive notifications for
|
378
|
+
channel_expiry = (DateTime.now + 1.day).iso8601 # choose a date when the channel should expire. 24h is the maximum, default is one hour
|
379
|
+
|
380
|
+
ZohoHub::Notifications.enable(notification_url, channel_id, events, channel_expiry, token)
|
381
|
+
```
|
382
|
+
|
383
|
+
After enabling notifications, Zoho will execute a POST request to the provided notification_url every time the requested event occurs.
|
384
|
+
|
385
|
+
For a list of an in-depth description of the response, check the [Zoho documentation](https://www.zoho.com/crm/developer/docs/api/notifications/overview.html)
|
386
|
+
|
387
|
+
### 8.2 List notifications
|
388
|
+
|
389
|
+
You can also retrieve all notifications that are currently enabled and that you are receiving uppdates for.
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
# Get all enabled notifications
|
393
|
+
ZohoHub::Notifications.all
|
243
394
|
```
|
244
395
|
|
396
|
+
### 8.3 Caveats
|
397
|
+
|
398
|
+
- Zoho does not notify you when records are merged.
|
399
|
+
- Since Zoho does not tell you what changed, you will have to request the record by yourself. Due to this you can miss changes, when they occur quickly after another. This is especially important for status changes, as you might miss state changes.
|
400
|
+
|
245
401
|
## Tips and suggestions
|
246
402
|
|
247
|
-
|
403
|
+
- Using a tool such as Postman or curl to issue HTTP requests and verify responses in isolation
|
248
404
|
can be a great sanity check during setup.
|
249
|
-
|
405
|
+
- Downloading ZohoHub code (as opposed to the gem) and running `bin/console` is a great way to
|
250
406
|
learn how the code works and test aspects of setup and Zoho's API in isolation.
|
251
|
-
|
407
|
+
- [The Zoho API Documentation](https://www.zoho.com/crm/help/developer/api/overview.html) is your
|
252
408
|
friend - especially the sample HTTP requests and responses in the various sections under "Rest
|
253
409
|
API" on the left.
|
254
|
-
|
255
|
-
the files in `/
|
256
|
-
|
410
|
+
- If you're manually implementing your record classes (rather than using the reflection mechanism),
|
411
|
+
the files in `/examples/models/` can help you get started.
|
412
|
+
- Requests can be issued to Zoho CRM's
|
413
|
+
[Sandbox](https://help.zoho.com/portal/kb/articles/using-sandbox)
|
257
414
|
by configuring `https://crmsandbox.zoho.com/crm` (or regional equivalent) as the `api_domain`.
|
258
415
|
|
416
|
+
## Examples
|
417
|
+
|
418
|
+
### Setup auth token and request CurrentUser
|
419
|
+
|
420
|
+
> This example assumes use of the dotenv gem and is written directly into
|
421
|
+
> ZohoHub's root directory (rather than using ZohoHub as a gem) for simplicity.
|
422
|
+
|
423
|
+
1. Edit `bin/console` to comment out refreshing the token and setting up the connection:
|
424
|
+
|
425
|
+
```ruby
|
426
|
+
# bin/console
|
427
|
+
|
428
|
+
...
|
429
|
+
# puts 'Refreshing token...'
|
430
|
+
# token_params = ZohoHub::Auth.refresh_token(ENV['ZOHO_REFRESH_TOKEN'])
|
431
|
+
# ZohoHub.setup_connection(token_params)
|
432
|
+
...
|
433
|
+
```
|
434
|
+
|
435
|
+
2. [Register your application](#1-register-your-application) to obtain a **client ID** and
|
436
|
+
**secret**. (Leave the [Zoho API Credentials page](https://accounts.zoho.com/developerconsole) open;
|
437
|
+
you'll need it in step 5.)
|
438
|
+
3. Determine your [Zoho Accounts URL](#11-zoho-accounts-url).
|
439
|
+
4. Add your registration and account URL information to a `.env` file:
|
440
|
+
|
441
|
+
```
|
442
|
+
# .env
|
443
|
+
|
444
|
+
ZOHO_CLIENT_ID=YOUR_CLIENT_ID
|
445
|
+
ZOHO_SECRET=YOUR_SECRET
|
446
|
+
ZOHO_API_DOMAIN=YOUR_ZOHO_ACCOUNTS_URL
|
447
|
+
```
|
448
|
+
|
449
|
+
5. On the [Zoho API Credentials page](https://accounts.zoho.com/developerconsole) from step 1, click
|
450
|
+
the three vertical dots and select `Self client`.
|
451
|
+
6. Paste this into the `Scope` field: `ZohoCRM.users.ALL`, choose an expiration time, and click
|
452
|
+
`View Code`; this is your **Grant token**.
|
453
|
+
7. Run the ZohoHub console from your terminal: `bin/console`
|
454
|
+
8. Issue a token request with the **grant token** (notice the quotes around the token value):
|
455
|
+
|
456
|
+
```ruby
|
457
|
+
ZohoHub::Auth.get_token('paste_your_grant_token_here')
|
458
|
+
```
|
459
|
+
|
460
|
+
This should return a response with an **access token**, e.g.:
|
461
|
+
|
462
|
+
```ruby
|
463
|
+
=> {:access_token=>"ACCESS_TOKEN_VALUE",
|
464
|
+
:expires_in_sec=>3600,
|
465
|
+
:api_domain=>"https://www.zohoapis.com",
|
466
|
+
:token_type=>"Bearer"
|
467
|
+
}
|
468
|
+
```
|
469
|
+
|
470
|
+
exit the console with `exit`.
|
471
|
+
|
472
|
+
9. Add the access token to your `.env` file:
|
473
|
+
|
474
|
+
```
|
475
|
+
# .env
|
476
|
+
|
477
|
+
ZOHO_CLIENT_ID=YOUR_CLIENT_ID
|
478
|
+
ZOHO_SECRET=YOUR_SECRET
|
479
|
+
ZOHO_API_DOMAIN=YOUR_ZOHO_ACCOUNTS_URL
|
480
|
+
ZOHO_ACCESS_TOKEN=YOUR_ACCESS_TOKEN
|
481
|
+
```
|
482
|
+
|
483
|
+
10. Edit `bin/console` to add a new `setup_connection` after the previously commented out one:
|
484
|
+
|
485
|
+
```ruby
|
486
|
+
# bin/console
|
487
|
+
|
488
|
+
...
|
489
|
+
# ZohoHub.setup_connection(token_params)
|
490
|
+
|
491
|
+
ZohoHub.setup_connection(access_token: ENV['ZOHO_ACCESS_TOKEN'])
|
492
|
+
...
|
493
|
+
```
|
494
|
+
|
495
|
+
11. Start the console again: `bin/console`.
|
496
|
+
|
497
|
+
12. Issue a request for the current user: `ZohoHub.connection.get 'users?type=CurrentUser'`
|
498
|
+
|
259
499
|
## Development
|
260
500
|
|
261
501
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run
|
@@ -270,4 +510,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/rikas/
|
|
270
510
|
|
271
511
|
## License
|
272
512
|
|
273
|
-
The gem is available as open source under the terms of the
|
513
|
+
The gem is available as open source under the terms of the
|
514
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/bin/console
CHANGED
data/lib/zoho_hub/auth.rb
CHANGED
data/lib/zoho_hub/base_record.rb
CHANGED
@@ -12,7 +12,7 @@ module ZohoHub
|
|
12
12
|
include WithAttributes
|
13
13
|
include WithValidations
|
14
14
|
|
15
|
-
# Default
|
15
|
+
# Default number of records when fetching all.
|
16
16
|
DEFAULT_RECORDS_PER_PAGE = 200
|
17
17
|
|
18
18
|
# Default page number when fetching all.
|
@@ -42,6 +42,21 @@ module ZohoHub
|
|
42
42
|
def where(params)
|
43
43
|
path = File.join(request_path, 'search')
|
44
44
|
|
45
|
+
if params.size == 1
|
46
|
+
params = case params.keys.first
|
47
|
+
when :criteria, :email, :phone, :word
|
48
|
+
# these attributes are directly handled by Zoho
|
49
|
+
# see https://www.zoho.com/crm/help/developer/api/search-records.html
|
50
|
+
params
|
51
|
+
else
|
52
|
+
key = attr_to_zoho_key(params.keys.first)
|
53
|
+
|
54
|
+
{
|
55
|
+
criteria: "#{key}:equals:#{params.values.first}"
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
45
60
|
body = get(path, params)
|
46
61
|
response = build_response(body)
|
47
62
|
|
@@ -59,6 +74,61 @@ module ZohoHub
|
|
59
74
|
new(params).save
|
60
75
|
end
|
61
76
|
|
77
|
+
def update(id, params)
|
78
|
+
new(id: id).update(params)
|
79
|
+
end
|
80
|
+
|
81
|
+
def blueprint_transition(id, transition_id, data = {})
|
82
|
+
new(id: id).blueprint_transition(transition_id, data)
|
83
|
+
end
|
84
|
+
|
85
|
+
def blueprint_transitions(id)
|
86
|
+
new(id: id).blueprint_transitions
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_note(id:, title: '', content: '')
|
90
|
+
path = File.join(request_path, id, 'Notes')
|
91
|
+
post(path, data: [{ Note_Title: title, Note_Content: content }])
|
92
|
+
end
|
93
|
+
|
94
|
+
def all_related(parent_module:, parent_id:)
|
95
|
+
body = get(File.join(parent_module.constantize.request_path, parent_id, request_path))
|
96
|
+
response = build_response(body)
|
97
|
+
|
98
|
+
data = response.nil? ? [] : response.data
|
99
|
+
|
100
|
+
data.map { |json| new(json) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_related(parent_module:, parent_id:, related_id:, data:)
|
104
|
+
path = File.join(
|
105
|
+
parent_module.constantize.request_path, parent_id, request_path, related_id
|
106
|
+
)
|
107
|
+
body = put(path, data: data)
|
108
|
+
build_response(body)
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_related(parent_module:, parent_id:, related_id:)
|
112
|
+
update_related(
|
113
|
+
parent_module: parent_module, parent_id: parent_id, related_id: related_id, data: [{}]
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def remove_related(parent_module:, parent_id:, related_id:)
|
118
|
+
body = delete(
|
119
|
+
File.join(parent_module.constantize.request_path, parent_id, request_path, related_id)
|
120
|
+
)
|
121
|
+
build_response(body)
|
122
|
+
end
|
123
|
+
|
124
|
+
def update_all(records)
|
125
|
+
zoho_params = records.map { |record| record.transform_keys { |key| attr_to_zoho_key(key) } }
|
126
|
+
|
127
|
+
body = put(File.join(request_path), data: zoho_params)
|
128
|
+
|
129
|
+
build_response(body)
|
130
|
+
end
|
131
|
+
|
62
132
|
def all(params = {})
|
63
133
|
params[:page] ||= DEFAULT_PAGE
|
64
134
|
params[:per_page] ||= DEFAULT_RECORDS_PER_PAGE
|
@@ -84,7 +154,12 @@ module ZohoHub
|
|
84
154
|
response = Response.new(body)
|
85
155
|
|
86
156
|
raise InvalidTokenError, response.msg if response.invalid_token?
|
157
|
+
raise InternalError, response.msg if response.internal_error?
|
87
158
|
raise RecordInvalid, response.msg if response.invalid_data?
|
159
|
+
raise InvalidModule, response.msg if response.invalid_module?
|
160
|
+
raise NoPermission, response.msg if response.no_permission?
|
161
|
+
raise MandatoryNotFound, response.msg if response.mandatory_not_found?
|
162
|
+
raise RecordInBlueprint, response.msg if response.record_in_blueprint?
|
88
163
|
|
89
164
|
response
|
90
165
|
end
|
@@ -93,8 +168,9 @@ module ZohoHub
|
|
93
168
|
def initialize(params = {})
|
94
169
|
attributes.each do |attr|
|
95
170
|
zoho_key = attr_to_zoho_key(attr)
|
171
|
+
value = params[zoho_key].nil? ? params[attr] : params[zoho_key]
|
96
172
|
|
97
|
-
send("#{attr}=",
|
173
|
+
send("#{attr}=", value)
|
98
174
|
end
|
99
175
|
end
|
100
176
|
|
@@ -110,6 +186,25 @@ module ZohoHub
|
|
110
186
|
response.data.first.dig(:details, :id)
|
111
187
|
end
|
112
188
|
|
189
|
+
def update(params)
|
190
|
+
zoho_params = params.transform_keys { |key| attr_to_zoho_key(key) }
|
191
|
+
body = put(File.join(self.class.request_path, id), data: [zoho_params])
|
192
|
+
|
193
|
+
build_response(body)
|
194
|
+
end
|
195
|
+
|
196
|
+
def blueprint_transition(transition_id, data = {})
|
197
|
+
body = put(File.join(self.class.request_path, id, 'actions/blueprint'),
|
198
|
+
blueprint: [{ transition_id: transition_id, data: data }])
|
199
|
+
|
200
|
+
build_response(body)
|
201
|
+
end
|
202
|
+
|
203
|
+
def blueprint_transitions
|
204
|
+
body = get(File.join(self.class.request_path, id, 'actions/blueprint'))
|
205
|
+
build_response(body)
|
206
|
+
end
|
207
|
+
|
113
208
|
def new_record?
|
114
209
|
!id
|
115
210
|
end
|
@@ -62,7 +62,7 @@ module ZohoHub
|
|
62
62
|
url = ZohoHub::Auth.auth_url
|
63
63
|
Launchy.open(url)
|
64
64
|
|
65
|
-
puts
|
65
|
+
puts 'Running callback server....'
|
66
66
|
ZohoHub::OauthCallbackServer.run!
|
67
67
|
end
|
68
68
|
|
@@ -79,13 +79,13 @@ module ZohoHub
|
|
79
79
|
def parse(argv, _env)
|
80
80
|
parser.parse!(argv)
|
81
81
|
true
|
82
|
-
rescue OptionParser::ParseError =>
|
83
|
-
error_output(
|
82
|
+
rescue OptionParser::ParseError => e
|
83
|
+
error_output(e)
|
84
84
|
end
|
85
85
|
|
86
86
|
def error_output(error)
|
87
|
-
|
88
|
-
|
87
|
+
warn "Error: #{error}"
|
88
|
+
warn "Try `#{parser.program_name} server --help' for more information"
|
89
89
|
|
90
90
|
false
|
91
91
|
end
|
@@ -107,13 +107,13 @@ module ZohoHub
|
|
107
107
|
def parse(argv, _env)
|
108
108
|
parser.parse!(argv)
|
109
109
|
true
|
110
|
-
rescue OptionParser::ParseError =>
|
111
|
-
error_output(
|
110
|
+
rescue OptionParser::ParseError => e
|
111
|
+
error_output(e)
|
112
112
|
end
|
113
113
|
|
114
114
|
def error_output(error)
|
115
|
-
|
116
|
-
|
115
|
+
warn "Error: #{error}"
|
116
|
+
warn "Try `#{parser.program_name} server --help' for more information"
|
117
117
|
|
118
118
|
false
|
119
119
|
end
|
data/lib/zoho_hub/connection.rb
CHANGED
@@ -9,6 +9,18 @@ require 'zoho_hub/response'
|
|
9
9
|
|
10
10
|
module ZohoHub
|
11
11
|
class Connection
|
12
|
+
class << self
|
13
|
+
def infer_api_domain
|
14
|
+
case ZohoHub.configuration.api_domain
|
15
|
+
when 'https://accounts.zoho.com' then 'https://www.zohoapis.com'
|
16
|
+
when 'https://accounts.zoho.com.cn' then 'https://www.zohoapis.com.cn'
|
17
|
+
when 'https://accounts.zoho.in' then 'https://www.zohoapis.in'
|
18
|
+
when 'https://accounts.zoho.eu' then 'https://www.zohoapis.eu'
|
19
|
+
else DEFAULT_DOMAIN
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
12
24
|
attr_accessor :debug, :access_token, :expires_in, :api_domain, :refresh_token
|
13
25
|
|
14
26
|
# This is a block to be run when the token is refreshed. This way you can do whatever you want
|
@@ -19,10 +31,10 @@ module ZohoHub
|
|
19
31
|
|
20
32
|
BASE_PATH = '/crm/v2/'
|
21
33
|
|
22
|
-
def initialize(access_token
|
34
|
+
def initialize(access_token: nil, api_domain: nil, expires_in: 3600, refresh_token: nil)
|
23
35
|
@access_token = access_token
|
24
36
|
@expires_in = expires_in
|
25
|
-
@api_domain = api_domain
|
37
|
+
@api_domain = api_domain || self.class.infer_api_domain
|
26
38
|
@refresh_token ||= refresh_token # do not overwrite if it's already set
|
27
39
|
end
|
28
40
|
|
@@ -76,7 +88,7 @@ module ZohoHub
|
|
76
88
|
response = Response.new(http_response.body)
|
77
89
|
|
78
90
|
# Try to refresh the token and try again
|
79
|
-
if response.invalid_token? && refresh_token?
|
91
|
+
if (response.invalid_token? || response.authentication_failure?) && refresh_token?
|
80
92
|
log "Refreshing outdated token... #{@access_token}"
|
81
93
|
params = ZohoHub::Auth.refresh_token(@refresh_token)
|
82
94
|
|
@@ -104,6 +116,7 @@ module ZohoHub
|
|
104
116
|
def adapter
|
105
117
|
Faraday.new(url: base_url) do |conn|
|
106
118
|
conn.headers = authorization_header if access_token?
|
119
|
+
conn.use FaradayMiddleware::EncodeJson
|
107
120
|
conn.use FaradayMiddleware::ParseJson
|
108
121
|
conn.response :json, parser_options: { symbolize_names: true }
|
109
122
|
conn.response :logger if ZohoHub.configuration.debug?
|
data/lib/zoho_hub/errors.rb
CHANGED
@@ -10,6 +10,21 @@ module ZohoHub
|
|
10
10
|
class InvalidTokenError < StandardError
|
11
11
|
end
|
12
12
|
|
13
|
+
class InternalError < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
class InvalidModule < StandardError
|
17
|
+
end
|
18
|
+
|
19
|
+
class NoPermission < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
class MandatoryNotFound < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
class RecordInBlueprint < StandardError
|
26
|
+
end
|
27
|
+
|
13
28
|
class ZohoAPIError < StandardError
|
14
29
|
end
|
15
30
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zoho_hub/base_record'
|
4
|
+
|
5
|
+
module ZohoHub
|
6
|
+
class BaseRecord
|
7
|
+
class << self
|
8
|
+
def related_attachments(parent_id:)
|
9
|
+
body = get(File.join(request_path, parent_id, 'Attachments'))
|
10
|
+
response = build_response(body)
|
11
|
+
|
12
|
+
data = response.nil? ? [] : response.data
|
13
|
+
|
14
|
+
data.map { |json| Attachment.new(json) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def download_attachment(parent_id:, attachment_id:)
|
18
|
+
attachment = related_attachments(parent_id: parent_id).find { |a| a.id == attachment_id }
|
19
|
+
uri = File.join(request_path, parent_id, 'Attachments', attachment_id)
|
20
|
+
res = ZohoHub.connection.adapter.get(uri)
|
21
|
+
attachment.content_type = res.headers['content-type']
|
22
|
+
extension = File.extname(attachment.file_name)
|
23
|
+
basename = File.basename(attachment.file_name, extension)
|
24
|
+
file = Tempfile.new([basename, extension])
|
25
|
+
file.binmode
|
26
|
+
file.write(res.body)
|
27
|
+
file.rewind
|
28
|
+
attachment.file = file
|
29
|
+
attachment
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Attachment < BaseRecord
|
35
|
+
attributes :id, :file_name, :created_by, :modified_by, :owner, :parent_id, :created_time,
|
36
|
+
:modified_time, :size
|
37
|
+
|
38
|
+
attribute_translation id: :id
|
39
|
+
attr_accessor :content_type, :file
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zoho_hub/response'
|
4
|
+
require 'zoho_hub/with_connection'
|
5
|
+
|
6
|
+
module ZohoHub
|
7
|
+
class Notifications
|
8
|
+
include WithConnection
|
9
|
+
|
10
|
+
# Default number of records when fetching all.
|
11
|
+
DEFAULT_RECORDS_PER_PAGE = 200
|
12
|
+
|
13
|
+
# Default page number when fetching all.
|
14
|
+
DEFAULT_PAGE = 1
|
15
|
+
|
16
|
+
# Minimum number of records to fetch when fetching all.
|
17
|
+
MIN_RECORDS = 2
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def request_path
|
21
|
+
@request_path = 'actions/watch'
|
22
|
+
end
|
23
|
+
|
24
|
+
def all(params = {})
|
25
|
+
params[:page] ||= DEFAULT_PAGE
|
26
|
+
params[:per_page] ||= DEFAULT_RECORDS_PER_PAGE
|
27
|
+
params[:per_page] = MIN_RECORDS if params[:per_page] < MIN_RECORDS
|
28
|
+
|
29
|
+
body = get(request_path, params)
|
30
|
+
return [] if body.nil?
|
31
|
+
|
32
|
+
build_response(body)
|
33
|
+
end
|
34
|
+
|
35
|
+
def enable(notify_url, channel_id, events, channel_expiry = nil, token = nil)
|
36
|
+
body = post(request_path, watch: [{ notify_url: notify_url,
|
37
|
+
channel_id: channel_id,
|
38
|
+
events: events,
|
39
|
+
channel_expiry: channel_expiry,
|
40
|
+
token: token }])
|
41
|
+
build_response(body)
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_response(body)
|
45
|
+
response = Response.new(body)
|
46
|
+
|
47
|
+
raise RecordInvalid, response.msg if response.invalid_data?
|
48
|
+
raise MandatoryNotFound, response.msg if response.mandatory_not_found?
|
49
|
+
|
50
|
+
response.data
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/zoho_hub/response.rb
CHANGED
@@ -7,22 +7,35 @@ module ZohoHub
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def invalid_data?
|
10
|
-
|
11
|
-
|
12
|
-
data[:code] == 'INVALID_DATA'
|
10
|
+
error_code?('INVALID_DATA')
|
13
11
|
end
|
14
12
|
|
15
|
-
# {:code=>"INVALID_TOKEN", :details=>{}, :message=>"invalid oauth token", :status=>"error"}
|
16
13
|
def invalid_token?
|
17
|
-
|
14
|
+
error_code?('INVALID_TOKEN')
|
15
|
+
end
|
18
16
|
|
19
|
-
|
17
|
+
def internal_error?
|
18
|
+
error_code?('INTERNAL_ERROR')
|
20
19
|
end
|
21
20
|
|
22
21
|
def authentication_failure?
|
23
|
-
|
22
|
+
error_code?('AUTHENTICATION_FAILURE')
|
23
|
+
end
|
24
|
+
|
25
|
+
def invalid_module?
|
26
|
+
error_code?('INVALID_MODULE')
|
27
|
+
end
|
28
|
+
|
29
|
+
def no_permission?
|
30
|
+
error_code?('NO_PERMISSION')
|
31
|
+
end
|
24
32
|
|
25
|
-
|
33
|
+
def mandatory_not_found?
|
34
|
+
error_code?('MANDATORY_NOT_FOUND')
|
35
|
+
end
|
36
|
+
|
37
|
+
def record_in_blueprint?
|
38
|
+
error_code?('RECORD_IN_BLUEPRINT')
|
26
39
|
end
|
27
40
|
|
28
41
|
def empty?
|
@@ -30,17 +43,18 @@ module ZohoHub
|
|
30
43
|
end
|
31
44
|
|
32
45
|
def data
|
33
|
-
data = @params[:data] if @params
|
46
|
+
data = @params[:data] if @params[:data]
|
34
47
|
data || @params
|
35
48
|
end
|
36
49
|
|
37
50
|
def msg
|
38
|
-
|
51
|
+
first_data = data.is_a?(Array) ? data.first : data
|
52
|
+
msg = first_data[:message]
|
39
53
|
|
40
|
-
if
|
41
|
-
expected =
|
42
|
-
field =
|
43
|
-
parent_api_name =
|
54
|
+
if first_data.dig(:details, :expected_data_type)
|
55
|
+
expected = first_data.dig(:details, :expected_data_type)
|
56
|
+
field = first_data.dig(:details, :api_name)
|
57
|
+
parent_api_name = first_data.dig(:details, :parent_api_name)
|
44
58
|
|
45
59
|
msg << ", expected #{expected} for '#{field}'"
|
46
60
|
msg << " in #{parent_api_name}" if parent_api_name
|
@@ -48,5 +62,18 @@ module ZohoHub
|
|
48
62
|
|
49
63
|
msg
|
50
64
|
end
|
65
|
+
|
66
|
+
# Error response examples:
|
67
|
+
# {"data":[{"code":"INVALID_DATA","details":{},"message":"the id given...","status":"error"}]}
|
68
|
+
# {:code=>"INVALID_TOKEN", :details=>{}, :message=>"invalid oauth token", :status=>"error"}
|
69
|
+
def error_code?(code)
|
70
|
+
if data.is_a?(Array)
|
71
|
+
return false if data.size > 1
|
72
|
+
|
73
|
+
return data.first[:code] == code
|
74
|
+
end
|
75
|
+
|
76
|
+
data[:code] == code
|
77
|
+
end
|
51
78
|
end
|
52
79
|
end
|
data/lib/zoho_hub/version.rb
CHANGED
@@ -43,6 +43,14 @@ module ZohoHub
|
|
43
43
|
@attribute_translation = translation
|
44
44
|
end
|
45
45
|
|
46
|
+
def attr_to_zoho_key(attr_name)
|
47
|
+
if attribute_translation.key?(attr_name.to_sym)
|
48
|
+
return attribute_translation[attr_name.to_sym]
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_name.to_s.split('_').map(&:capitalize).join('_').to_sym
|
52
|
+
end
|
53
|
+
|
46
54
|
def zoho_key_translation
|
47
55
|
@attribute_translation.to_a.map(&:rotate).to_h
|
48
56
|
end
|
@@ -53,14 +61,25 @@ module ZohoHub
|
|
53
61
|
self.class.attributes
|
54
62
|
end
|
55
63
|
|
56
|
-
private
|
64
|
+
# This method and the correponding private methods are inspired from Rails ActiveModel
|
65
|
+
# github.com/rails/rails/blob/master/activemodel/lib/active_model/attribute_assignment.rb
|
66
|
+
def assign_attributes(new_attributes)
|
67
|
+
unless new_attributes.is_a?(Hash)
|
68
|
+
raise ArgumentError, 'When assigning attributes, you must pass a hash as an argument'
|
69
|
+
end
|
57
70
|
|
58
|
-
|
59
|
-
translations = self.class.attribute_translation
|
71
|
+
return if new_attributes.empty?
|
60
72
|
|
61
|
-
|
73
|
+
attributes = new_attributes.transform_keys(&:to_s)
|
74
|
+
attributes.each do |k, v|
|
75
|
+
assign_attribute(k, v)
|
76
|
+
end
|
77
|
+
end
|
62
78
|
|
63
|
-
|
79
|
+
private
|
80
|
+
|
81
|
+
def attr_to_zoho_key(attr_name)
|
82
|
+
self.class.attr_to_zoho_key(attr_name)
|
64
83
|
end
|
65
84
|
|
66
85
|
def zoho_key_to_attr(zoho_key)
|
@@ -70,5 +89,13 @@ module ZohoHub
|
|
70
89
|
|
71
90
|
zoho_key.to_sym
|
72
91
|
end
|
92
|
+
|
93
|
+
def assign_attribute(key, value)
|
94
|
+
setter = :"#{key}="
|
95
|
+
|
96
|
+
return public_send(setter, value) if respond_to?(setter)
|
97
|
+
|
98
|
+
raise ArgumentError, "Unknown attribute #{key}"
|
99
|
+
end
|
73
100
|
end
|
74
101
|
end
|
@@ -13,15 +13,15 @@ module ZohoHub
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def post(path, params = {})
|
16
|
-
ZohoHub.connection.post(path, params
|
16
|
+
ZohoHub.connection.post(path, params)
|
17
17
|
end
|
18
18
|
|
19
19
|
def put(path, params = {})
|
20
|
-
ZohoHub.connection.put(path, params
|
20
|
+
ZohoHub.connection.put(path, params)
|
21
21
|
end
|
22
22
|
|
23
23
|
def delete(path, params = {})
|
24
|
-
ZohoHub.connection.delete(path, params
|
24
|
+
ZohoHub.connection.delete(path, params)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
data/lib/zoho_hub.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'backports/2.3.0/hash' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3.0')
|
4
|
+
require 'backports/2.5.0/hash' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0')
|
4
5
|
|
5
6
|
require 'zoho_hub/version'
|
6
7
|
require 'zoho_hub/auth'
|
@@ -8,6 +9,7 @@ require 'zoho_hub/configuration'
|
|
8
9
|
require 'zoho_hub/connection'
|
9
10
|
require 'zoho_hub/errors'
|
10
11
|
require 'zoho_hub/base_record'
|
12
|
+
require 'zoho_hub/modules/attachment'
|
11
13
|
require 'zoho_hub/settings/module'
|
12
14
|
|
13
15
|
require 'zoho_hub/reflection/module_builder'
|
@@ -39,7 +41,7 @@ module ZohoHub
|
|
39
41
|
|
40
42
|
connection_params = params.dup.slice(:access_token, :expires_in, :api_domain, :refresh_token)
|
41
43
|
|
42
|
-
@connection = Connection.new(connection_params)
|
44
|
+
@connection = Connection.new(**connection_params)
|
43
45
|
end
|
44
46
|
|
45
47
|
def connection
|
data/zoho_hub.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.required_ruby_version = '>= 2.1.0'
|
27
27
|
|
28
28
|
spec.add_dependency 'addressable'
|
29
|
-
spec.add_dependency 'backports'
|
29
|
+
spec.add_dependency 'backports'
|
30
30
|
spec.add_dependency 'faraday'
|
31
31
|
spec.add_dependency 'faraday_middleware'
|
32
32
|
spec.add_dependency 'launchy'
|
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency 'rainbow'
|
35
35
|
spec.add_dependency 'sinatra'
|
36
36
|
|
37
|
+
spec.add_development_dependency 'activesupport'
|
37
38
|
spec.add_development_dependency 'bundler'
|
38
39
|
spec.add_development_dependency 'dotenv'
|
39
40
|
spec.add_development_dependency 'pry-byebug'
|
@@ -42,4 +43,5 @@ Gem::Specification.new do |spec|
|
|
42
43
|
spec.add_development_dependency 'rubocop'
|
43
44
|
spec.add_development_dependency 'rubocop-rspec'
|
44
45
|
spec.add_development_dependency 'simplecov'
|
46
|
+
spec.add_development_dependency 'webmock'
|
45
47
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zoho_hub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ricardo Otero
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: backports
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: faraday
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,20 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: activesupport
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: bundler
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -220,6 +248,20 @@ dependencies:
|
|
220
248
|
- - ">="
|
221
249
|
- !ruby/object:Gem::Version
|
222
250
|
version: '0'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: webmock
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - ">="
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '0'
|
258
|
+
type: :development
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - ">="
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '0'
|
223
265
|
description: Simple gem to connect to Zoho CRM API V2
|
224
266
|
email:
|
225
267
|
- oterosantos@gmail.com
|
@@ -228,11 +270,11 @@ executables:
|
|
228
270
|
extensions: []
|
229
271
|
extra_rdoc_files: []
|
230
272
|
files:
|
273
|
+
- ".github/workflows/ci.yml"
|
231
274
|
- ".gitignore"
|
232
275
|
- ".rspec"
|
233
276
|
- ".rubocop.yml"
|
234
277
|
- ".ruby-version"
|
235
|
-
- ".travis.yml"
|
236
278
|
- Gemfile
|
237
279
|
- LICENSE.txt
|
238
280
|
- README.md
|
@@ -253,6 +295,8 @@ files:
|
|
253
295
|
- lib/zoho_hub/configuration.rb
|
254
296
|
- lib/zoho_hub/connection.rb
|
255
297
|
- lib/zoho_hub/errors.rb
|
298
|
+
- lib/zoho_hub/modules/attachment.rb
|
299
|
+
- lib/zoho_hub/notifications.rb
|
256
300
|
- lib/zoho_hub/oauth_callback_server.rb
|
257
301
|
- lib/zoho_hub/reflection/module_builder.rb
|
258
302
|
- lib/zoho_hub/response.rb
|
@@ -287,8 +331,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
287
331
|
- !ruby/object:Gem::Version
|
288
332
|
version: '0'
|
289
333
|
requirements: []
|
290
|
-
|
291
|
-
rubygems_version: 2.7.6
|
334
|
+
rubygems_version: 3.1.6
|
292
335
|
signing_key:
|
293
336
|
specification_version: 4
|
294
337
|
summary: Simple gem to connect to Zoho CRM API V2
|