procore 0.8.6 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 6545e8ea432e0c10b0a3cd0e1257e96b57bf18f44c6d3ecfd3fa5805212290db
4
- data.tar.gz: c93910a023d455bcc2731f8f74a1ebe3631851c0b711cf4effff9f1fe84b4503
2
+ SHA1:
3
+ metadata.gz: d521ee6322fa923e85f77d6c836d7e5f9cb208df
4
+ data.tar.gz: 48eee44692be1ce01bd486ec57dc05699b1c9198
5
5
  SHA512:
6
- metadata.gz: 6376d8a7cc0a0a494594ce185e77630b885da38bbd14614219f069e3eaccd7f0f7cc202f007ef58129db49910bd518523e285830fbd3d6c379d920fdeceef6f9
7
- data.tar.gz: e258551108c844d80137919ce861233db506ba97d72598f93da3c2e7818425188857a42be0ef2b3d28dde99e95eb304f5d11b179b2f470c45dfde37574e7b742
6
+ metadata.gz: a94c2bd6877729616f381027130ee9216093a9fc5ffb9bc2af473dffa45ac1050d39a280fd92ad6e6978ff6b8d2551afe464002743a46d0b6b34be9a5161a86d
7
+ data.tar.gz: aaa762a18d06ea9e432111fadd89ae6eb389ff645ef2f9b488278f1710c3d368595054565e5d7b97ece4f9e73f6c6c3ff80d00c628658613c47f712b82261d42
data/.rubocop.yml CHANGED
@@ -1,5 +1,9 @@
1
+ require:
2
+ - rubocop-rails
3
+ - rubocop-performance
4
+
1
5
  AllCops:
2
- TargetRubyVersion: 2.3
6
+ TargetRubyVersion: 2.5
3
7
  Include:
4
8
  - '**/Rakefile'
5
9
  - '**/config.ru'
@@ -305,7 +309,7 @@ Style/PerlBackrefs:
305
309
  Naming/PredicateName:
306
310
  Description: 'Check the names of predicate methods.'
307
311
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark'
308
- NamePrefixBlacklist:
312
+ ForbiddenPrefixes:
309
313
  - is_
310
314
  Exclude:
311
315
  - spec/**/*
@@ -437,7 +441,7 @@ Style/RedundantBegin:
437
441
 
438
442
  # Layout
439
443
 
440
- Layout/AlignParameters:
444
+ Layout/ParameterAlignment:
441
445
  Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.'
442
446
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'
443
447
  Enabled: false
@@ -505,7 +509,7 @@ Lint/DeprecatedClassMethods:
505
509
  Description: 'Check for deprecated class method calls.'
506
510
  Enabled: false
507
511
 
508
- Lint/DuplicatedKey:
512
+ Lint/DuplicateHashKey:
509
513
  Description: 'Check for duplicate keys in hash literals.'
510
514
  Enabled: false
511
515
 
@@ -521,7 +525,7 @@ Lint/FormatParameterMismatch:
521
525
  Description: 'The number of parameters to format/sprint must match the fields.'
522
526
  Enabled: false
523
527
 
524
- Lint/HandleExceptions:
528
+ Lint/SuppressedException:
525
529
  Description: "Don't suppress exception."
526
530
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'
527
531
  Enabled: false
data/.travis.yml CHANGED
@@ -1,28 +1,25 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
-
3
+ - 2.3
4
+ - 2.4
5
+ - 2.5
6
+ - 2.6
7
7
  services:
8
- - redis-server
9
-
8
+ - redis-server
9
+ - memcached
10
10
  before_install:
11
- - gem update --system
12
- - gem install bundler
13
-
11
+ - gem update --system
12
+ - gem install bundler
14
13
  cache: bundler
15
-
16
14
  script: bundle exec rake
17
-
18
15
  deploy:
19
16
  provider: rubygems
20
17
  api_key:
21
- secure: GYH7nTi2D9ZwbCP+JYSgHceDUvZT9RQSP6yXFndwSyxdiSmB6wfq6EtoH4Wpznd6ALT6IxrtbR4aftTI9DO6HPq+PVO6NdxjkdlbdxxAqRQJBAwFwFATT8jCuuDjGPLf8PF0ouzcMvAIdIgHypwmLdevWdEMomWdRWlm1UFP/0C3RDjY1v04XDrRb+fEagPNnzMDCcO352nyFm06jnCq/ezjCRXBvNtHTZeqd7t9pbNw7gtYebZ3b9hfd4IIa68budeh+URygiPnNNWr6TCjO95IuHIeMDele3tir0KzqVBNsVAqEb7WpB26rf1JgneAoYZwVMFh+hk09cs68EbIELD4MX6/PzEUz2Ws20dIMjs1pcDtlw35ixoEpwC/vz2qVJyMRYXg8af5rpsZkKXRl7QP2LrX+xSEt4LiRnylj4PaRZWu1gvhC7fsCHXEM3mW3WlXFUmb9W4Jv8VooqOJtGhpD9Qs9pYtTOgJoEmYqttbuCaiYKaiPutBY4+t+t2ZmyCopv41nkjv/UCWXfo11ew1qK0rxDJVE2/JYTUDZ+zuHypTf2lHCWjyP65vGwBnmdixLnEMCUOkzKUmqFR4kAE6jRMEhtki4fTlFSIHJGM5oIXeRXDR43NIh7xWNvhl7lHkwWoRA0BfSITtvO8k0SefGnFwhNfOglQ/GKfyAaY=
22
- gem: procore
18
+ secure: LC+5qDeaqBtA8IIDt0yHNYsTA/KEs7IK/hESF2V2f4bhmMapifEVSjikoZVE1lTe0R2+CyO+5mWHloFeTLayTchpZhW3yfduei3l2tktZOH9UCsXIQZKveSZlCMQ+XOn65Gstbd/5krh9+axKZswgM5YgBhjXNRgjL+yOGh2kXN0exk5nBvIBFxSCQdwqGiBDcrtgJ4eWrlPgofXszMFb3ogNQcIuUcKmAOC0j/fNag4qQV0jnp3zHDuOjrsARPJn4w6RMqSLpFVUYpJw3tE94GDW5kILHr7taTPj12PeYNbYjW7kRSgMYbQlIWUKLeyMp+vw3kNqd3Ysat+M+Mrf3z2KRKR5w62IJnCq758uerCdUo5v/vsWHYFcJvZWLrvsPSk+xQWysAqRdkun7ZBCK/FLR75S6Vkm0DBnbqd/yYp6MP9Vbi77gIZpWMXANfdk1aWGVlPNpskYeWyqPjwZX1nYybJQIIpZfX21F2WPrNA9r/cj1KYjb8vKopMEdn9GP9xMeUXXu85K+ZVPqSD/AblrLHyD2x7cES/i4+XcQSaKUOSLxTUqemAiDRBahDCehPOOeLHnu/rUzgQr1gzQL0YcLNgHovmIdstljBys62JC4gy376cNawiSW4ZPMHWg6PewqQ6Ozg4Lkytx06bBwOos55dFgI4k0T4H0/6QiE=
19
+ gem: ruby-sdk
23
20
  on:
24
21
  tags: true
25
22
  repo: procore/ruby-sdk
26
-
23
+ skip_cleanup: 'true'
27
24
  notifications:
28
25
  email: false
data/CHANGELOG.md CHANGED
@@ -1,4 +1,88 @@
1
- ## Unreleased
1
+ ## 1.1.1 (May 5, 2021)
2
+
3
+ * Change default host to 'api.procore.com'.
4
+
5
+ PR #42 - https://github.com/procore/ruby-sdk/pull/42
6
+
7
+ *Nate Baer*
8
+
9
+ ## 1.1.0 (March 11, 2021)
10
+
11
+ * Allow tokens to be revoked and manually refreshed.
12
+
13
+ PR #39 - https://github.com/procore/ruby-sdk/pull/39
14
+
15
+ *Nate Baer*
16
+
17
+ ## 1.0.0 (January 5, 2021)
18
+
19
+ * Adds support for API versioning
20
+
21
+ *Nate Baer*
22
+
23
+ ### Upgrading
24
+
25
+ As of v1.0.0, this gem now defaults to making requests against Procore's new
26
+ Rest v1.0 resources, instead of the now deprecated `/vapid` namespace. Example:
27
+
28
+ ```ruby
29
+ # Previously makes a request to
30
+ client.get("me")
31
+ => app.procore.com/vapid/me
32
+
33
+ # In 1.0.0
34
+ client.get("me")
35
+ => app.procore.com/rest/v1.0/me
36
+ ```
37
+
38
+ To keep the legacy behavior, set the new `default_version` configuration option.
39
+ Note, that Rest v1.0 is a superset of the Vapid Api - there are no breaking
40
+ changes. The Vapid API will be decommissioned in December 2021.
41
+
42
+ [Read more here](https://developers.procore.com/documentation/vapid-deprecation)
43
+
44
+ ```ruby
45
+ Procore.configure do |config|
46
+ ...
47
+ # Defaults to "v1.0"
48
+ config.default_version = "vapid"
49
+ ...
50
+ end
51
+ ```
52
+
53
+ All the request methods (`get`, `post`, `patch`, `put`, `delete`, `sync`) now
54
+ accept an optional version parameter to specify the version at request time.
55
+
56
+ ```ruby
57
+ client.get("me")
58
+ => https://app.procore.com/rest/v1.0/me
59
+
60
+ client.get("me", version: "v1.1")
61
+ => https://app.procore.com/rest/v1.1/me
62
+
63
+ client.get("me", version: "vapid")
64
+ => https://app.procore.com/vapid/me
65
+ ```
66
+
67
+ ## 0.8.8 (October 17, 2019)
68
+
69
+ * Expose #sync, a method that enables calling sync-actions
70
+
71
+ *Patrick Koperwas*
72
+
73
+ * Addition of contribution guidelines to README
74
+
75
+ *Megan O'Neill*
76
+
77
+ * Fix TravisCI failures
78
+
79
+ *Patrick Koperwas*
80
+
81
+ ## 0.8.7 (April 18, 2019)
82
+
83
+ * Add api_version to allow calls to procore rest endpoints
84
+
85
+ *Shane Means*
2
86
 
3
87
  ## 0.8.6 (May 10, 2018)
4
88
 
@@ -7,7 +91,7 @@
7
91
  *Patrick Koperwas*
8
92
 
9
93
  * Fix Requestable paths to prevent double slash in URI
10
-
94
+
11
95
  *Megan O'Neill*
12
96
 
13
97
  ## 0.8.5 (May 9, 2018)
data/README.md CHANGED
@@ -4,12 +4,14 @@
4
4
 
5
5
  #### Table of Contents
6
6
  - [Installation](#installation)
7
+ - [1.0.0 Release](#100-release)
7
8
  - [Making Requests](#making-requests)
8
9
  - [Usage](#usage)
9
10
  - [Error Handling](#error-handling)
10
11
  - [Pagination](#pagination)
11
12
  - [Navigating Through Paginated Results](#navigating-through-paginated-results)
12
13
  - [Change Number of Results](#change-number-of-results)
14
+ - [Sync Actions](#sync-actions)
13
15
  - [Configuration](#configuration)
14
16
  - [Stores](#stores)
15
17
  - [Session Store](#session-store)
@@ -19,6 +21,7 @@
19
21
  - [File Store](#file-store)
20
22
  - [Memory Store](#memory-store)
21
23
  - [Full Example](#full-example)
24
+ - [Contributing](#contributing)
22
25
 
23
26
  ## Installation
24
27
 
@@ -28,6 +31,11 @@ Add this line to your application's Gemfile:
28
31
  gem "procore"
29
32
  ```
30
33
 
34
+ ## 1.0.0 Release
35
+
36
+ v1.0.0 was released on January 5, 2021, and adds support the new Rest v1.0 API.
37
+ See the CHANGELOG for upgrade instructions.
38
+
31
39
  ## Making Requests
32
40
 
33
41
  At the core of the gem is the `Client` class. Clients are initialized with a
@@ -39,19 +47,27 @@ Stores automatically manage tokens for you - refreshing, revoking and storage
39
47
  are abstracted away to make your code as simple as possible. There are several
40
48
  different [types of stores](#stores) available to you.
41
49
 
42
- The Client class exposes `#get`, `#post`, `#put`, `#patch` and `#delete` methods
43
- to you.
50
+ The Client class exposes `#get`, `#post`, `#put`, `#patch`, `#sync` and
51
+ `#delete` methods to you.
44
52
 
45
53
  ```ruby
46
- get(path, query: {})
47
- post(path, body: {}, options: {})
48
- put(path, body: {}, options: {})
49
- patch(path, body: {}, options: {})
50
- delete(path, query: {})
54
+ get(path, version: "", query: {})
55
+ post(path, version: "", body: {}, options: {})
56
+ put(path, version: "", body: {}, options: {})
57
+ patch(path, version: "", body: {}, options: {})
58
+ delete(path, version: "", query: {})
59
+ sync(path, version: "", body: {}, options: {})
51
60
  ```
52
61
 
53
- All paths are relative - the gem will handle expanding `client.get("me")` to
54
- `https://app.procore.com/vapid/me`.
62
+ All paths are relative, the gem will handle expanding them. An API version may
63
+ be specified in the `version:` argument, or the default version is used. The
64
+ default version is `v1.0` unless otherwise configured.
65
+
66
+ | Example | Requested URL |
67
+ | --- | --- |
68
+ | `client.get("me")` | `https://api.procore.com/rest/v1.0/me` |
69
+ | `client.get("me", version: "v1.1")` | `https://api.procore.com/rest/v1.1/me` |
70
+ | `client.get("me", version: "vapid")` | `https://api.procore.com/vapid/me` |
55
71
 
56
72
  Example Usage:
57
73
 
@@ -69,6 +85,19 @@ companies = client.get("companies")
69
85
  companies.first[:name] #=> "Procore Company 1"
70
86
  ```
71
87
 
88
+ To use Procore's older API Vapid by default, the default version can be set in
89
+ either the Gem's [configuration](https://github.com/procore/ruby-sdk#configuration)
90
+ or the client's `options` hash:
91
+
92
+ ```ruby
93
+ client = Procore::Client.new(
94
+ ...
95
+ options {
96
+ default_version: "vapid"
97
+ }
98
+ )
99
+ ```
100
+
72
101
  ## Usage
73
102
 
74
103
  The first step is to place the user's token into the store. For this example,
@@ -112,6 +141,18 @@ client = Procore::Client.new(
112
141
  client.get("me")
113
142
  ```
114
143
 
144
+ Expired tokens will automatically be refreshed, but can also be refreshed manually:
145
+
146
+ ```ruby
147
+ client.refresh
148
+ ```
149
+
150
+ Tokens may also be manually revoked, forcing the client to refresh its token on the next request:
151
+
152
+ ```ruby
153
+ client.revoke
154
+ ```
155
+
115
156
  ## Error Handling
116
157
 
117
158
  The Procore Gem raises errors whenever a request returns a non `2xx` response.
@@ -259,6 +300,49 @@ puts first_page.pagination
259
300
  Notice that because `per_page` has been set to 250, there are only two pages of
260
301
  results (500 resources / 250 page size = 2 pages).
261
302
 
303
+ ## Sync Actions
304
+ The Sync action enables batch creation or updates to resources using a single
305
+ call. When using a Sync action, the resources to be created or updated can be
306
+ specified by supplying either an `id` or an `origin_id` in the request body.
307
+ Utilizing the `origin_id` attribute for batch operations is often preferable as
308
+ it allows you to easily link to external systems by maintaining your own list of
309
+ unique resource identifiers outside of Procore.
310
+
311
+ The caller provides an array of hashes, each hash containing the attributes for
312
+ a single resource. The attribute names in each hash match those used by the
313
+ Create and Update actions for the resource. Attributes for a maximum of 1000
314
+ resources within a collection may be passed with each call. The API will always
315
+ return an HTTP status of 200.
316
+
317
+ The response body contains two attributes - `entities` and `errors`. The
318
+ attributes for each successfully created or updated resource will appear in the
319
+ entities list. The attributes for each resource will match those returned by the
320
+ Show action. For each resource which could not be created or updated, the
321
+ attributes supplied by the caller are present in the errors list, along with an
322
+ additional errors attribute which provides reasons for the failure.
323
+
324
+ [Continue reading
325
+ here.](https://developers.procore.com/documentation/using-sync-actions)
326
+
327
+ Example Usage:
328
+
329
+ ```ruby
330
+ client.sync(
331
+ "projects/sync",
332
+ body: {
333
+ updates: [
334
+ { id: 1, name: "Update 1" },
335
+ { id: 2, name: "Update 2" },
336
+ { id: 3, name: "Update 3" },
337
+ ...
338
+ ...
339
+ { id: 5055, name: "Update 5055" },
340
+ ]
341
+ },
342
+ options: { batch_size: 500, company_id: 1 },
343
+ )
344
+ ```
345
+
262
346
  ## Configuration
263
347
 
264
348
  The Procore Gem exposes a configuration with several options.
@@ -271,7 +355,16 @@ Procore.configure do |config|
271
355
  # Base API host name. Alter this depending on your environment - in a
272
356
  # staging or test environment you may want to point this at a sandbox
273
357
  # instead of production.
274
- config.host = ENV.fetch("PROCORE_BASE_API_PATH", "https://app.procore.com")
358
+ config.host = ENV.fetch("PROCORE_BASE_API_PATH", "https://api.procore.com")
359
+
360
+ # When using #sync action, sets the default batch size to use for chunking
361
+ # up a request body. Example: if the size is set to 500, and 2,000 updates
362
+ # are desired, 4 requests will be made. Note, the maximum size is 1000.
363
+ config.default_batch_size = 500
364
+
365
+ # The default API version to use if none is specified in the request.
366
+ # Should be either "v1.0" (recommended) or "vapid" (legacy).
367
+ config.default_version = "v1.0"
275
368
 
276
369
  # Integer: Number of times to retry a failed API call. Reasons an API call
277
370
  # could potentially fail:
@@ -314,6 +407,14 @@ Options: `session`: Instance of a Rails session
314
407
 
315
408
  For applications that want to keep access tokens in the user's session.
316
409
 
410
+ :warning:
411
+ We strongly discourage using the session as a token store since the rails
412
+ session is often logged by default to external apps such as bugsnag etc. Be sure
413
+ you are not logging tokens. There is also the possibility that the rails session
414
+ is using a cookie store which, depending on application settings, could be
415
+ unencrypted. Tokens should not be stored client-side if it can be avoided.
416
+ :warning:
417
+
317
418
  ```ruby
318
419
  store = Procore::Auth::Stores::Session.new(session: session)
319
420
  ```
@@ -425,6 +526,27 @@ class ProjectsController
425
526
  end
426
527
  end
427
528
  ```
529
+ ## Contributing
530
+
531
+ To contribute to the gem, please clone the repo and cut a new branch. In the PR update the changelog with a short explanation of what you've changed, and your name under the "Unreleased" section. Example changelog update:
532
+
533
+ ```markdown
534
+ ## Unreleased
535
+
536
+ * Short sentence of what has changed
537
+
538
+ *Your Name*
539
+ ```
540
+
541
+ Please **do not** bump the gem version in your PR. This will be done in a follow up PR by the gem maintainers.
542
+
543
+ ### Tests
544
+
545
+ To run the specs run the following command:
546
+ ```bash
547
+ $ bundle exec rake test
548
+ ```
549
+
428
550
 
429
551
  ## License
430
552
 
@@ -32,6 +32,17 @@ module Procore
32
32
  end
33
33
  end
34
34
 
35
+ def revoke(token:)
36
+ request = {
37
+ client_id: @client_id,
38
+ client_secret: @client_secret,
39
+ token: token.access_token,
40
+ }
41
+ client.request(:post, "/oauth/revoke", body: request)
42
+ rescue RestClient::ExceptionWithResponse
43
+ raise OAuthError.new(e.description, response: e.response)
44
+ end
45
+
35
46
  private
36
47
 
37
48
  def client
@@ -29,7 +29,7 @@ module Procore
29
29
  raise OAuthError.new(e.description, response: e.response)
30
30
  rescue Faraday::ConnectionFailed => e
31
31
  raise APIConnectionError.new("Connection Error: #{e.message}")
32
- rescue URI::BadURIError
32
+ rescue URI::BadURIError, URI::InvalidURIError
33
33
  raise OAuthError.new(
34
34
  "Host is not a valid URI. Check your host option to make sure it " \
35
35
  "is a properly formed url",
@@ -38,19 +38,56 @@ module Procore
38
38
  @store = store
39
39
  end
40
40
 
41
+ # @raise [OAuthError] if a token cannot be refreshed.
42
+ def refresh
43
+ token = fetch_token
44
+
45
+ begin
46
+ new_token = @credentials.refresh(
47
+ token: token.access_token,
48
+ refresh: token.refresh_token,
49
+ )
50
+
51
+ Util.log_info("Token Refresh Successful", store: store)
52
+ store.save(new_token)
53
+ rescue RuntimeError
54
+ Util.log_error("Token Refresh Failed", store: store)
55
+ raise Procore::OAuthError.new(
56
+ "Unable to refresh the access token. Perhaps the Procore API is " \
57
+ "down or your access token store is misconfigured. Either " \
58
+ "way, you should clear the store and prompt the user to sign in " \
59
+ "again.",
60
+ )
61
+ end
62
+ end
63
+
64
+ # @raise [OAuthError] if a token cannot be revoked.
65
+ def revoke
66
+ token = fetch_token
67
+
68
+ begin
69
+ @credentials.revoke(token: token)
70
+ Util.log_info("Token Revocation Successful", store: store)
71
+ rescue RuntimeError
72
+ Util.log_error("Token Revocation Failed", store: store)
73
+ raise Procore::OAuthError.new(
74
+ "Unable to revoke the access token. Perhaps the Procore API is " \
75
+ "down or your access token store is misconfigured. Either " \
76
+ "way, you should clear the store and prompt the user to sign in " \
77
+ "again.",
78
+ )
79
+ end
80
+ end
81
+
41
82
  private
42
83
 
43
84
  def base_api_path
44
- "#{options[:host]}/vapid"
85
+ "#{options[:host]}"
45
86
  end
46
87
 
47
- # @raise [OAuthError] if the store does not have a token stored in it prior
48
- # to making a request.
49
- # @raise [OAuthError] if a token cannot be refreshed.
50
- # @raise [OAuthError] if incorrect credentials have been supplied.
51
- def access_token
88
+ # @raise [OAuthError] if the store does not have a token stored.
89
+ def fetch_token
52
90
  token = store.fetch
53
-
54
91
  if token.nil? || token.invalid?
55
92
  raise Procore::MissingTokenError.new(
56
93
  "Unable to retreive an access token from the store. Double check " \
@@ -58,28 +95,15 @@ module Procore
58
95
  "before attempting to make API requests",
59
96
  )
60
97
  end
98
+ token
99
+ end
61
100
 
101
+ def access_token
102
+ token = fetch_token
62
103
  if token.expired?
63
104
  Util.log_info("Token Expired", store: store)
64
- begin
65
- token = @credentials.refresh(
66
- token: token.access_token,
67
- refresh: token.refresh_token,
68
- )
69
-
70
- Util.log_info("Token Refresh Successful", store: store)
71
- store.save(token)
72
- rescue RuntimeError
73
- Util.log_error("Token Refresh Failed", store: store)
74
- raise Procore::OAuthError.new(
75
- "Unable to refresh the access token. Perhaps the Procore API is " \
76
- "down or the your access token store is misconfigured. Either " \
77
- "way, you should clear the store and prompt the user to sign in " \
78
- "again.",
79
- )
80
- end
105
+ refresh
81
106
  end
82
-
83
107
  token.access_token
84
108
  end
85
109
  end
@@ -32,6 +32,15 @@ module Procore
32
32
  # @return [String]
33
33
  attr_accessor :host
34
34
 
35
+ # @!attribute [rw] default_version
36
+ # @note defaults to Defaults::DEFAULT_VERSION
37
+ #
38
+ # The default API version to use if none is specified in the request.
39
+ # Should be either "v1.0" (recommended) or "vapid" (legacy).
40
+ #
41
+ # @return [String]
42
+ attr_accessor :default_version
43
+
35
44
  # @!attribute [rw] logger
36
45
  # @note defaults to nil
37
46
  #
@@ -77,12 +86,26 @@ module Procore
77
86
  # @return [String]
78
87
  attr_accessor :user_agent
79
88
 
89
+ # @!attribute [rw] default_batch_size
90
+ # @note defaults to Defaults::BATCH_SIZE
91
+ #
92
+ # When using #sync action, sets the default batch size to use for chunking
93
+ # up a request body. Example, if the size is set to 500 and 2,000 updates
94
+ # are desired, 4 requests will be made.
95
+ #
96
+ # Note: The maximum size is 1,000
97
+ #
98
+ # @return [Integer]
99
+ attr_accessor :default_batch_size
100
+
80
101
  def initialize
102
+ @default_batch_size = Procore::Defaults::BATCH_SIZE
81
103
  @host = Procore::Defaults::API_ENDPOINT
82
104
  @logger = nil
83
105
  @max_retries = 1
84
106
  @timeout = 1.0
85
107
  @user_agent = Procore::Defaults::USER_AGENT
108
+ @default_version = Procore::Defaults::DEFAULT_VERSION
86
109
  end
87
110
  end
88
111
  end
@@ -4,15 +4,22 @@ module Procore
4
4
  # Specifies some sensible defaults for certain configurations + clients
5
5
  class Defaults
6
6
  # Default API endpoint
7
- API_ENDPOINT = "https://app.procore.com".freeze
7
+ API_ENDPOINT = "https://api.procore.com".freeze
8
8
 
9
9
  # Default User Agent header string
10
10
  USER_AGENT = "Procore Ruby Gem #{Procore::VERSION}".freeze
11
11
 
12
+ # Default size to use for batch requests
13
+ BATCH_SIZE = 500
14
+
15
+ # Default API version to use
16
+ DEFAULT_VERSION = "v1.0"
17
+
12
18
  def self.client_options
13
19
  {
14
20
  host: Procore.configuration.host,
15
21
  user_agent: Procore.configuration.user_agent,
22
+ default_version: Procore.configuration.default_version,
16
23
  }
17
24
  end
18
25
  end
@@ -19,6 +19,7 @@ module Procore
19
19
  RestClient::ServerBrokeConnection,
20
20
  ].freeze
21
21
  # @param path [String] URL path
22
+ # @param version [String] API version
22
23
  # @param query [Hash] Query options to pass along with the request
23
24
  # @option options [Hash] :company_id
24
25
  #
@@ -26,8 +27,8 @@ module Procore
26
27
  # client.get("my_open_items", query: { per_page: 5, filter: {} })
27
28
  #
28
29
  # @return [Response]
29
- def get(path, query: {}, options: {})
30
- full_path = full_path(path)
30
+ def get(path, version: nil, query: {}, options: {})
31
+ full_path = full_path(path, version)
31
32
 
32
33
  Util.log_info(
33
34
  "API Request Initiated",
@@ -47,8 +48,9 @@ module Procore
47
48
  end
48
49
 
49
50
  # @param path [String] URL path
51
+ # @param version [String] API version
50
52
  # @param body [Hash] Body parameters to send with the request
51
- # @param options [Hash} Extra request options
53
+ # @param options [Hash] Extra request options
52
54
  # @option options [String] :idempotency_token | :company_id
53
55
  #
54
56
  # @example Usage
@@ -59,8 +61,8 @@ module Procore
59
61
  # )
60
62
  #
61
63
  # @return [Response]
62
- def post(path, body: {}, options: {})
63
- full_path = full_path(path)
64
+ def post(path, version: nil, body: {}, options: {})
65
+ full_path = full_path(path, version)
64
66
 
65
67
  Util.log_info(
66
68
  "API Request Initiated",
@@ -81,16 +83,17 @@ module Procore
81
83
  end
82
84
 
83
85
  # @param path [String] URL path
86
+ # @param version [String] API version
84
87
  # @param body [Hash] Body parameters to send with the request
85
- # @param options [Hash} Extra request options
88
+ # @param options [Hash] Extra request options
86
89
  # @option options [String] :idempotency_token | :company_id
87
90
  #
88
91
  # @example Usage
89
92
  # client.put("dashboards/1/users", body: [1,2,3], options: { company_id: 1 })
90
93
  #
91
94
  # @return [Response]
92
- def put(path, body: {}, options: {})
93
- full_path = full_path(path)
95
+ def put(path, version: nil, body: {}, options: {})
96
+ full_path = full_path(path, version)
94
97
 
95
98
  Util.log_info(
96
99
  "API Request Initiated",
@@ -111,8 +114,9 @@ module Procore
111
114
  end
112
115
 
113
116
  # @param path [String] URL path
117
+ # @param version [String] API version
114
118
  # @param body [Hash] Body parameters to send with the request
115
- # @param options [Hash} Extra request options
119
+ # @param options [Hash] Extra request options
116
120
  # @option options [String] :idempotency_token | :company_id
117
121
  #
118
122
  # @example Usage
@@ -123,8 +127,8 @@ module Procore
123
127
  # )
124
128
  #
125
129
  # @return [Response]
126
- def patch(path, body: {}, options: {})
127
- full_path = full_path(path)
130
+ def patch(path, version: nil, body: {}, options: {})
131
+ full_path = full_path(path, version)
128
132
 
129
133
  Util.log_info(
130
134
  "API Request Initiated",
@@ -145,6 +149,73 @@ module Procore
145
149
  end
146
150
 
147
151
  # @param path [String] URL path
152
+ # @param version [String] API version
153
+ # @param body [Hash] Body parameters to send with the request
154
+ # @param options [Hash] Extra request options
155
+ # @option options [String | Integer] :company_id | :batch_size
156
+ #
157
+ # @example Usage
158
+ # client.sync(
159
+ # "projects/sync",
160
+ # body: {
161
+ # updates: [
162
+ # { id: 1, name: "Update 1" },
163
+ # { id: 2, name: "Update 2" },
164
+ # { id: 3, name: "Update 3" },
165
+ # ...
166
+ # ...
167
+ # { id: 5055, name: "Update 5055" },
168
+ # ]
169
+ # },
170
+ # options: { batch_size: 500, company_id: 1 },
171
+ # )
172
+ #
173
+ # @return [Response]
174
+ def sync(path, version: nil, body: {}, options: {})
175
+ full_path = full_path(path, version)
176
+
177
+ batch_size = options[:batch_size] ||
178
+ Procore.configuration.default_batch_size
179
+
180
+ if batch_size > 1000
181
+ batch_size = 1000
182
+ end
183
+
184
+ Util.log_info(
185
+ "API Request Initiated",
186
+ path: full_path,
187
+ method: "SYNC",
188
+ batch_size: batch_size,
189
+ )
190
+
191
+ groups = body[:updates].each_slice(batch_size).to_a
192
+
193
+ responses = groups.map do |group|
194
+ batched_body = body.merge(updates: group)
195
+ with_response_handling(request_body: batched_body) do
196
+ RestClient::Request.execute(
197
+ method: :patch,
198
+ url: full_path,
199
+ payload: payload(batched_body),
200
+ headers: headers(options),
201
+ timeout: Procore.configuration.timeout,
202
+ )
203
+ end
204
+ end
205
+
206
+ Procore::Response.new(
207
+ body: responses.reduce({}) do |combined, response|
208
+ combined.deep_merge(response.body) { |_, v1, v2| v1 + v2 }
209
+ end.to_json,
210
+ headers: responses.map(&:headers).inject({}, &:deep_merge),
211
+ code: 200,
212
+ request: responses.last&.request,
213
+ request_body: body,
214
+ )
215
+ end
216
+
217
+ # @param path [String] URL path
218
+ # @param version [String] API version
148
219
  # @param query [Hash] Query options to pass along with the request
149
220
  # @option options [String] :company_id
150
221
  #
@@ -152,8 +223,8 @@ module Procore
152
223
  # client.delete("users/1", query: {}, options: {})
153
224
  #
154
225
  # @return [Response]
155
- def delete(path, query: {}, options: {})
156
- full_path = full_path(path)
226
+ def delete(path, version: nil, query: {}, options: {})
227
+ full_path = full_path(path, version)
157
228
 
158
229
  Util.log_info(
159
230
  "API Request Initiated",
@@ -296,8 +367,15 @@ module Procore
296
367
  RestClient::Payload::has_file?(body)
297
368
  end
298
369
 
299
- def full_path(path)
300
- File.join(base_api_path, path).to_s
370
+ def full_path(path, version)
371
+ version ||= options[:default_version]
372
+ if version == "vapid"
373
+ File.join(base_api_path, "vapid", path)
374
+ elsif /\Av\d+\.\d+\z/.match?(version)
375
+ File.join(base_api_path, "rest", version, path)
376
+ else
377
+ raise Procore::InvalidRequestError.new "#{version} is an invalid Procore API version"
378
+ end
301
379
  end
302
380
  end
303
381
  end
@@ -72,7 +72,7 @@ module Procore
72
72
 
73
73
  def parse_pagination
74
74
  headers[:link].to_s.split(", ").map(&:strip).reduce({}) do |links, link|
75
- url, name = link.match(/vapid\/(.*?)>; rel="(\w+)"/).captures
75
+ url, name = link.match(/(?:vapid|rest\/.*?)\/(.*?)>; rel="(\w+)"/).captures
76
76
  links.merge!(name.to_sym => url)
77
77
  end
78
78
  end
@@ -1,3 +1,3 @@
1
1
  module Procore
2
- VERSION = "0.8.6".freeze
2
+ VERSION = "1.1.1".freeze
3
3
  end
data/procore.gemspec CHANGED
@@ -32,6 +32,8 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency "rake"
33
33
  spec.add_development_dependency "redis"
34
34
  spec.add_development_dependency "rubocop"
35
+ spec.add_development_dependency "rubocop-performance"
36
+ spec.add_development_dependency "rubocop-rails"
35
37
  spec.add_development_dependency "sqlite3"
36
38
  spec.add_development_dependency "webmock"
37
39
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: procore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Procore Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-10 00:00:00.000000000 Z
11
+ date: 2021-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -150,6 +150,34 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop-performance
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop-rails
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
153
181
  - !ruby/object:Gem::Dependency
154
182
  name: sqlite3
155
183
  requirement: !ruby/object:Gem::Requirement
@@ -276,7 +304,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
276
304
  version: '0'
277
305
  requirements: []
278
306
  rubyforge_project:
279
- rubygems_version: 2.7.6
307
+ rubygems_version: 2.6.14
280
308
  signing_key:
281
309
  specification_version: 4
282
310
  summary: Procore Ruby Gem