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