procore 0.8.7 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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