ruby_hubspot_api 0.3.1 → 0.3.3
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 +4 -4
- data/.env.sample +14 -22
- data/.rspec +1 -0
- data/Gemfile.lock +4 -6
- data/README.md +56 -15
- data/lib/hubspot/exceptions.rb +4 -1
- data/lib/hubspot/form.rb +6 -2
- data/lib/hubspot/paged_collection.rb +79 -3
- data/lib/hubspot/resource.rb +112 -63
- data/lib/hubspot/resource_filter.rb +49 -0
- data/lib/hubspot/version.rb +1 -1
- data/lib/hubspot.rb +9 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a2e9bcd6887d8ca6e6aadd20459732e94e76114
|
4
|
+
data.tar.gz: '02728d9515a0ef2b5d1eecd6b7b703c7ba9cddb1'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 054defa6af013acea911e6f671b4c4d0fb200c25bba22c2969f52cadefa7c5beb2d776989936fff62d573f173acdd85514d3ae1dc0a95d1d35958b5f7fab4f0b
|
7
|
+
data.tar.gz: 959a83f088e542877d37ed660b20a59c062c314c6ae9343bccbaf8eed6fd63e350566ce8591b982db6649b835c8c87257ca3dc726faeba7caf3da10a962246fc
|
data/.env.sample
CHANGED
@@ -8,33 +8,25 @@ export HUBSPOT_ACCESS_TOKEN=your_private_app_access_token
|
|
8
8
|
export HUBSPOT_CLIENT_SECRET=your_client_secret
|
9
9
|
export HUBSPOT_PORTAL_ID=portal_id
|
10
10
|
|
11
|
+
export HUBSPOT_NO_AUTH_ACCESS_TOKEN=your_private_app_access_token_with_no_scopes
|
12
|
+
|
11
13
|
# VCR configuration
|
12
14
|
# record mode can be none, once, newepisodes or all (default none)
|
13
15
|
# see https://andrewmcodes.gitbook.io/vcr/record_modes
|
14
|
-
export VCR_RECORD_MODE=once
|
15
|
-
# to allow new request set to true
|
16
|
-
export VCR_ALLOW_REQUESTS=false
|
17
|
-
|
18
|
-
# Used in specs....
|
19
|
-
# The values of the following are the defaults if the env var is not set
|
20
16
|
|
21
|
-
#
|
22
|
-
export
|
17
|
+
# export VCR_RECORD_MODE=once # (default)
|
18
|
+
# export VCR_RECORD_MODE=all # always record http requests (overwrites)
|
23
19
|
|
24
|
-
#
|
25
|
-
export
|
26
|
-
|
27
|
-
# Company id to test deleting (hint: make a dummy one first!)
|
28
|
-
export HUBSPOT_TEST_COMPANY_ID_DELETE=666
|
29
|
-
|
30
|
-
# Contact if to test find and update
|
31
|
-
export HUBSPOT_TEST_CONTACT_ID=1
|
20
|
+
# to allow new request set to true
|
21
|
+
export VCR_ALLOW_REQUESTS=false
|
32
22
|
|
33
|
-
#
|
34
|
-
|
23
|
+
# SPECS:
|
24
|
+
# the following can be used when running the specs
|
35
25
|
|
36
|
-
|
37
|
-
export
|
26
|
+
export HUBSPOT_LOG_LEVEL=FATAL # no output during testing (default)
|
27
|
+
# export HUBSPOT_LOG_LEVEL=WARN # will show exceptions and batch load wait message
|
28
|
+
# export HUBSPOT_LOG_LEVEL=INFO # adds api request descriptions
|
29
|
+
# export HUBSPOT_LOG_LEVEL=DEBUG # adds api request body and full response (lots of output!)
|
38
30
|
|
39
|
-
#
|
40
|
-
export
|
31
|
+
# How long to wait after a batch is created (for propagation)
|
32
|
+
export HUBSPOT_BATCH_WAIT=0
|
data/.rspec
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ruby_hubspot_api (0.3.
|
4
|
+
ruby_hubspot_api (0.3.3)
|
5
5
|
httparty (>= 0.1, < 1.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -40,12 +40,12 @@ GEM
|
|
40
40
|
rspec-core (~> 3.13.0)
|
41
41
|
rspec-expectations (~> 3.13.0)
|
42
42
|
rspec-mocks (~> 3.13.0)
|
43
|
-
rspec-core (3.13.
|
43
|
+
rspec-core (3.13.2)
|
44
44
|
rspec-support (~> 3.13.0)
|
45
45
|
rspec-expectations (3.13.3)
|
46
46
|
diff-lcs (>= 1.2.0, < 2.0)
|
47
47
|
rspec-support (~> 3.13.0)
|
48
|
-
rspec-mocks (3.13.
|
48
|
+
rspec-mocks (3.13.2)
|
49
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
50
50
|
rspec-support (~> 3.13.0)
|
51
51
|
rspec-support (3.13.1)
|
@@ -62,8 +62,6 @@ GEM
|
|
62
62
|
|
63
63
|
PLATFORMS
|
64
64
|
ruby
|
65
|
-
x86_64-darwin-18
|
66
|
-
x86_64-linux
|
67
65
|
|
68
66
|
DEPENDENCIES
|
69
67
|
bundler (>= 2.0, < 2.4.0)
|
@@ -80,4 +78,4 @@ DEPENDENCIES
|
|
80
78
|
webmock (>= 3.0)
|
81
79
|
|
82
80
|
BUNDLED WITH
|
83
|
-
2.2
|
81
|
+
2.0.2
|
data/README.md
CHANGED
@@ -78,7 +78,7 @@ Hubspot::Company # crm > companies
|
|
78
78
|
Hubspot::User # hubspot users (also referred to as 'owners')
|
79
79
|
Hubspot::Owner # alias of Hubspot::User if you prefer to use it
|
80
80
|
```
|
81
|
-
|
81
|
+
|
82
82
|
however you can [add custom objects of your own](#user-content-custom-resources) based on your own custom defined Objects in Hubspot
|
83
83
|
|
84
84
|
### Creating and Saving an Object
|
@@ -254,7 +254,7 @@ Example:
|
|
254
254
|
|
255
255
|
```ruby
|
256
256
|
# Search for contacts with email containing "hubspot.com"
|
257
|
-
contacts = Hubspot::Contact.search(
|
257
|
+
contacts = Hubspot::Contact.search({ email_contains: 'hubspot.com' }, properties: %w[firstname lastname email])
|
258
258
|
|
259
259
|
puts "Searching for Hubspot staff in the contacts CRM"
|
260
260
|
puts ""
|
@@ -264,17 +264,18 @@ contacts.each do |contact|
|
|
264
264
|
end
|
265
265
|
|
266
266
|
# Search for companies with number of employees greater than or equal to 100
|
267
|
-
companies = Hubspot::Company.search(query: { number_of_employees_gte: 100 })
|
268
|
-
|
269
267
|
puts "Searching for medium to large companies"
|
270
268
|
puts ""
|
271
269
|
|
272
|
-
companies.
|
273
|
-
|
270
|
+
companies = Hubspot::Company.search(number_of_employees_gte: 100).all # returns a PagedCollection
|
271
|
+
companies.each_page do |page_of_companies|
|
272
|
+
page_of_companies.each do |company|
|
273
|
+
puts " Found: #{company.name} (#{company.number_of_employees} employees)"
|
274
|
+
end
|
274
275
|
end
|
275
276
|
|
276
277
|
# Search for contacts with email in a specific list (IN operator)
|
277
|
-
contacts = Hubspot::Contact.search(
|
278
|
+
contacts = Hubspot::Contact.search(email: ['user1@example.com', 'user2@example.com'])
|
278
279
|
|
279
280
|
contacts.each do |contact|
|
280
281
|
puts "Found: #{contact.email}"
|
@@ -288,7 +289,6 @@ end
|
|
288
289
|
- **gte**: Greater than or equal to.
|
289
290
|
- **lt**: Less than.
|
290
291
|
- **lte**: Less than or equal to.
|
291
|
-
- **IN**: Matches any of the values in an array.
|
292
292
|
|
293
293
|
#### Searching for empty values (NOT_HAS_PROPERTY)
|
294
294
|
|
@@ -296,7 +296,7 @@ Any empty value in your search will be matched using the correect filter in Hubs
|
|
296
296
|
|
297
297
|
```ruby
|
298
298
|
# Search for companies with no value for a given field
|
299
|
-
companies = Hubspot::Company.search(
|
299
|
+
companies = Hubspot::Company.search({ client_category: nil }, properties: %w[name number_of_employees])
|
300
300
|
# Request body: {"filterGroups":[{"filters":[{"propertyName":"client_category","operator":"NOT_HAS_PROPERTY"}]}]
|
301
301
|
|
302
302
|
puts "Searching for uncategorised customers"
|
@@ -310,6 +310,47 @@ companies.each do |company|
|
|
310
310
|
end
|
311
311
|
```
|
312
312
|
|
313
|
+
#### Searching for any value in an array (IN)
|
314
|
+
|
315
|
+
If value is an array the operator will be set to 'IN'
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
# Search for companies with no value for a given field
|
319
|
+
hot_contacts = Hubspot::Contact.search(hs_lead_status: %w[IN_PROGRESS OPEN_DEAL])
|
320
|
+
# Request body: {"filterGroups":[{"filters":[{"propertyName":"hs_lead_status","operator":"IN","values":["IN_PROGRESS", "OPEN_DEAL"]}]}]
|
321
|
+
|
322
|
+
puts "Current best leads"
|
323
|
+
puts ""
|
324
|
+
|
325
|
+
hot_contacts.each do |contact|
|
326
|
+
puts "#{contact.name} .... "
|
327
|
+
end
|
328
|
+
|
329
|
+
```
|
330
|
+
|
331
|
+
#### Chaining search conditions (a la Active Record)
|
332
|
+
|
333
|
+
You can use the alternative Rails-like interface to return a PagedCollection and can chain calls. Use `where(search_params)` to filter and `.select('firstname', 'lastname', 'email', 'custom_property')` to determine the properties returned by the API.
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
contact = Hubspot::Contact.where(hs_lead_status: [])
|
337
|
+
```
|
338
|
+
|
339
|
+
#### Getting the total number of records
|
340
|
+
|
341
|
+
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
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
contacts_collection = Hubspot::Contact.search(lead_status: 'cold')
|
345
|
+
|
346
|
+
if contacts_collection.total > 200
|
347
|
+
puts "Contacts will be processed overnight"
|
348
|
+
# schedule_processing_job
|
349
|
+
else
|
350
|
+
puts "Found #{contacts_collection.total} contacts. Processing...."
|
351
|
+
process_contacts(contacts_collection)
|
352
|
+
```
|
353
|
+
|
313
354
|
#### Specifying Properties in Search
|
314
355
|
|
315
356
|
When performing a search, you can also specify which properties to return.
|
@@ -320,7 +361,7 @@ Example:
|
|
320
361
|
```ruby
|
321
362
|
# Search for contacts with email containing "hubspot.com" and only return specific properties
|
322
363
|
contacts = Hubspot::Contact.search(
|
323
|
-
|
364
|
+
{ email_contains: 'hubspot.com' },
|
324
365
|
properties: ['firstname', 'lastname', 'email', 'mobile', 'custom_property_1']
|
325
366
|
)
|
326
367
|
|
@@ -383,7 +424,7 @@ companies = batch.resources
|
|
383
424
|
```
|
384
425
|
|
385
426
|
Example of reading contacts by email and the helper method `batch_read`
|
386
|
-
By using this method you can page through the results as needed or collect them
|
427
|
+
By using this method you can page through the results as needed or collect them
|
387
428
|
|
388
429
|
```ruby
|
389
430
|
email_addresses = my_selected_contacts.collect(&:email).compact
|
@@ -400,7 +441,7 @@ batch.each_page do |contacts|
|
|
400
441
|
end
|
401
442
|
```
|
402
443
|
|
403
|
-
Finally there is another helper method `batch_read_all` on any Hubspot::Resource class (Hubspot::Contact, Hubspot::Company, Hubspot::User etc) which will read all of the resources and return a HubSpot::Batch (with all of the resources).
|
444
|
+
Finally there is another helper method `batch_read_all` on any Hubspot::Resource class (Hubspot::Contact, Hubspot::Company, Hubspot::User etc) which will read all of the resources and return a HubSpot::Batch (with all of the resources).
|
404
445
|
|
405
446
|
You can then update the resources and call `update` on the batch.... see below
|
406
447
|
|
@@ -456,7 +497,7 @@ In this example, if a contact with the given email already exists in HubSpot, it
|
|
456
497
|
To archive objects in bulk, you can use the `archive` method. This removes the objects from HubSpot.
|
457
498
|
|
458
499
|
```ruby
|
459
|
-
contacts = Hubspot::Contact.
|
500
|
+
contacts = Hubspot::Contact.where(email_contains: 'hubspot.com').all
|
460
501
|
|
461
502
|
batch = Hubspot::Batch.new(contacts)
|
462
503
|
batch.archive
|
@@ -489,7 +530,7 @@ require 'ruby_hubspot_api' # if not required by bundler already...
|
|
489
530
|
|
490
531
|
module Hubspot
|
491
532
|
class Project < Resource
|
492
|
-
|
533
|
+
|
493
534
|
# resource_name (part of the url in the api) will default
|
494
535
|
# to a simple plural of the class name - in this case 'projects'
|
495
536
|
# if the url for your custom object is different you can override it
|
@@ -500,7 +541,7 @@ module Hubspot
|
|
500
541
|
end
|
501
542
|
end
|
502
543
|
|
503
|
-
projects = Hubspot::Projects.
|
544
|
+
projects = Hubspot::Projects.where(status: ['upcoming', 'active', 'overrun']).all
|
504
545
|
|
505
546
|
```
|
506
547
|
|
data/lib/hubspot/exceptions.rb
CHANGED
@@ -7,7 +7,9 @@ module Hubspot
|
|
7
7
|
attr_accessor :response
|
8
8
|
|
9
9
|
def initialize(response, message = nil)
|
10
|
-
|
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
|
@@ -20,6 +22,7 @@ module Hubspot
|
|
20
22
|
class NotConfiguredError < StandardError; end
|
21
23
|
class ArgumentError < StandardError; end
|
22
24
|
class NothingToDoError < StandardError; end
|
25
|
+
class NotImplementedError < StandardError; end
|
23
26
|
|
24
27
|
class << self
|
25
28
|
def error_from_response(response)
|
data/lib/hubspot/form.rb
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
module Hubspot
|
4
4
|
class Form < Resource
|
5
|
-
METADATA_FIELDS = %w[createdAt updatedAt archived].freeze
|
6
|
-
|
7
5
|
# :nocov:
|
8
6
|
def inspect
|
9
7
|
"#<#{self.class.name} " \
|
@@ -13,6 +11,8 @@ module Hubspot
|
|
13
11
|
# :nocov:
|
14
12
|
|
15
13
|
class << self
|
14
|
+
private
|
15
|
+
|
16
16
|
def api_root
|
17
17
|
'/marketing/v3'
|
18
18
|
end
|
@@ -24,5 +24,9 @@ module Hubspot
|
|
24
24
|
def extract_id(id)
|
25
25
|
id
|
26
26
|
end
|
27
|
+
|
28
|
+
def metadata_fields
|
29
|
+
%w[createdAt updatedAt archived].freeze
|
30
|
+
end
|
27
31
|
end
|
28
32
|
end
|
@@ -3,27 +3,38 @@
|
|
3
3
|
require 'json'
|
4
4
|
require_relative './api_client'
|
5
5
|
require_relative './exceptions'
|
6
|
+
require_relative './resource_filter'
|
6
7
|
|
7
8
|
module Hubspot
|
8
9
|
# Enumerable class for handling paged data from the API
|
9
10
|
class PagedCollection < ApiClient
|
10
11
|
include Enumerable
|
12
|
+
include ResourceFilter::FilterGroupMethods
|
11
13
|
|
12
|
-
MAX_LIMIT =
|
14
|
+
MAX_LIMIT = 200 # HubSpot max items per page
|
13
15
|
|
14
|
-
|
16
|
+
# rubocop:disable Lint/MissingSuper
|
17
|
+
def initialize(url:, params: {}, resource_class: nil, method: :get, results_param: 'results')
|
15
18
|
@url = url
|
16
19
|
@params = params
|
17
20
|
@resource_class = resource_class
|
18
21
|
@method = method.to_sym
|
22
|
+
@results_param = results_param
|
23
|
+
end
|
24
|
+
# rubocop:enable Lint/MissingSuper
|
25
|
+
|
26
|
+
def total
|
27
|
+
@total ||= determine_total
|
19
28
|
end
|
20
29
|
|
21
30
|
def each_page
|
22
31
|
offset = nil
|
23
32
|
loop do
|
24
33
|
response = fetch_page(offset)
|
34
|
+
@total = response['total'] if response['total']
|
25
35
|
mapped_results = process_results(response)
|
26
36
|
yield mapped_results unless mapped_results.empty?
|
37
|
+
sleep wait_between_pages
|
27
38
|
offset = response.dig('paging', 'next', 'after')
|
28
39
|
break unless offset
|
29
40
|
end
|
@@ -38,10 +49,12 @@ module Hubspot
|
|
38
49
|
end
|
39
50
|
|
40
51
|
# Override Enumerable's first method so as not to have to call each (via all)
|
52
|
+
# rubocop:disable Metrics/MethodLength
|
41
53
|
def first(limit = 1)
|
42
54
|
resources = []
|
43
55
|
remaining = limit
|
44
56
|
|
57
|
+
original_limit = @params.delete(:limit)
|
45
58
|
# Modify @params directly to set the limit
|
46
59
|
@params[:limit] = [remaining, MAX_LIMIT].min
|
47
60
|
|
@@ -52,8 +65,10 @@ module Hubspot
|
|
52
65
|
break if remaining <= 0
|
53
66
|
end
|
54
67
|
|
68
|
+
@params[:limit] = original_limit
|
55
69
|
limit == 1 ? resources.first : resources.first(limit)
|
56
70
|
end
|
71
|
+
# rubocop:enable Metrics/MethodLength
|
57
72
|
|
58
73
|
def each(&block)
|
59
74
|
each_page do |page|
|
@@ -61,8 +76,63 @@ module Hubspot
|
|
61
76
|
end
|
62
77
|
end
|
63
78
|
|
79
|
+
def where(filters = {})
|
80
|
+
dup.where!(filters)
|
81
|
+
end
|
82
|
+
|
83
|
+
def where!(filters = {})
|
84
|
+
# Build new filter groups from the provided filters
|
85
|
+
new_filter_groups = build_filter_groups(filters)
|
86
|
+
|
87
|
+
# Merge new filters into the existing params structure
|
88
|
+
if @params[:filterGroups]&.any?
|
89
|
+
@params[:filterGroups][0][:filters].concat(new_filter_groups[0][:filters])
|
90
|
+
else
|
91
|
+
@params[:filterGroups] = new_filter_groups
|
92
|
+
end
|
93
|
+
|
94
|
+
self # Returning self for method chaining if needed
|
95
|
+
end
|
96
|
+
|
97
|
+
def select(*properties)
|
98
|
+
unless properties.blank?
|
99
|
+
@params ||= {}
|
100
|
+
@params[:properties] ||= []
|
101
|
+
@params[:properties].concat(properties.flatten)
|
102
|
+
end
|
103
|
+
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
64
107
|
private
|
65
108
|
|
109
|
+
def determine_total
|
110
|
+
# We only get a response['total'] for the search endpoint
|
111
|
+
raise NotImplementedError, 'Total only available for search requests' unless search_request?
|
112
|
+
|
113
|
+
# if we don't already know the total we will make a single request and minimise the response
|
114
|
+
# size by asking for just one property and one record.
|
115
|
+
|
116
|
+
# store the current properties
|
117
|
+
original_properties = @params.delete(:properties)
|
118
|
+
|
119
|
+
# just request hs_object_id
|
120
|
+
@params[:properties] = ['hs_object_id']
|
121
|
+
|
122
|
+
# dummy request. @total will be set during the each_page evaluation
|
123
|
+
_first_page = first
|
124
|
+
|
125
|
+
# restore the original properties
|
126
|
+
@params[:properties] = original_properties
|
127
|
+
|
128
|
+
# return the now set total
|
129
|
+
@total
|
130
|
+
end
|
131
|
+
|
132
|
+
def search_request?
|
133
|
+
@url.include?('/search')
|
134
|
+
end
|
135
|
+
|
66
136
|
def fetch_page(offset)
|
67
137
|
params_with_offset = @params.dup
|
68
138
|
params_with_offset.merge!(after: offset) if offset
|
@@ -82,8 +152,14 @@ module Hubspot
|
|
82
152
|
end
|
83
153
|
end
|
84
154
|
|
155
|
+
def wait_between_pages
|
156
|
+
return 0.2 if search_request?
|
157
|
+
|
158
|
+
0
|
159
|
+
end
|
160
|
+
|
85
161
|
def process_results(response)
|
86
|
-
results = response[
|
162
|
+
results = response[@results_param] || []
|
87
163
|
return results unless @resource_class
|
88
164
|
|
89
165
|
results.map { |result| @resource_class.new(result) }
|
data/lib/hubspot/resource.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative './api_client'
|
4
4
|
require_relative './paged_collection'
|
5
5
|
require_relative './paged_batch'
|
6
|
+
require_relative './resource_filter'
|
6
7
|
|
7
8
|
module Hubspot
|
8
9
|
# rubocop:disable Metrics/ClassLength
|
@@ -26,6 +27,7 @@ module Hubspot
|
|
26
27
|
#
|
27
28
|
class Resource < ApiClient
|
28
29
|
METADATA_FIELDS = %w[createdate hs_object_id lastmodifieddate].freeze
|
30
|
+
extend ResourceFilter::FilterGroupMethods
|
29
31
|
|
30
32
|
# Allow read/write access to id, properties, changes and metadata
|
31
33
|
|
@@ -42,6 +44,62 @@ module Hubspot
|
|
42
44
|
attr_accessor :metadata
|
43
45
|
|
44
46
|
class << self
|
47
|
+
# Return a paged_collection - similar to an ActiveRecord Relation
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
# contacts_collection = Hubspot::Contact.all
|
51
|
+
# <PagedCollection>
|
52
|
+
#
|
53
|
+
# contacts_collection.where(email_contains: 'hubspot.com')
|
54
|
+
# <PagedCollection, @params={:filterGroups=> ....
|
55
|
+
def all
|
56
|
+
PagedCollection.new(
|
57
|
+
url: "#{api_root}/#{resource_name}/search",
|
58
|
+
params: {},
|
59
|
+
resource_class: self,
|
60
|
+
method: :post,
|
61
|
+
results_param: results_param
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Filter resources - allows chaining
|
66
|
+
#
|
67
|
+
# This method allows searching for resources by passing a hash with special suffixes
|
68
|
+
# on the keys to define different comparison operators.
|
69
|
+
#
|
70
|
+
# Available suffixes for query keys (when using a hash):
|
71
|
+
# - `_contains`: Matches values that contain the given string.
|
72
|
+
# - `_gt`: Greater than comparison.
|
73
|
+
# - `_lt`: Less than comparison.
|
74
|
+
# - `_gte`: Greater than or equal to comparison.
|
75
|
+
# - `_lte`: Less than or equal to comparison.
|
76
|
+
# - `_neq`: Not equal to comparison.
|
77
|
+
#
|
78
|
+
# If value is an array the 'IN' operator will be used
|
79
|
+
#
|
80
|
+
# Otherwise if no suffix is provided, the default comparison is equality (`EQ`).
|
81
|
+
#
|
82
|
+
# If no value is provided, or is empty the NOT_HAS_PROPERTY operator will be used
|
83
|
+
#
|
84
|
+
# filters - [Hash] The query for searching.
|
85
|
+
# each key represents a property and may have suffixes for the comparison
|
86
|
+
# (e.g., `{ email_contains: 'example.org', age_gt: 30 }`).
|
87
|
+
|
88
|
+
# Example:
|
89
|
+
#
|
90
|
+
# big_companies = Hubspot:Company.where(number_of_employees_gte: 100 )
|
91
|
+
# live_contacts = Hubspot::Contact.where(hs_lead_status: %w[NEW OPEN IN_PROGRESS])
|
92
|
+
#
|
93
|
+
# Returns a PagedCollection
|
94
|
+
def where(filters = {})
|
95
|
+
all.where!(filters)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Select which properties to return from the api - allows chaining
|
99
|
+
def select(*properties)
|
100
|
+
all.select(*properties)
|
101
|
+
end
|
102
|
+
|
45
103
|
# Find a resource by ID and return an instance of the class
|
46
104
|
#
|
47
105
|
# id - [Integer] The ID (or hs_object_id) of the resource to fetch.
|
@@ -53,11 +111,14 @@ module Hubspot
|
|
53
111
|
#
|
54
112
|
# Returns An instance of the resource.
|
55
113
|
def find(id, properties: nil)
|
56
|
-
|
57
|
-
if
|
58
|
-
|
59
|
-
|
60
|
-
|
114
|
+
response = response_for_find_by_id(id, properties: properties)
|
115
|
+
return if response.not_found?
|
116
|
+
|
117
|
+
instantiate_from_response(response)
|
118
|
+
end
|
119
|
+
|
120
|
+
def find!(id, properties: nil)
|
121
|
+
response = response_for_find_by_id(id, properties: properties)
|
61
122
|
instantiate_from_response(response)
|
62
123
|
end
|
63
124
|
|
@@ -69,16 +130,16 @@ module Hubspot
|
|
69
130
|
#
|
70
131
|
# Example:
|
71
132
|
# properties = %w[firstname lastname email last_contacted]
|
72
|
-
# contact = Hubspot::Contact.find_by("email", "john@example.com", properties)
|
133
|
+
# contact = Hubspot::Contact.find_by("email", "john@example.com", properties: properties)
|
73
134
|
#
|
74
135
|
# Returns An instance of the resource.
|
75
|
-
def find_by(property, value, properties
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
params[:properties] = all_properties unless all_properties.empty?
|
136
|
+
def find_by(property, value, properties: nil)
|
137
|
+
response = response_for_find_by_property(property, value, properties: properties)
|
138
|
+
instantiate_from_response(response) unless response.not_found?
|
139
|
+
end
|
80
140
|
|
81
|
-
|
141
|
+
def find_by!(property, value, properties: nil)
|
142
|
+
response = response_for_find_by_property(property, value, properties: properties)
|
82
143
|
instantiate_from_response(response)
|
83
144
|
end
|
84
145
|
|
@@ -231,17 +292,6 @@ module Hubspot
|
|
231
292
|
properties.detect { |prop| prop.name == property_name }
|
232
293
|
end
|
233
294
|
|
234
|
-
# Simplified search interface
|
235
|
-
OPERATOR_MAP = {
|
236
|
-
'_contains' => 'CONTAINS_TOKEN',
|
237
|
-
'_gt' => 'GT',
|
238
|
-
'_lt' => 'LT',
|
239
|
-
'_gte' => 'GTE',
|
240
|
-
'_lte' => 'LTE',
|
241
|
-
'_neq' => 'NEQ',
|
242
|
-
'_in' => 'IN'
|
243
|
-
}.freeze
|
244
|
-
|
245
295
|
# rubocop:disable Metrics/MethodLength
|
246
296
|
|
247
297
|
# Search for resources using a flexible query format and optional properties.
|
@@ -287,11 +337,11 @@ module Hubspot
|
|
287
337
|
# )
|
288
338
|
#
|
289
339
|
# Returns [PagedCollection] A paged collection of results that can be iterated over.
|
290
|
-
def search(query
|
340
|
+
def search(query, properties: [], page_size: 200)
|
291
341
|
search_body = {}
|
292
342
|
|
293
343
|
# Add properties if specified
|
294
|
-
search_body[:properties] = properties unless properties.empty?
|
344
|
+
search_body[:properties] = build_property_list(properties) unless properties.empty?
|
295
345
|
|
296
346
|
# Handle the query using case-when for RuboCop compliance
|
297
347
|
case query
|
@@ -311,16 +361,12 @@ module Hubspot
|
|
311
361
|
url: "#{api_root}/#{resource_name}/search",
|
312
362
|
params: search_body,
|
313
363
|
resource_class: self,
|
314
|
-
method: :post
|
364
|
+
method: :post,
|
365
|
+
results_param: results_param
|
315
366
|
)
|
316
367
|
end
|
317
|
-
|
318
368
|
# rubocop:enable Metrics/MethodLength
|
319
369
|
|
320
|
-
# The root of the api call. Mostly this will be "crm"
|
321
|
-
# but you can override this to account for a different
|
322
|
-
# object hierarchy
|
323
|
-
|
324
370
|
# Define the resource name based on the class
|
325
371
|
def resource_name
|
326
372
|
name = self.name.split('::').last.downcase
|
@@ -339,49 +385,44 @@ module Hubspot
|
|
339
385
|
|
340
386
|
private
|
341
387
|
|
388
|
+
# The root of the api call. Mostly this will be "crm"
|
389
|
+
# but you can override this to account for a different
|
390
|
+
# object hierarchy
|
342
391
|
def api_root
|
343
392
|
'/crm/v3/objects'
|
344
393
|
end
|
345
394
|
|
346
|
-
|
347
|
-
|
395
|
+
# In the response from the api the resources returned in this key
|
396
|
+
def results_param
|
397
|
+
'results'
|
348
398
|
end
|
349
399
|
|
350
|
-
|
351
|
-
|
352
|
-
data = handle_response(response)
|
353
|
-
new(data) # Passing full response data to initialize
|
400
|
+
def list_page_uri
|
401
|
+
"#{api_root}/#{resource_name}"
|
354
402
|
end
|
355
403
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
filters.each do |key, value|
|
361
|
-
filter = extract_property_and_operator(key, value)
|
362
|
-
value_key = value.is_a?(Array) ? :values : :value
|
363
|
-
filter[value_key] = value unless value.blank?
|
364
|
-
filter_groups.first[:filters] << filter
|
404
|
+
def response_for_find_by_id(id, properties: nil)
|
405
|
+
all_properties = build_property_list(properties)
|
406
|
+
if all_properties.is_a?(Array) && !all_properties.empty?
|
407
|
+
params = { query: { properties: all_properties } }
|
365
408
|
end
|
366
409
|
|
367
|
-
|
410
|
+
get("#{api_root}/#{resource_name}/#{id}", params || {})
|
368
411
|
end
|
369
412
|
|
370
|
-
|
371
|
-
|
372
|
-
|
413
|
+
def response_for_find_by_property(property, value, properties: nil)
|
414
|
+
params = { idProperty: property }
|
415
|
+
|
416
|
+
all_properties = build_property_list(properties)
|
417
|
+
params[:properties] = all_properties unless all_properties.empty?
|
373
418
|
|
374
|
-
|
375
|
-
|
376
|
-
return {
|
377
|
-
propertyName: key.to_s.sub(suffix, ''),
|
378
|
-
operator: hubspot_operator
|
379
|
-
}
|
380
|
-
end
|
381
|
-
end
|
419
|
+
get("#{api_root}/#{resource_name}/#{value}", query: params)
|
420
|
+
end
|
382
421
|
|
383
|
-
|
384
|
-
|
422
|
+
# Instantiate a single resource object from the response
|
423
|
+
def instantiate_from_response(response)
|
424
|
+
data = handle_response(response)
|
425
|
+
new(data) # Passing full response data to initialize
|
385
426
|
end
|
386
427
|
|
387
428
|
# Internal make a list of properties to request from the API
|
@@ -420,7 +461,7 @@ module Hubspot
|
|
420
461
|
# existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)
|
421
462
|
def initialize(data = {})
|
422
463
|
data.transform_keys!(&:to_s)
|
423
|
-
@id = extract_id(data.delete(
|
464
|
+
@id = extract_id(data.delete(api_id_field))
|
424
465
|
@properties = {}
|
425
466
|
@metadata = {}
|
426
467
|
if @id
|
@@ -571,6 +612,10 @@ module Hubspot
|
|
571
612
|
|
572
613
|
private
|
573
614
|
|
615
|
+
def api_id_field
|
616
|
+
'id'
|
617
|
+
end
|
618
|
+
|
574
619
|
# Extract ID from data and convert to integer
|
575
620
|
def extract_id(id)
|
576
621
|
id&.to_i
|
@@ -616,9 +661,13 @@ module Hubspot
|
|
616
661
|
end
|
617
662
|
end
|
618
663
|
|
619
|
-
# allows overwriting in other resource classes
|
620
664
|
def metadata_field?(key)
|
621
|
-
|
665
|
+
metadata_fields.include?(key)
|
666
|
+
end
|
667
|
+
|
668
|
+
# allows overwriting in other resource classes
|
669
|
+
def metadata_fields
|
670
|
+
METADATA_FIELDS
|
622
671
|
end
|
623
672
|
|
624
673
|
# Initialize a new object (no API response)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hubspot
|
4
|
+
module ResourceFilter
|
5
|
+
module FilterGroupMethods
|
6
|
+
# Simplified search interface
|
7
|
+
OPERATOR_MAP = {
|
8
|
+
'_contains' => 'CONTAINS_TOKEN',
|
9
|
+
'_gt' => 'GT',
|
10
|
+
'_lt' => 'LT',
|
11
|
+
'_gte' => 'GTE',
|
12
|
+
'_lte' => 'LTE',
|
13
|
+
'_neq' => 'NEQ',
|
14
|
+
'_in' => 'IN'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# Convert simple filters to HubSpot's filterGroups format
|
18
|
+
def build_filter_groups(filters)
|
19
|
+
filter_groups = [{ filters: [] }]
|
20
|
+
|
21
|
+
filters.each do |key, value|
|
22
|
+
filter = extract_property_and_operator(key, value)
|
23
|
+
value_key = value.is_a?(Array) ? :values : :value
|
24
|
+
filter[value_key] = value unless value.blank?
|
25
|
+
filter_groups.first[:filters] << filter
|
26
|
+
end
|
27
|
+
|
28
|
+
filter_groups
|
29
|
+
end
|
30
|
+
|
31
|
+
# Extract property name and operator from the key
|
32
|
+
def extract_property_and_operator(key, value)
|
33
|
+
return { propertyName: key.to_s, operator: 'NOT_HAS_PROPERTY' } if value.blank?
|
34
|
+
|
35
|
+
OPERATOR_MAP.each do |suffix, hubspot_operator|
|
36
|
+
if key.to_s.end_with?(suffix)
|
37
|
+
return {
|
38
|
+
propertyName: key.to_s.sub(suffix, ''),
|
39
|
+
operator: hubspot_operator
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Default to 'EQ' operator if no suffix is found
|
45
|
+
{ propertyName: key.to_s, operator: 'EQ' }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/hubspot/version.rb
CHANGED
data/lib/hubspot.rb
CHANGED
@@ -24,14 +24,20 @@ module Hubspot
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def configured?
|
27
|
-
|
27
|
+
true unless @config.nil?
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
# Set Authorization header on
|
32
|
+
# Set Authorization header on all the resource classes when access_token is configured
|
33
33
|
def set_client_headers
|
34
|
-
|
34
|
+
api_classes = ObjectSpace.each_object(Class).select do |klass|
|
35
|
+
klass < Hubspot::ApiClient && !klass.singleton_class?
|
36
|
+
end
|
37
|
+
|
38
|
+
api_classes.each do |subclass|
|
39
|
+
subclass.headers 'Authorization' => "Bearer #{config.access_token}"
|
40
|
+
end
|
35
41
|
end
|
36
42
|
|
37
43
|
def set_request_timeouts
|
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.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Brook
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -228,6 +228,7 @@ files:
|
|
228
228
|
- lib/hubspot/paged_collection.rb
|
229
229
|
- lib/hubspot/property.rb
|
230
230
|
- lib/hubspot/resource.rb
|
231
|
+
- lib/hubspot/resource_filter.rb
|
231
232
|
- lib/hubspot/user.rb
|
232
233
|
- lib/hubspot/version.rb
|
233
234
|
- lib/ruby_hubspot_api.rb
|