procore 0.8.7 → 1.1.2

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: 0e9072c893654842077d13100acc0afa9f78a4af28e902748494c58f467c8350
4
- data.tar.gz: b78ba6679d1dfbc2d96d0b395a9778278eac1979efa1fccb5bb3063d81b7430d
2
+ SHA1:
3
+ metadata.gz: 49f3e1daa62e1998646029758f63b21f973a9851
4
+ data.tar.gz: a2613a7ea2ddd4aa4ce6cbb7531a267af94dc195
5
5
  SHA512:
6
- metadata.gz: e36b749b1c34f1f87316c1530a4668bc4e52914fd5b60e29d4a5055755957dbcf46d87028b62baaedec45de57815ba93d4dd305f04dd19fb7972114fe350a8a7
7
- data.tar.gz: c49008dd2ccf0cc0d37fc1457462328a0916161a9bb54c1bd1c35c9d537ccd284b90d319e4c67cadce4b6afc530a37f80347af163877fc52978f69f39c07e4ae
6
+ metadata.gz: 92474ea3b4e4ac1c4658ed192e929e9ac6b203dead4b1b5e0e424fc545d723197a606e906e224f7809f2914ed7dca83243d38bb9c6ee783d1422c7c36f0ecb90
7
+ data.tar.gz: 707e8d18312f9c029fe86849eecc84dc50f4fd52191c4938c858a7ad0d420aac6798673575fb413a8c37b90def7f5c4bfdc5826a20f23525a2a253f52d586558
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,29 +1,25 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
-
3
+ - 2.3
4
+ - 2.4
5
+ - 2.5
6
+ - 2.6
8
7
  services:
9
- - redis-server
10
-
8
+ - redis-server
9
+ - memcached
11
10
  before_install:
12
- - gem update --system
13
- - gem install bundler
14
-
11
+ - gem update --system
12
+ - gem install bundler
15
13
  cache: bundler
16
-
17
14
  script: bundle exec rake
18
-
19
15
  deploy:
20
16
  provider: rubygems
21
17
  api_key:
22
- secure: GYH7nTi2D9ZwbCP+JYSgHceDUvZT9RQSP6yXFndwSyxdiSmB6wfq6EtoH4Wpznd6ALT6IxrtbR4aftTI9DO6HPq+PVO6NdxjkdlbdxxAqRQJBAwFwFATT8jCuuDjGPLf8PF0ouzcMvAIdIgHypwmLdevWdEMomWdRWlm1UFP/0C3RDjY1v04XDrRb+fEagPNnzMDCcO352nyFm06jnCq/ezjCRXBvNtHTZeqd7t9pbNw7gtYebZ3b9hfd4IIa68budeh+URygiPnNNWr6TCjO95IuHIeMDele3tir0KzqVBNsVAqEb7WpB26rf1JgneAoYZwVMFh+hk09cs68EbIELD4MX6/PzEUz2Ws20dIMjs1pcDtlw35ixoEpwC/vz2qVJyMRYXg8af5rpsZkKXRl7QP2LrX+xSEt4LiRnylj4PaRZWu1gvhC7fsCHXEM3mW3WlXFUmb9W4Jv8VooqOJtGhpD9Qs9pYtTOgJoEmYqttbuCaiYKaiPutBY4+t+t2ZmyCopv41nkjv/UCWXfo11ew1qK0rxDJVE2/JYTUDZ+zuHypTf2lHCWjyP65vGwBnmdixLnEMCUOkzKUmqFR4kAE6jRMEhtki4fTlFSIHJGM5oIXeRXDR43NIh7xWNvhl7lHkwWoRA0BfSITtvO8k0SefGnFwhNfOglQ/GKfyAaY=
23
- 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
24
20
  on:
25
21
  tags: true
26
22
  repo: procore/ruby-sdk
27
-
23
+ skip_cleanup: 'true'
28
24
  notifications:
29
25
  email: false
data/CHANGELOG.md CHANGED
@@ -1,4 +1,90 @@
1
- ## Unreleased
1
+ ## 1.1.2 (Jun 4, 2021)
2
+
3
+ * Add Procore-Sdk-Version header to all requests
4
+
5
+ PR #44 - https://github.com/procore/ruby-sdk/pull/44
6
+
7
+ *Benjamin Ross*
8
+
9
+ ## 1.1.1 (May 5, 2021)
10
+
11
+ * Change default host to 'api.procore.com'.
12
+
13
+ PR #42 - https://github.com/procore/ruby-sdk/pull/42
14
+
15
+ *Nate Baer*
16
+
17
+ ## 1.1.0 (March 11, 2021)
18
+
19
+ * Allow tokens to be revoked and manually refreshed.
20
+
21
+ PR #39 - https://github.com/procore/ruby-sdk/pull/39
22
+
23
+ *Nate Baer*
24
+
25
+ ## 1.0.0 (January 5, 2021)
26
+
27
+ * Adds support for API versioning
28
+
29
+ *Nate Baer*
30
+
31
+ ### Upgrading
32
+
33
+ As of v1.0.0, this gem now defaults to making requests against Procore's new
34
+ Rest v1.0 resources, instead of the now deprecated `/vapid` namespace. Example:
35
+
36
+ ```ruby
37
+ # Previously makes a request to
38
+ client.get("me")
39
+ => app.procore.com/vapid/me
40
+
41
+ # In 1.0.0
42
+ client.get("me")
43
+ => app.procore.com/rest/v1.0/me
44
+ ```
45
+
46
+ To keep the legacy behavior, set the new `default_version` configuration option.
47
+ Note, that Rest v1.0 is a superset of the Vapid Api - there are no breaking
48
+ changes. The Vapid API will be decommissioned in December 2021.
49
+
50
+ [Read more here](https://developers.procore.com/documentation/vapid-deprecation)
51
+
52
+ ```ruby
53
+ Procore.configure do |config|
54
+ ...
55
+ # Defaults to "v1.0"
56
+ config.default_version = "vapid"
57
+ ...
58
+ end
59
+ ```
60
+
61
+ All the request methods (`get`, `post`, `patch`, `put`, `delete`, `sync`) now
62
+ accept an optional version parameter to specify the version at request time.
63
+
64
+ ```ruby
65
+ client.get("me")
66
+ => https://app.procore.com/rest/v1.0/me
67
+
68
+ client.get("me", version: "v1.1")
69
+ => https://app.procore.com/rest/v1.1/me
70
+
71
+ client.get("me", version: "vapid")
72
+ => https://app.procore.com/vapid/me
73
+ ```
74
+
75
+ ## 0.8.8 (October 17, 2019)
76
+
77
+ * Expose #sync, a method that enables calling sync-actions
78
+
79
+ *Patrick Koperwas*
80
+
81
+ * Addition of contribution guidelines to README
82
+
83
+ *Megan O'Neill*
84
+
85
+ * Fix TravisCI failures
86
+
87
+ *Patrick Koperwas*
2
88
 
3
89
  ## 0.8.7 (April 18, 2019)
4
90
 
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,30 @@ 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` |
71
+
72
+ In addition to the settings above, you will need to set `company_id` in the request
73
+ options to work with [Multiple Procore Zones (MPZ)](https://developers.procore.com/documentation/tutorial-mpz).
55
74
 
56
75
  Example Usage:
57
76
 
@@ -67,6 +86,24 @@ client = Procore::Client.new(
67
86
  companies = client.get("companies")
68
87
 
69
88
  companies.first[:name] #=> "Procore Company 1"
89
+
90
+ # Get a company's projects (note the company_id value in options)
91
+ projects = client.get("projects", query: {company_id: <company_id>}, options: {company_id: <company_id>})
92
+
93
+ projects.first[:name] #=> "Project 1"
94
+ ```
95
+
96
+ To use Procore's older API Vapid by default, the default version can be set in
97
+ either the Gem's [configuration](https://github.com/procore/ruby-sdk#configuration)
98
+ or the client's `options` hash:
99
+
100
+ ```ruby
101
+ client = Procore::Client.new(
102
+ ...
103
+ options {
104
+ default_version: "vapid"
105
+ }
106
+ )
70
107
  ```
71
108
 
72
109
  ## Usage
@@ -112,6 +149,18 @@ client = Procore::Client.new(
112
149
  client.get("me")
113
150
  ```
114
151
 
152
+ Expired tokens will automatically be refreshed, but can also be refreshed manually:
153
+
154
+ ```ruby
155
+ client.refresh
156
+ ```
157
+
158
+ Tokens may also be manually revoked, forcing the client to refresh its token on the next request:
159
+
160
+ ```ruby
161
+ client.revoke
162
+ ```
163
+
115
164
  ## Error Handling
116
165
 
117
166
  The Procore Gem raises errors whenever a request returns a non `2xx` response.
@@ -259,6 +308,49 @@ puts first_page.pagination
259
308
  Notice that because `per_page` has been set to 250, there are only two pages of
260
309
  results (500 resources / 250 page size = 2 pages).
261
310
 
311
+ ## Sync Actions
312
+ The Sync action enables batch creation or updates to resources using a single
313
+ call. When using a Sync action, the resources to be created or updated can be
314
+ specified by supplying either an `id` or an `origin_id` in the request body.
315
+ Utilizing the `origin_id` attribute for batch operations is often preferable as
316
+ it allows you to easily link to external systems by maintaining your own list of
317
+ unique resource identifiers outside of Procore.
318
+
319
+ The caller provides an array of hashes, each hash containing the attributes for
320
+ a single resource. The attribute names in each hash match those used by the
321
+ Create and Update actions for the resource. Attributes for a maximum of 1000
322
+ resources within a collection may be passed with each call. The API will always
323
+ return an HTTP status of 200.
324
+
325
+ The response body contains two attributes - `entities` and `errors`. The
326
+ attributes for each successfully created or updated resource will appear in the
327
+ entities list. The attributes for each resource will match those returned by the
328
+ Show action. For each resource which could not be created or updated, the
329
+ attributes supplied by the caller are present in the errors list, along with an
330
+ additional errors attribute which provides reasons for the failure.
331
+
332
+ [Continue reading
333
+ here.](https://developers.procore.com/documentation/using-sync-actions)
334
+
335
+ Example Usage:
336
+
337
+ ```ruby
338
+ client.sync(
339
+ "projects/sync",
340
+ body: {
341
+ updates: [
342
+ { id: 1, name: "Update 1" },
343
+ { id: 2, name: "Update 2" },
344
+ { id: 3, name: "Update 3" },
345
+ ...
346
+ ...
347
+ { id: 5055, name: "Update 5055" },
348
+ ]
349
+ },
350
+ options: { batch_size: 500, company_id: 1 },
351
+ )
352
+ ```
353
+
262
354
  ## Configuration
263
355
 
264
356
  The Procore Gem exposes a configuration with several options.
@@ -271,7 +363,16 @@ Procore.configure do |config|
271
363
  # Base API host name. Alter this depending on your environment - in a
272
364
  # staging or test environment you may want to point this at a sandbox
273
365
  # instead of production.
274
- config.host = ENV.fetch("PROCORE_BASE_API_PATH", "https://app.procore.com")
366
+ config.host = ENV.fetch("PROCORE_BASE_API_PATH", "https://api.procore.com")
367
+
368
+ # When using #sync action, sets the default batch size to use for chunking
369
+ # up a request body. Example: if the size is set to 500, and 2,000 updates
370
+ # are desired, 4 requests will be made. Note, the maximum size is 1000.
371
+ config.default_batch_size = 500
372
+
373
+ # The default API version to use if none is specified in the request.
374
+ # Should be either "v1.0" (recommended) or "vapid" (legacy).
375
+ config.default_version = "v1.0"
275
376
 
276
377
  # Integer: Number of times to retry a failed API call. Reasons an API call
277
378
  # could potentially fail:
@@ -433,6 +534,27 @@ class ProjectsController
433
534
  end
434
535
  end
435
536
  ```
537
+ ## Contributing
538
+
539
+ 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:
540
+
541
+ ```markdown
542
+ ## Unreleased
543
+
544
+ * Short sentence of what has changed
545
+
546
+ *Your Name*
547
+ ```
548
+
549
+ Please **do not** bump the gem version in your PR. This will be done in a follow up PR by the gem maintainers.
550
+
551
+ ### Tests
552
+
553
+ To run the specs run the following command:
554
+ ```bash
555
+ $ bundle exec rake test
556
+ ```
557
+
436
558
 
437
559
  ## License
438
560
 
@@ -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
@@ -38,23 +38,56 @@ module Procore
38
38
  @store = store
39
39
  end
40
40
 
41
- private
41
+ # @raise [OAuthError] if a token cannot be refreshed.
42
+ def refresh
43
+ token = fetch_token
42
44
 
43
- def base_api_path
44
- "#{options[:host]}/#{api_version}"
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
45
62
  end
46
63
 
47
- def api_version
48
- options[:api_version] || 'vapid'
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
49
80
  end
50
81
 
51
- # @raise [OAuthError] if the store does not have a token stored in it prior
52
- # to making a request.
53
- # @raise [OAuthError] if a token cannot be refreshed.
54
- # @raise [OAuthError] if incorrect credentials have been supplied.
55
- def access_token
56
- token = store.fetch
82
+ private
83
+
84
+ def base_api_path
85
+ "#{options[:host]}"
86
+ end
57
87
 
88
+ # @raise [OAuthError] if the store does not have a token stored.
89
+ def fetch_token
90
+ token = store.fetch
58
91
  if token.nil? || token.invalid?
59
92
  raise Procore::MissingTokenError.new(
60
93
  "Unable to retreive an access token from the store. Double check " \
@@ -62,28 +95,15 @@ module Procore
62
95
  "before attempting to make API requests",
63
96
  )
64
97
  end
98
+ token
99
+ end
65
100
 
101
+ def access_token
102
+ token = fetch_token
66
103
  if token.expired?
67
104
  Util.log_info("Token Expired", store: store)
68
- begin
69
- token = @credentials.refresh(
70
- token: token.access_token,
71
- refresh: token.refresh_token,
72
- )
73
-
74
- Util.log_info("Token Refresh Successful", store: store)
75
- store.save(token)
76
- rescue RuntimeError
77
- Util.log_error("Token Refresh Failed", store: store)
78
- raise Procore::OAuthError.new(
79
- "Unable to refresh the access token. Perhaps the Procore API is " \
80
- "down or the your access token store is misconfigured. Either " \
81
- "way, you should clear the store and prompt the user to sign in " \
82
- "again.",
83
- )
84
- end
105
+ refresh
85
106
  end
86
-
87
107
  token.access_token
88
108
  end
89
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",
@@ -204,7 +275,6 @@ module Procore
204
275
  code: result.code,
205
276
  request: result.request,
206
277
  request_body: request_body,
207
- api_version: api_version
208
278
  )
209
279
 
210
280
  case result.code
@@ -273,6 +343,7 @@ module Procore
273
343
  "Accepts" => "application/json",
274
344
  "Authorization" => "Bearer #{access_token}",
275
345
  "Content-Type" => "application/json",
346
+ "Procore-Sdk-Version" => "ruby-#{Procore::VERSION}",
276
347
  "User-Agent" => Procore.configuration.user_agent,
277
348
  }.tap do |headers|
278
349
  if options[:idempotency_token]
@@ -297,8 +368,15 @@ module Procore
297
368
  RestClient::Payload::has_file?(body)
298
369
  end
299
370
 
300
- def full_path(path)
301
- File.join(base_api_path, path).to_s
371
+ def full_path(path, version)
372
+ version ||= options[:default_version]
373
+ if version == "vapid"
374
+ File.join(base_api_path, "vapid", path)
375
+ elsif /\Av\d+\.\d+\z/.match?(version)
376
+ File.join(base_api_path, "rest", version, path)
377
+ else
378
+ raise Procore::InvalidRequestError.new "#{version} is an invalid Procore API version"
379
+ end
302
380
  end
303
381
  end
304
382
  end
@@ -45,12 +45,11 @@ module Procore
45
45
  # @return [Integer] Status Code returned from Procore API.
46
46
  # @!attribute [r] pagination
47
47
  # @return [Hash<Symbol, String>] Pagination URLs
48
- attr_reader :headers, :code, :pagination, :request, :request_body, :api_version
48
+ attr_reader :headers, :code, :pagination, :request, :request_body
49
49
 
50
- def initialize(body:, headers:, code:, request:, request_body:, api_version: 'vapid')
50
+ def initialize(body:, headers:, code:, request:, request_body:)
51
51
  @code = code
52
52
  @headers = headers
53
- @api_version = api_version
54
53
  @pagination = parse_pagination
55
54
  @request = request
56
55
  @request_body = request_body
@@ -73,7 +72,7 @@ module Procore
73
72
 
74
73
  def parse_pagination
75
74
  headers[:link].to_s.split(", ").map(&:strip).reduce({}) do |links, link|
76
- url, name = link.match(/#{api_version}\/(.*?)>; rel="(\w+)"/).captures
75
+ url, name = link.match(/(?:vapid|rest\/.*?)\/(.*?)>; rel="(\w+)"/).captures
77
76
  links.merge!(name.to_sym => url)
78
77
  end
79
78
  end
@@ -1,3 +1,3 @@
1
1
  module Procore
2
- VERSION = "0.8.7".freeze
2
+ VERSION = "1.1.2".freeze
3
3
  end
data/procore-1.1.1.gem ADDED
Binary file
data/procore.gemspec CHANGED
@@ -32,7 +32,9 @@ 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 "sqlite3", "~> 1.3.6"
35
+ spec.add_development_dependency "rubocop-performance"
36
+ spec.add_development_dependency "rubocop-rails"
37
+ spec.add_development_dependency "sqlite3"
36
38
  spec.add_development_dependency "webmock"
37
39
 
38
40
  spec.add_dependency "activesupport", "> 2.4"
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.7
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Procore Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-18 00:00:00.000000000 Z
11
+ date: 2021-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -150,20 +150,48 @@ 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
156
184
  requirements:
157
- - - "~>"
185
+ - - ">="
158
186
  - !ruby/object:Gem::Version
159
- version: 1.3.6
187
+ version: '0'
160
188
  type: :development
161
189
  prerelease: false
162
190
  version_requirements: !ruby/object:Gem::Requirement
163
191
  requirements:
164
- - - "~>"
192
+ - - ">="
165
193
  - !ruby/object:Gem::Version
166
- version: 1.3.6
194
+ version: '0'
167
195
  - !ruby/object:Gem::Dependency
168
196
  name: webmock
169
197
  requirement: !ruby/object:Gem::Requirement
@@ -255,6 +283,7 @@ files:
255
283
  - lib/procore/response.rb
256
284
  - lib/procore/util.rb
257
285
  - lib/procore/version.rb
286
+ - procore-1.1.1.gem
258
287
  - procore.gemspec
259
288
  homepage: https://github.com/procore/ruby-sdk
260
289
  licenses:
@@ -275,7 +304,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
275
304
  - !ruby/object:Gem::Version
276
305
  version: '0'
277
306
  requirements: []
278
- rubygems_version: 3.0.3
307
+ rubyforge_project:
308
+ rubygems_version: 2.6.14
279
309
  signing_key:
280
310
  specification_version: 4
281
311
  summary: Procore Ruby Gem