procore 0.8.4 → 1.0.0

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
2
  SHA256:
3
- metadata.gz: 7654871c3d9bcf462a38bae7694e54444da0ce52f53276b41baa26ae6440dfb2
4
- data.tar.gz: 68ba0f550d88869e4eebd382585df01a232b77ba5f0f84361b16da036a52f242
3
+ metadata.gz: ab6a771f9ecbd227e500591c59381e08e70b1ad3cea2f82e767b4de2d88664b3
4
+ data.tar.gz: 1159fa494f5d38ce2d0a0fbcbdb465bf6203e8989a96acf3bcceab915ea9dbac
5
5
  SHA512:
6
- metadata.gz: b52ad7bec7c5c7aa6d6e658a490a72d6f481c6f0c046aa247d58c703698d02170bb3db241ba801362d1bf8969f55e262ccb665ba5bb11d758c6fbccaaa4da429
7
- data.tar.gz: bf2b57f7595b6bf9fabdf9b75246700246e6722176d69502b447b2f264c331a326cef613ca10b215bce93a2d0d19821632480b5391bbb2298d8577daa9f10ce9
6
+ metadata.gz: e1c9f942d9c8223df00bff8c67b366adc85be3224d2a6ef41ee4e345d40cba8cc0ca71fe0361ff094be1ba358d3ad7ead40fc99d36bba8e5edac0dfc19932c57
7
+ data.tar.gz: afb9275ad4ec9b1396f3f7bed77317eb39b8e6cacf5d82dc77b38e9a934491e439795fe7df432d27fdc337800b7343327f1cb9d7b70c072ed72244bcd41d6a0a
@@ -1,5 +1,9 @@
1
+ require:
2
+ - rubocop-rails
3
+ - rubocop-performance
4
+
1
5
  AllCops:
2
- TargetRubyVersion: 2.3
6
+ TargetRubyVersion: 2.4
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
@@ -3,9 +3,11 @@ rvm:
3
3
  - 2.3
4
4
  - 2.4
5
5
  - 2.5
6
+ - 2.6
6
7
 
7
8
  services:
8
9
  - redis-server
10
+ - memcached
9
11
 
10
12
  before_install:
11
13
  - gem update --system
@@ -1,4 +1,87 @@
1
- ## Unreleased
1
+ ## 1.0.0 (January 5, 2021)
2
+
3
+ * Adds support for API versioning
4
+
5
+ *Nate Baer*
6
+
7
+ ### Upgrading
8
+
9
+ As of v1.0.0, this gem now defaults to making requests against Procore's new
10
+ Rest v1.0 resources, instead of the now deprecated `/vapid` namespace. Example:
11
+
12
+ ```ruby
13
+ # Previously makes a request to
14
+ client.get("me")
15
+ => app.procore.com/vapid/me
16
+
17
+ # In 1.0.0
18
+ client.get("me")
19
+ => app.procore.com/rest/v1.0/me
20
+ ```
21
+
22
+ To keep the legacy behavior, set the new `default_version` configuration option.
23
+ Note, that Rest v1.0 is a superset of the Vapid Api - there are no breaking
24
+ changes. The Vapid API will be decommissioned in December 2021.
25
+
26
+ [Read more here](https://developers.procore.com/documentation/vapid-deprecation)
27
+
28
+ ```ruby
29
+ Procore.configure do |config|
30
+ ...
31
+ # Defaults to "v1.0"
32
+ config.default_version = "vapid"
33
+ ...
34
+ end
35
+ ```
36
+
37
+ All the request methods (`get`, `post`, `patch`, `put`, `delete`, `sync`) now
38
+ accept an optional version parameter to specify the version at request time.
39
+
40
+ ```ruby
41
+ client.get("me")
42
+ => https://app.procore.com/rest/v1.0/me
43
+
44
+ client.get("me", version: "v1.1")
45
+ => https://app.procore.com/rest/v1.1/me
46
+
47
+ client.get("me", version: "vapid")
48
+ => https://app.procore.com/vapid/me
49
+ ```
50
+
51
+ ## 0.8.8 (October 17, 2019)
52
+
53
+ * Expose #sync, a method that enables calling sync-actions
54
+
55
+ *Patrick Koperwas*
56
+
57
+ * Addition of contribution guidelines to README
58
+
59
+ *Megan O'Neill*
60
+
61
+ * Fix TravisCI failures
62
+
63
+ *Patrick Koperwas*
64
+
65
+ ## 0.8.7 (April 18, 2019)
66
+
67
+ * Add api_version to allow calls to procore rest endpoints
68
+
69
+ *Shane Means*
70
+
71
+ ## 0.8.6 (May 10, 2018)
72
+
73
+ * Dalli Store
74
+
75
+ *Patrick Koperwas*
76
+
77
+ * Fix Requestable paths to prevent double slash in URI
78
+
79
+ *Megan O'Neill*
80
+
81
+ ## 0.8.5 (May 9, 2018)
82
+ * Rescue Errno::ECONNREFUSED errors and RestClient::ServerBrokeConnection
83
+
84
+ *Casey Ochs*
2
85
 
3
86
  ## 0.8.4 (May 8, 2018)
4
87
 
data/README.md CHANGED
@@ -4,20 +4,24 @@
4
4
 
5
5
  #### Table of Contents
6
6
  - [Installation](#installation)
7
+ - [1.0.0 Release](#1.0.0-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)
16
18
  - [Redis Store](#redis-store)
19
+ - [Dalli Store](#dalli-store)
17
20
  - [ActiveRecord Store](#activerecord-store)
18
21
  - [File Store](#file-store)
19
22
  - [Memory Store](#memory-store)
20
23
  - [Full Example](#full-example)
24
+ - [Contributing](#contributing)
21
25
 
22
26
  ## Installation
23
27
 
@@ -27,6 +31,11 @@ Add this line to your application's Gemfile:
27
31
  gem "procore"
28
32
  ```
29
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
+
30
39
  ## Making Requests
31
40
 
32
41
  At the core of the gem is the `Client` class. Clients are initialized with a
@@ -38,19 +47,27 @@ Stores automatically manage tokens for you - refreshing, revoking and storage
38
47
  are abstracted away to make your code as simple as possible. There are several
39
48
  different [types of stores](#stores) available to you.
40
49
 
41
- The Client class exposes `#get`, `#post`, `#put`, `#patch` and `#delete` methods
42
- to you.
50
+ The Client class exposes `#get`, `#post`, `#put`, `#patch`, `#sync` and
51
+ `#delete` methods to you.
43
52
 
44
53
  ```ruby
45
- get(path, query: {})
46
- post(path, body: {}, options: {})
47
- put(path, body: {}, options: {})
48
- patch(path, body: {}, options: {})
49
- 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: {})
50
60
  ```
51
61
 
52
- All paths are relative - the gem will handle expanding `client.get("me")` to
53
- `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://app.procore.com/rest/v1.0/me` |
69
+ | `client.get("me", version: "v1.1")` | `https://app.procore.com/rest/v1.1/me` |
70
+ | `client.get("me", version: "vapid")` | `https://app.procore.com/vapid/me` |
54
71
 
55
72
  Example Usage:
56
73
 
@@ -68,6 +85,19 @@ companies = client.get("companies")
68
85
  companies.first[:name] #=> "Procore Company 1"
69
86
  ```
70
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
+
71
101
  ## Usage
72
102
 
73
103
  The first step is to place the user's token into the store. For this example,
@@ -258,6 +288,49 @@ puts first_page.pagination
258
288
  Notice that because `per_page` has been set to 250, there are only two pages of
259
289
  results (500 resources / 250 page size = 2 pages).
260
290
 
291
+ ## Sync Actions
292
+ The Sync action enables batch creation or updates to resources using a single
293
+ call. When using a Sync action, the resources to be created or updated can be
294
+ specified by supplying either an `id` or an `origin_id` in the request body.
295
+ Utilizing the `origin_id` attribute for batch operations is often preferable as
296
+ it allows you to easily link to external systems by maintaining your own list of
297
+ unique resource identifiers outside of Procore.
298
+
299
+ The caller provides an array of hashes, each hash containing the attributes for
300
+ a single resource. The attribute names in each hash match those used by the
301
+ Create and Update actions for the resource. Attributes for a maximum of 1000
302
+ resources within a collection may be passed with each call. The API will always
303
+ return an HTTP status of 200.
304
+
305
+ The response body contains two attributes - `entities` and `errors`. The
306
+ attributes for each successfully created or updated resource will appear in the
307
+ entities list. The attributes for each resource will match those returned by the
308
+ Show action. For each resource which could not be created or updated, the
309
+ attributes supplied by the caller are present in the errors list, along with an
310
+ additional errors attribute which provides reasons for the failure.
311
+
312
+ [Continue reading
313
+ here.](https://developers.procore.com/documentation/using-sync-actions)
314
+
315
+ Example Usage:
316
+
317
+ ```ruby
318
+ client.sync(
319
+ "projects/sync",
320
+ body: {
321
+ updates: [
322
+ { id: 1, name: "Update 1" },
323
+ { id: 2, name: "Update 2" },
324
+ { id: 3, name: "Update 3" },
325
+ ...
326
+ ...
327
+ { id: 5055, name: "Update 5055" },
328
+ ]
329
+ },
330
+ options: { batch_size: 500, company_id: 1 },
331
+ )
332
+ ```
333
+
261
334
  ## Configuration
262
335
 
263
336
  The Procore Gem exposes a configuration with several options.
@@ -272,6 +345,15 @@ Procore.configure do |config|
272
345
  # instead of production.
273
346
  config.host = ENV.fetch("PROCORE_BASE_API_PATH", "https://app.procore.com")
274
347
 
348
+ # When using #sync action, sets the default batch size to use for chunking
349
+ # up a request body. Example: if the size is set to 500, and 2,000 updates
350
+ # are desired, 4 requests will be made. Note, the maximum size is 1000.
351
+ config.default_batch_size = 500
352
+
353
+ # The default API version to use if none is specified in the request.
354
+ # Should be either "v1.0" (recommended) or "vapid" (legacy).
355
+ config.default_version = "v1.0"
356
+
275
357
  # Integer: Number of times to retry a failed API call. Reasons an API call
276
358
  # could potentially fail:
277
359
  # 1. Service is briefly down or unreachable
@@ -313,6 +395,14 @@ Options: `session`: Instance of a Rails session
313
395
 
314
396
  For applications that want to keep access tokens in the user's session.
315
397
 
398
+ :warning:
399
+ We strongly discourage using the session as a token store since the rails
400
+ session is often logged by default to external apps such as bugsnag etc. Be sure
401
+ you are not logging tokens. There is also the possibility that the rails session
402
+ is using a cookie store which, depending on application settings, could be
403
+ unencrypted. Tokens should not be stored client-side if it can be avoided.
404
+ :warning:
405
+
316
406
  ```ruby
317
407
  store = Procore::Auth::Stores::Session.new(session: session)
318
408
  ```
@@ -331,6 +421,20 @@ The key will usually be the id of the current user.
331
421
  store = Procore::Auth::Stores::Redis.new(redis: Redis.new, key: current_user.id)
332
422
  ```
333
423
 
424
+ ### Dalli Store
425
+
426
+ Options: `dalli`: Instance of Dalli
427
+ Options: `key`: Unique identifier to an access token
428
+
429
+ For applications which want to store access tokens in memcached using Dalli.
430
+ There's two required options, `dalli` which is an instance of a Dalli client,
431
+ and `key` which is a unique key which will be used to save / retrieve an access
432
+ token. The key will usually be the id of the current user.
433
+
434
+ ```ruby
435
+ store = Procore::Auth::Stores::Dalli.new(dalli: Dalli.new, key: current_user.id)
436
+ ```
437
+
334
438
  ### ActiveRecord Store
335
439
 
336
440
  Options: `object`: Instance of an ActiveRecord model.
@@ -410,6 +514,27 @@ class ProjectsController
410
514
  end
411
515
  end
412
516
  ```
517
+ ## Contributing
518
+
519
+ 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:
520
+
521
+ ```markdown
522
+ ## Unreleased
523
+
524
+ * Short sentence of what has changed
525
+
526
+ *Your Name*
527
+ ```
528
+
529
+ Please **do not** bump the gem version in your PR. This will be done in a follow up PR by the gem maintainers.
530
+
531
+ ### Tests
532
+
533
+ To run the specs run the following command:
534
+ ```bash
535
+ $ bundle exec rake test
536
+ ```
537
+
413
538
 
414
539
  ## License
415
540
 
@@ -7,6 +7,7 @@ require "json"
7
7
  require "procore/auth/access_token_credentials"
8
8
  require "procore/auth/client_credentials"
9
9
  require "procore/auth/stores/active_record"
10
+ require "procore/auth/stores/dalli"
10
11
  require "procore/auth/stores/file"
11
12
  require "procore/auth/stores/memory"
12
13
  require "procore/auth/stores/redis"
@@ -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",
@@ -0,0 +1,38 @@
1
+ module Procore
2
+ module Auth
3
+ module Stores
4
+ class Dalli
5
+ attr_reader :key, :dalli
6
+ def initialize(key:, dalli:)
7
+ @key = key
8
+ @dalli = dalli
9
+ end
10
+
11
+ def save(token)
12
+ dalli.set(dalli_key, token.to_json)
13
+ end
14
+
15
+ def fetch
16
+ return unless dalli.get(dalli_key)
17
+
18
+ token = JSON.parse(dalli.get(dalli_key))
19
+ Procore::Auth::Token.new(
20
+ access_token: token["access_token"],
21
+ refresh_token: token["refresh_token"],
22
+ expires_at: token["expires_at"],
23
+ )
24
+ end
25
+
26
+ def delete
27
+ dalli.delete(dalli_key)
28
+ end
29
+
30
+ private
31
+
32
+ def dalli_key
33
+ "procore-dalli-#{key}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -41,7 +41,7 @@ module Procore
41
41
  private
42
42
 
43
43
  def base_api_path
44
- "#{options[:host]}/vapid"
44
+ "#{options[:host]}"
45
45
  end
46
46
 
47
47
  # @raise [OAuthError] if the store does not have a token stored in it prior
@@ -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
@@ -9,10 +9,17 @@ module Procore
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
@@ -1,4 +1,5 @@
1
1
  require "rest-client"
2
+ require "procore/errors"
2
3
 
3
4
  module Procore
4
5
  # Module which defines HTTP verbs GET, POST, PUT, PATCH and DELETE. Is
@@ -10,7 +11,15 @@ module Procore
10
11
  # @example Using #post:
11
12
  # client.post("projects", name: "New Project")
12
13
  module Requestable
14
+ HTTP_EXCEPTIONS = [
15
+ Errno::ECONNREFUSED,
16
+ Errno::ECONNRESET,
17
+ Procore::OAuthError,
18
+ RestClient::Exceptions::Timeout,
19
+ RestClient::ServerBrokeConnection,
20
+ ].freeze
13
21
  # @param path [String] URL path
22
+ # @param version [String] API version
14
23
  # @param query [Hash] Query options to pass along with the request
15
24
  # @option options [Hash] :company_id
16
25
  #
@@ -18,10 +27,12 @@ module Procore
18
27
  # client.get("my_open_items", query: { per_page: 5, filter: {} })
19
28
  #
20
29
  # @return [Response]
21
- def get(path, query: {}, options: {})
30
+ def get(path, version: nil, query: {}, options: {})
31
+ full_path = full_path(path, version)
32
+
22
33
  Util.log_info(
23
34
  "API Request Initiated",
24
- path: "#{base_api_path}/#{path}",
35
+ path: full_path,
25
36
  method: "GET",
26
37
  query: query.to_s,
27
38
  )
@@ -29,7 +40,7 @@ module Procore
29
40
  with_response_handling do
30
41
  RestClient::Request.execute(
31
42
  method: :get,
32
- url: "#{base_api_path}/#{path}",
43
+ url: full_path,
33
44
  headers: headers(options).merge(params: query),
34
45
  timeout: Procore.configuration.timeout,
35
46
  )
@@ -37,8 +48,9 @@ module Procore
37
48
  end
38
49
 
39
50
  # @param path [String] URL path
51
+ # @param version [String] API version
40
52
  # @param body [Hash] Body parameters to send with the request
41
- # @param options [Hash} Extra request options
53
+ # @param options [Hash] Extra request options
42
54
  # @option options [String] :idempotency_token | :company_id
43
55
  #
44
56
  # @example Usage
@@ -49,10 +61,12 @@ module Procore
49
61
  # )
50
62
  #
51
63
  # @return [Response]
52
- def post(path, body: {}, options: {})
64
+ def post(path, version: nil, body: {}, options: {})
65
+ full_path = full_path(path, version)
66
+
53
67
  Util.log_info(
54
68
  "API Request Initiated",
55
- path: "#{base_api_path}/#{path}",
69
+ path: full_path,
56
70
  method: "POST",
57
71
  body: body.to_s,
58
72
  )
@@ -60,7 +74,7 @@ module Procore
60
74
  with_response_handling(request_body: body) do
61
75
  RestClient::Request.execute(
62
76
  method: :post,
63
- url: "#{base_api_path}/#{path}",
77
+ url: full_path,
64
78
  payload: payload(body),
65
79
  headers: headers(options),
66
80
  timeout: Procore.configuration.timeout,
@@ -69,18 +83,21 @@ module Procore
69
83
  end
70
84
 
71
85
  # @param path [String] URL path
86
+ # @param version [String] API version
72
87
  # @param body [Hash] Body parameters to send with the request
73
- # @param options [Hash} Extra request options
88
+ # @param options [Hash] Extra request options
74
89
  # @option options [String] :idempotency_token | :company_id
75
90
  #
76
91
  # @example Usage
77
92
  # client.put("dashboards/1/users", body: [1,2,3], options: { company_id: 1 })
78
93
  #
79
94
  # @return [Response]
80
- def put(path, body: {}, options: {})
95
+ def put(path, version: nil, body: {}, options: {})
96
+ full_path = full_path(path, version)
97
+
81
98
  Util.log_info(
82
99
  "API Request Initiated",
83
- path: "#{base_api_path}/#{path}",
100
+ path: full_path,
84
101
  method: "PUT",
85
102
  body: body.to_s,
86
103
  )
@@ -88,7 +105,7 @@ module Procore
88
105
  with_response_handling(request_body: body) do
89
106
  RestClient::Request.execute(
90
107
  method: :put,
91
- url: "#{base_api_path}/#{path}",
108
+ url: full_path,
92
109
  payload: payload(body),
93
110
  headers: headers(options),
94
111
  timeout: Procore.configuration.timeout,
@@ -97,8 +114,9 @@ module Procore
97
114
  end
98
115
 
99
116
  # @param path [String] URL path
117
+ # @param version [String] API version
100
118
  # @param body [Hash] Body parameters to send with the request
101
- # @param options [Hash} Extra request options
119
+ # @param options [Hash] Extra request options
102
120
  # @option options [String] :idempotency_token | :company_id
103
121
  #
104
122
  # @example Usage
@@ -109,10 +127,12 @@ module Procore
109
127
  # )
110
128
  #
111
129
  # @return [Response]
112
- def patch(path, body: {}, options: {})
130
+ def patch(path, version: nil, body: {}, options: {})
131
+ full_path = full_path(path, version)
132
+
113
133
  Util.log_info(
114
134
  "API Request Initiated",
115
- path: "#{base_api_path}/#{path}",
135
+ path: full_path,
116
136
  method: "PATCH",
117
137
  body: body.to_s,
118
138
  )
@@ -120,7 +140,7 @@ module Procore
120
140
  with_response_handling(request_body: body) do
121
141
  RestClient::Request.execute(
122
142
  method: :patch,
123
- url: "#{base_api_path}/#{path}",
143
+ url: full_path,
124
144
  payload: payload(body),
125
145
  headers: headers(options),
126
146
  timeout: Procore.configuration.timeout,
@@ -129,6 +149,73 @@ module Procore
129
149
  end
130
150
 
131
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
132
219
  # @param query [Hash] Query options to pass along with the request
133
220
  # @option options [String] :company_id
134
221
  #
@@ -136,10 +223,12 @@ module Procore
136
223
  # client.delete("users/1", query: {}, options: {})
137
224
  #
138
225
  # @return [Response]
139
- def delete(path, query: {}, options: {})
226
+ def delete(path, version: nil, query: {}, options: {})
227
+ full_path = full_path(path, version)
228
+
140
229
  Util.log_info(
141
230
  "API Request Initiated",
142
- path: "#{base_api_path}/#{path}",
231
+ path: full_path,
143
232
  method: "DELETE",
144
233
  headers: headers(options),
145
234
  query: query.to_s,
@@ -148,7 +237,7 @@ module Procore
148
237
  with_response_handling do
149
238
  RestClient::Request.execute(
150
239
  method: :delete,
151
- url: "#{base_api_path}/#{path}",
240
+ url: full_path,
152
241
  headers: headers.merge(params: query),
153
242
  timeout: Procore.configuration.timeout,
154
243
  )
@@ -163,7 +252,7 @@ module Procore
163
252
 
164
253
  begin
165
254
  result = yield
166
- rescue RestClient::Exceptions::Timeout, Errno::ECONNREFUSED, Procore::OAuthError => e
255
+ rescue *HTTP_EXCEPTIONS => e
167
256
  if retries <= Procore.configuration.max_retries
168
257
  retries += 1
169
258
  sleep 1.5**retries
@@ -277,5 +366,16 @@ module Procore
277
366
  def multipart?(body)
278
367
  RestClient::Payload::has_file?(body)
279
368
  end
369
+
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 ArgumentError.new "'#{version}' is an invalid Procore API version"
378
+ end
379
+ end
280
380
  end
281
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.4".freeze
2
+ VERSION = "1.0.0".freeze
3
3
  end
@@ -25,12 +25,15 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "actionpack"
26
26
  spec.add_development_dependency "activerecord"
27
27
  spec.add_development_dependency "bundler"
28
+ spec.add_development_dependency "dalli"
28
29
  spec.add_development_dependency "fakefs"
29
30
  spec.add_development_dependency "minitest"
30
31
  spec.add_development_dependency "pry"
31
32
  spec.add_development_dependency "rake"
32
33
  spec.add_development_dependency "redis"
33
34
  spec.add_development_dependency "rubocop"
35
+ spec.add_development_dependency "rubocop-performance"
36
+ spec.add_development_dependency "rubocop-rails"
34
37
  spec.add_development_dependency "sqlite3"
35
38
  spec.add_development_dependency "webmock"
36
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.4
4
+ version: 1.0.0
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-08 00:00:00.000000000 Z
11
+ date: 2021-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dalli
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: fakefs
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +150,34 @@ dependencies:
136
150
  - - ">="
137
151
  - !ruby/object:Gem::Version
138
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'
139
181
  - !ruby/object:Gem::Dependency
140
182
  name: sqlite3
141
183
  requirement: !ruby/object:Gem::Requirement
@@ -227,6 +269,7 @@ files:
227
269
  - lib/procore/auth/access_token_credentials.rb
228
270
  - lib/procore/auth/client_credentials.rb
229
271
  - lib/procore/auth/stores/active_record.rb
272
+ - lib/procore/auth/stores/dalli.rb
230
273
  - lib/procore/auth/stores/file.rb
231
274
  - lib/procore/auth/stores/memory.rb
232
275
  - lib/procore/auth/stores/redis.rb
@@ -260,8 +303,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
260
303
  - !ruby/object:Gem::Version
261
304
  version: '0'
262
305
  requirements: []
263
- rubyforge_project:
264
- rubygems_version: 2.7.6
306
+ rubygems_version: 3.1.4
265
307
  signing_key:
266
308
  specification_version: 4
267
309
  summary: Procore Ruby Gem