ruby_hubspot_api 0.3.0 → 0.3.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
- SHA1:
3
- metadata.gz: 6052dfd71294e988924beeab1b89662673ceaf93
4
- data.tar.gz: 4b2e295931295d639e473a9b139df03ec7df1554
2
+ SHA256:
3
+ metadata.gz: 85164b2e72737294737730eea14e12955d3f143f5fadc6a586523b002b1d82fa
4
+ data.tar.gz: 4240f83a2b75521845a0db28a6f7eac72b12542cd0c8c0bd1504f4a5c36c3c00
5
5
  SHA512:
6
- metadata.gz: caee0d8350205b6bcf45bb4ed4b5421f3fdaacaff87acfcb52c4845858bbafb664f70acbca1a564a34a99a166fd1072a838702d4bf4044b076cfd0b77e87f6a7
7
- data.tar.gz: 28cbb5b1ea5ebc076d7177e8f4ee35f450bbe7fbb33224a31c55f0801fc566b705cf9b65faf95453b91154675139fb33cfce557ea74e06afd001a09f710190bf
6
+ metadata.gz: d74e992af58345457232d6617c6133e1874a85f98925606a954c86c0a766c198bd5e7d0643e5417d8a21ea195f1aba9d823147123c9cf8d631ac9514a4ed268c
7
+ data.tar.gz: 32be447b0f03a5d86c340f00d1639d61a839c040fbe96789bdaeb7869403fadaf04843167a497e99b6223b0cca4c7e00c62d04ac8554d6ec618db6861cc969f8
data/CHANGELOG.md CHANGED
@@ -1,139 +1,139 @@
1
1
 
2
2
  ## v0.3.0
3
3
 
4
- - Clarify usage
5
- - More usage clarification
6
- - Create ruby.yml
7
- - fix the github workflow
8
- - fix the github workflow properly
9
- - add plaatforms to Gemfile.lock
10
- - add ruby 2.5 and reduce log output
11
- - try calling rspec directly
12
- - Use ERB in VCR tests so as to be independent of the env vars
13
- - ignore ruby version file
14
- - test on ruby 3.0 too
15
- - determine if a Hubspot property is read_only (or by negation updatable)
16
- - adding codecov
17
- - Using earlier bundler
18
- - Adding lcov format
19
- - Add read-only properties to resource class
20
- - Add property check to the contact spec
21
- - Only apply lcov formatter if running on github
22
- - try to upload the coverage results to codacy too
23
- - Adding Codacy badge
24
- - Ensure we always apply the right log_level
25
- - Tidy up documentation of resource
26
- - Yep. Back ported to 2.4
27
- - adjust some rubocop settings
28
- - Tidy up somer doc comments
29
- - Add some handling of required properties
30
- - Update user model to force specific properties to be retrieved
31
- - Improve logic of resource matching
32
- - Adds :sparkle: attributes method to a resource
33
- - Tidy comments
34
- - Update the hierarchy to allow more flexibility
35
- - Fix find resources
36
- - Clear up processing results
37
- - Update batch spec
38
- - Bump the version
39
- - Adds validation for resource matcher
40
- - Dynamically add a method to batch to allow "resources" to be referred to as the resource_name
41
- - Ignore gem files
42
- - Drop cadacy for now
43
- - Update batch spec to check resource_matcher works
44
- - Ensure properties are passed as named argument
45
- - No cov for inspect
46
- - Allow ERB in json fixtures
47
- - Contact find_by_token spec
48
- - find_by_token method - uses v1 API
49
- - Tests the method missing setter for resource
50
- - Sanitize web mock output
51
- - Finish specs
52
- - check the env vars before sanitising
53
- - make erb explicitly determined by the file exension (.json.erb)
54
- - use safe navigation for extracting id
55
- - bump version
56
4
  - update gem version in lock file
5
+ - bump version
6
+ - use safe navigation for extracting id
7
+ - make erb explicitly determined by the file exension (.json.erb)
8
+ - check the env vars before sanitising
9
+ - Finish specs
10
+ - Sanitize web mock output
11
+ - Tests the method missing setter for resource
12
+ - find_by_token method - uses v1 API
13
+ - Contact find_by_token spec
14
+ - Allow ERB in json fixtures
15
+ - No cov for inspect
16
+ - Ensure properties are passed as named argument
17
+ - Update batch spec to check resource_matcher works
18
+ - Drop cadacy for now
19
+ - Ignore gem files
20
+ - Dynamically add a method to batch to allow "resources" to be referred to as the resource_name
21
+ - Adds validation for resource matcher
22
+ - Bump the version
23
+ - Update batch spec
24
+ - Clear up processing results
25
+ - Fix find resources
26
+ - Update the hierarchy to allow more flexibility
27
+ - Tidy comments
28
+ - Adds :sparkle: attributes method to a resource
29
+ - Improve logic of resource matching
30
+ - Update user model to force specific properties to be retrieved
31
+ - Add some handling of required properties
32
+ - Tidy up somer doc comments
33
+ - adjust some rubocop settings
34
+ - Yep. Back ported to 2.4
35
+ - Tidy up documentation of resource
36
+ - Ensure we always apply the right log_level
37
+ - Adding Codacy badge
38
+ - try to upload the coverage results to codacy too
39
+ - Only apply lcov formatter if running on github
40
+ - Add property check to the contact spec
41
+ - Add read-only properties to resource class
42
+ - Adding lcov format
43
+ - Using earlier bundler
44
+ - adding codecov
45
+ - determine if a Hubspot property is read_only (or by negation updatable)
46
+ - test on ruby 3.0 too
47
+ - ignore ruby version file
48
+ - Use ERB in VCR tests so as to be independent of the env vars
49
+ - try calling rspec directly
50
+ - add ruby 2.5 and reduce log output
51
+ - add plaatforms to Gemfile.lock
52
+ - fix the github workflow properly
53
+ - fix the github workflow
54
+ - Create ruby.yml
55
+ - More usage clarification
56
+ - Clarify usage
57
57
 
58
58
  ## v0.2.0
59
59
 
60
- - Get the development dependencies right!
61
- - Bump the version again
62
- - describe find_by method
63
- - update lock
64
- - batch :sparkle: upsert spec
65
- - Borrowing Object#blank? method cos it actually really helps...
66
- - batch implemntation
67
- - logger.debug the post body and response body
68
- - Adds a changes? Method on resource
69
- - Adds instance method resource_name on resource
70
- - Ensure keys are stringified
71
- - Add all end points to the batch spec
72
- - Adds create and archive methods to batches
73
- - Tidy up resource code
74
- - Cover the previously nocov'd code
75
- - Add api client logging spec
76
- - add configurable timeouts to requests
77
- - Move rate limit handling to the client
78
- - Simplify mocked responses in batch spec
79
- - Adds PagedBatch as pager for batch/read request
80
- - Update the Readme to add Batch operations
81
60
  - bump version
61
+ - Update the Readme to add Batch operations
62
+ - Adds PagedBatch as pager for batch/read request
63
+ - Simplify mocked responses in batch spec
64
+ - Move rate limit handling to the client
65
+ - add configurable timeouts to requests
66
+ - Add api client logging spec
67
+ - Cover the previously nocov'd code
68
+ - Tidy up resource code
69
+ - Adds create and archive methods to batches
70
+ - Add all end points to the batch spec
71
+ - Ensure keys are stringified
72
+ - Adds instance method resource_name on resource
73
+ - Adds a changes? Method on resource
74
+ - logger.debug the post body and response body
75
+ - batch implemntation
76
+ - Borrowing Object#blank? method cos it actually really helps...
77
+ - batch :sparkle: upsert spec
78
+ - update lock
79
+ - describe find_by method
80
+ - Bump the version again
81
+ - Get the development dependencies right!
82
82
 
83
83
  ## v0.1.2
84
84
 
85
- - Fix the Readme
86
- - Sure the search param is values where passing an array
87
- - update changelog and Gemfile.lock
88
85
  - bump version
86
+ - update changelog and Gemfile.lock
87
+ - Sure the search param is values where passing an array
88
+ - Fix the Readme
89
89
 
90
90
  ## v0.1.1
91
91
 
92
- - adds the version numbers to the gemspec
93
- - Fix dependencies
94
92
  - bump version
93
+ - Fix dependencies
94
+ - adds the version numbers to the gemspec
95
95
 
96
96
  ## v0.1.0
97
97
 
98
- - Setup the configuration block
99
- - Adds spec for config
100
- - Set the auth headers when access_token configured
101
- - Version spec
102
- - don't test for client id
103
- - Load api client and add exception handler
104
- - Initial bases class Resource for api crud
105
- - Contact class with spec
106
- - Cassettes for contact spec
107
- - company model and spec with cassettes
108
- - Adds PagedCollection for paged results (list / search)
109
- - Adds Hubspot configuration to tests
110
- - Update required files
111
- - Adds list method to return PagedCollection
112
- - Add interface for search
113
- - VCR configuration
114
- - Contact/search cassette
115
- - Sample ENV file for developers
116
- - console with configuration if env vars set
117
- - Readme file
118
- - MIT license
119
- - allow connections if vcr_record_mode is on
120
- - Fix rubocop config
121
- - Update Readme
122
- - Log api requests and add interface to set logging
123
- - Update exception handing logic and add more exception classes
124
- - Test all parts of the config code
125
- - Adds user model (aliased to Owner) and specs
126
- - Add a configured? method to Hubspot module
127
- - Adds a dummy Hubspot::Property class
128
- - Update Paged request handling based on method
129
- - Adds a find_by mechanism for resources
130
- - Use the Hubspot::Property class to return properties for a given resource object
131
- - Update the specs for 100% coverage
132
- - Update the sample .env file
133
- - Reorder and clarify Readme
134
- - When we use limit(1) we should only return the object not an array
135
- - Test that we only get the properties we ask for or the defaults
136
- - Flatten the properties array into a comma separated list
137
- - Improve the intialiser
138
98
  - Update the changeling and link in gem spec
99
+ - Improve the intialiser
100
+ - Flatten the properties array into a comma separated list
101
+ - Test that we only get the properties we ask for or the defaults
102
+ - When we use limit(1) we should only return the object not an array
103
+ - Reorder and clarify Readme
104
+ - Update the sample .env file
105
+ - Update the specs for 100% coverage
106
+ - Use the Hubspot::Property class to return properties for a given resource object
107
+ - Adds a find_by mechanism for resources
108
+ - Update Paged request handling based on method
109
+ - Adds a dummy Hubspot::Property class
110
+ - Add a configured? method to Hubspot module
111
+ - Adds user model (aliased to Owner) and specs
112
+ - Test all parts of the config code
113
+ - Update exception handing logic and add more exception classes
114
+ - Log api requests and add interface to set logging
115
+ - Update Readme
116
+ - Fix rubocop config
117
+ - allow connections if vcr_record_mode is on
118
+ - MIT license
119
+ - Readme file
120
+ - console with configuration if env vars set
121
+ - Sample ENV file for developers
122
+ - Contact/search cassette
123
+ - VCR configuration
124
+ - Add interface for search
125
+ - Adds list method to return PagedCollection
126
+ - Update required files
127
+ - Adds Hubspot configuration to tests
128
+ - Adds PagedCollection for paged results (list / search)
129
+ - company model and spec with cassettes
130
+ - Cassettes for contact spec
131
+ - Contact class with spec
132
+ - Initial bases class Resource for api crud
133
+ - Load api client and add exception handler
134
+ - don't test for client id
135
+ - Version spec
136
+ - Set the auth headers when access_token configured
137
+ - Adds spec for config
138
+ - Setup the configuration block
139
139
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_hubspot_api (0.3.0)
4
+ ruby_hubspot_api (0.3.2)
5
5
  httparty (>= 0.1, < 1.0)
6
6
 
7
7
  GEM
@@ -17,15 +17,17 @@ GEM
17
17
  crack (1.0.0)
18
18
  bigdecimal
19
19
  rexml
20
+ csv (3.3.0)
20
21
  diff-lcs (1.5.1)
21
22
  docile (1.3.5)
22
23
  dotenv (2.8.1)
23
24
  hashdiff (1.1.1)
24
- httparty (0.21.0)
25
+ httparty (0.22.0)
26
+ csv
25
27
  mini_mime (>= 1.0.0)
26
28
  multi_xml (>= 0.5.2)
27
29
  method_source (1.1.0)
28
- mini_mime (1.1.2)
30
+ mini_mime (1.1.5)
29
31
  multi_xml (0.6.0)
30
32
  pry (0.14.2)
31
33
  coderay (~> 1.1)
@@ -80,4 +82,4 @@ DEPENDENCIES
80
82
  webmock (>= 3.0)
81
83
 
82
84
  BUNDLED WITH
83
- 2.2.34
85
+ 2.3.27
data/README.md CHANGED
@@ -120,7 +120,7 @@ puts "Contact: #{contact.firstname} #{contact.lastname}"
120
120
 
121
121
  ### Updating an Existing Object
122
122
 
123
- To update an existing object, you can either modify the object and call `save`, or use the `update` method specifying the properties you want to update. You can test whether or not the object will need to upload changes to the api by using the changes? method
123
+ To update an existing object, you can either modify the object and call `save`, or use the `update` method specifying the properties you want to update. You can test whether or not the object will need to upload changes to the api by using the changes? method. If you don't want to check for changes? you can use the method `save!` on the resource which will raise a Hubspot::NothingToDoError if there are no changes.
124
124
 
125
125
  Example using `save`:
126
126
 
@@ -136,6 +136,22 @@ contact.save # true
136
136
  contact.changes? # false
137
137
  ```
138
138
 
139
+ Example using `save!`:
140
+
141
+ ```ruby
142
+ contact = Hubspot::Contact.find(1)
143
+
144
+ MyDecorator.new(contact).apply_changes!
145
+
146
+ begin
147
+ contact.save!
148
+ rescue Hubspot::NothingToDoError = _
149
+ puts "Nothing changed, cancelled api call"
150
+ rescue Hubspot::RequestError => e
151
+ puts "API Error: #{e.message}"
152
+ end
153
+ ```
154
+
139
155
  Example using `update`:
140
156
 
141
157
  ```ruby
@@ -274,6 +290,41 @@ end
274
290
  - **lte**: Less than or equal to.
275
291
  - **IN**: Matches any of the values in an array.
276
292
 
293
+ #### Searching for empty values (NOT_HAS_PROPERTY)
294
+
295
+ Any empty value in your search will be matched using the correect filter in Hubspot
296
+
297
+ ```ruby
298
+ # Search for companies with no value for a given field
299
+ companies = Hubspot::Company.search(query: { client_category: nil }, properties: %w[name number_of_employees])
300
+ # Request body: {"filterGroups":[{"filters":[{"propertyName":"client_category","operator":"NOT_HAS_PROPERTY"}]}]
301
+
302
+ puts "Searching for uncategorised customers"
303
+ puts ""
304
+
305
+ companies.each do |company|
306
+ category = company_category_by_size(company.number_of_employees)
307
+ company.update(client_category: category)
308
+
309
+ puts " Found: #{company.name} (size #{company.number_of_employees}) - filed under #{category}"
310
+ end
311
+ ```
312
+
313
+ #### Getting the total number of records
314
+
315
+ Having created a search collection you can retrieve the total number of matching records from Hubspot by calling .total on the collection. This will make a single request to the api to retrieve the number of matching records
316
+
317
+ ```ruby
318
+ contacts_collection = Hubspot::Contact.search(query: { lead_status: 'cold' } )
319
+
320
+ if contacts_collection.total > 200
321
+ puts "Contacts will be processed overnight"
322
+ # schedule_processing_job
323
+ else
324
+ puts "Found #{contacts_collection.total} contacts. Processing...."
325
+ process_contacts(contacts_collection)
326
+ ```
327
+
277
328
  #### Specifying Properties in Search
278
329
 
279
330
  When performing a search, you can also specify which properties to return.
@@ -7,7 +7,9 @@ module Hubspot
7
7
  attr_accessor :response
8
8
 
9
9
  def initialize(response, message = nil)
10
- message = response.parsed_response['message'] if !message && response.respond_to?(:parsed_response)
10
+ if !message && response.respond_to?(:parsed_response)
11
+ message = response.parsed_response['message']
12
+ end
11
13
  message += "\n" if message
12
14
  me = super("#{message}Response body: #{response.body}",)
13
15
  me.response = response
@@ -19,6 +21,8 @@ module Hubspot
19
21
  class RateLimitExceededError < RequestError; end
20
22
  class NotConfiguredError < StandardError; end
21
23
  class ArgumentError < StandardError; end
24
+ class NothingToDoError < StandardError; end
25
+ class NotImplementedError < StandardError; end
22
26
 
23
27
  class << self
24
28
  def error_from_response(response)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require_relative './api_client'
4
5
  require_relative './exceptions'
5
6
 
@@ -10,17 +11,24 @@ module Hubspot
10
11
 
11
12
  MAX_LIMIT = 100 # HubSpot max items per page
12
13
 
14
+ # rubocop:disable Lint/MissingSuper
13
15
  def initialize(url:, params: {}, resource_class: nil, method: :get)
14
16
  @url = url
15
17
  @params = params
16
18
  @resource_class = resource_class
17
19
  @method = method.to_sym
18
20
  end
21
+ # rubocop:enable Lint/MissingSuper
22
+
23
+ def total
24
+ @total ||= determine_total
25
+ end
19
26
 
20
27
  def each_page
21
28
  offset = nil
22
29
  loop do
23
30
  response = fetch_page(offset)
31
+ @total = response['total'] if response['total']
24
32
  mapped_results = process_results(response)
25
33
  yield mapped_results unless mapped_results.empty?
26
34
  offset = response.dig('paging', 'next', 'after')
@@ -37,10 +45,12 @@ module Hubspot
37
45
  end
38
46
 
39
47
  # Override Enumerable's first method so as not to have to call each (via all)
48
+ # rubocop:disable Metrics/MethodLength
40
49
  def first(limit = 1)
41
50
  resources = []
42
51
  remaining = limit
43
52
 
53
+ original_limit = @params.delete(:limit)
44
54
  # Modify @params directly to set the limit
45
55
  @params[:limit] = [remaining, MAX_LIMIT].min
46
56
 
@@ -51,8 +61,10 @@ module Hubspot
51
61
  break if remaining <= 0
52
62
  end
53
63
 
64
+ @params[:limit] = original_limit
54
65
  limit == 1 ? resources.first : resources.first(limit)
55
66
  end
67
+ # rubocop:enable Metrics/MethodLength
56
68
 
57
69
  def each(&block)
58
70
  each_page do |page|
@@ -62,6 +74,33 @@ module Hubspot
62
74
 
63
75
  private
64
76
 
77
+ def determine_total
78
+ # We only get a response['total'] for the search endpoint
79
+ raise NotImplementedError, 'Total only available for search requests' unless search_request?
80
+
81
+ # if we don't already know the total we will make a single request and minimise the response
82
+ # size by asking for just one property and one record.
83
+
84
+ # store the current properties
85
+ original_properties = @params.delete(:properties)
86
+
87
+ # just request hs_object_id
88
+ @params[:properties] = ['hs_object_id']
89
+
90
+ # dummy request. @total will be set during the each_page evaluation
91
+ _first_page = first
92
+
93
+ # restore the original properties
94
+ @params[:properties] = original_properties
95
+
96
+ # return the now set total
97
+ @total
98
+ end
99
+
100
+ def search_request?
101
+ @url.include?('/search')
102
+ end
103
+
65
104
  def fetch_page(offset)
66
105
  params_with_offset = @params.dup
67
106
  params_with_offset.merge!(after: offset) if offset
@@ -263,6 +263,8 @@ module Hubspot
263
263
  #
264
264
  # If no suffix is provided, the default comparison is equality (`EQ`).
265
265
  #
266
+ # If no value is provided, or is empty the NOT_HAS_PROPERTY operator will be used
267
+ #
266
268
  # query - [String, Hash] The query for searching. This can be either:
267
269
  # - A String: for full-text search.
268
270
  # - A Hash: where each key represents a property and may have suffixes for the comparison
@@ -312,13 +314,8 @@ module Hubspot
312
314
  method: :post
313
315
  )
314
316
  end
315
-
316
317
  # rubocop:enable Metrics/MethodLength
317
318
 
318
- # The root of the api call. Mostly this will be "crm"
319
- # but you can override this to account for a different
320
- # object hierarchy
321
-
322
319
  # Define the resource name based on the class
323
320
  def resource_name
324
321
  name = self.name.split('::').last.downcase
@@ -337,6 +334,9 @@ module Hubspot
337
334
 
338
335
  private
339
336
 
337
+ # The root of the api call. Mostly this will be "crm"
338
+ # but you can override this to account for a different
339
+ # object hierarchy
340
340
  def api_root
341
341
  '/crm/v3/objects'
342
342
  end
@@ -356,9 +356,9 @@ module Hubspot
356
356
  filter_groups = [{ filters: [] }]
357
357
 
358
358
  filters.each do |key, value|
359
- filter = extract_property_and_operator(key)
359
+ filter = extract_property_and_operator(key, value)
360
360
  value_key = value.is_a?(Array) ? :values : :value
361
- filter[value_key] = value
361
+ filter[value_key] = value unless value.blank?
362
362
  filter_groups.first[:filters] << filter
363
363
  end
364
364
 
@@ -366,7 +366,9 @@ module Hubspot
366
366
  end
367
367
 
368
368
  # Extract property name and operator from the key
369
- def extract_property_and_operator(key)
369
+ def extract_property_and_operator(key, value)
370
+ return { propertyName: key.to_s, operator: 'NOT_HAS_PROPERTY' } if value.blank?
371
+
370
372
  OPERATOR_MAP.each do |suffix, hubspot_operator|
371
373
  if key.to_s.end_with?(suffix)
372
374
  return {
@@ -454,6 +456,12 @@ module Hubspot
454
456
  end
455
457
  end
456
458
 
459
+ def save!
460
+ raise NothingToDoError, 'Nothing to save' unless changes?
461
+
462
+ save
463
+ end
464
+
457
465
  # If the resource exists in Hubspot
458
466
  #
459
467
  # Returns Boolean
@@ -622,6 +630,8 @@ module Hubspot
622
630
  def create_new
623
631
  created_resource = self.class.create(@changes)
624
632
  @id = created_resource.id
633
+ @properties.merge!(@changes)
634
+ @changes = {}
625
635
  @id ? true : false
626
636
  end
627
637
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hubspot
4
- VERSION = '0.3.0'
4
+ VERSION = '0.3.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_hubspot_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Brook
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-15 00:00:00.000000000 Z
11
+ date: 2024-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -239,7 +239,7 @@ licenses:
239
239
  metadata:
240
240
  homepage_uri: https://github.com/sensadrome/ruby_hubspot_api
241
241
  changelog_uri: https://github.com/sensadrome/ruby_hubspot_api/blob/main/CHANGELOG.md
242
- post_install_message:
242
+ post_install_message:
243
243
  rdoc_options: []
244
244
  require_paths:
245
245
  - lib
@@ -254,9 +254,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
254
  - !ruby/object:Gem::Version
255
255
  version: '0'
256
256
  requirements: []
257
- rubyforge_project:
258
- rubygems_version: 2.6.14
259
- signing_key:
257
+ rubygems_version: 3.1.6
258
+ signing_key:
260
259
  specification_version: 4
261
260
  summary: ruby_hubspot_api is an ORM-like wrapper for the Hubspot API
262
261
  test_files: []