ruby_hubspot_api 0.2.1 → 0.2.2
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/.rubocop.yml +17 -4
- data/Gemfile.lock +12 -16
- data/lib/hubspot/api_client.rb +10 -3
- data/lib/hubspot/batch.rb +76 -31
- data/lib/hubspot/config.rb +5 -5
- data/lib/hubspot/form.rb +26 -0
- data/lib/hubspot/paged_batch.rb +18 -3
- data/lib/hubspot/paged_collection.rb +8 -4
- data/lib/hubspot/resource.rb +121 -47
- data/lib/hubspot/user.rb +25 -1
- data/lib/hubspot/version.rb +1 -1
- data/lib/hubspot.rb +1 -0
- data/lib/ruby_hubspot_api.rb +6 -1
- data/lib/support/patches.rb +27 -0
- data/ruby_hubspot_api.gemspec +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52c9ee03d417a29792dbd370399982a03662cb621c0ab3e5ec0a2abd7de89ac5
|
4
|
+
data.tar.gz: aae2926993d4b6dcf78b8e728c031631225f667c882f0b6d0710601ff62c0772
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d2c9d82565c619a189bfc0780c914bb1b10cf769eb94b66e1e702fa5268fedadf7e3b9a188f6a1b0caa67c1b2d432e8f6401ffb890ca01efc768abc16ae3d3a
|
7
|
+
data.tar.gz: e66a9d5daa8373529a3a2ee07c474d288d94327fc40d83ed08e050e1dbb83c8f89217c95083035396f212ed78b97348f1a9437889d2719cd9dbf49d4624cbce3
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,19 @@
|
|
1
|
-
|
1
|
+
Documentation:
|
2
|
+
Enabled: false
|
3
|
+
|
4
|
+
Metrics/LineLength:
|
5
|
+
Max: 100
|
6
|
+
Exclude:
|
7
|
+
- spec/**/*.rb
|
8
|
+
|
9
|
+
# Metrics/MethodLength:
|
10
|
+
# Max: 20
|
11
|
+
|
12
|
+
# Metrics/ClassLength:
|
13
|
+
# Max: 200
|
2
14
|
|
3
|
-
# Ignore Metrics/BlockLength for the spec directory
|
4
15
|
Metrics/BlockLength:
|
5
|
-
|
6
|
-
|
16
|
+
# Max: 50
|
17
|
+
# Ignore Metrics/BlockLength for the spec directory
|
18
|
+
Exclude:
|
19
|
+
- spec/**/*.rb
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ruby_hubspot_api (0.2)
|
4
|
+
ruby_hubspot_api (0.2.2)
|
5
5
|
httparty (>= 0.1, < 1.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -9,35 +9,33 @@ GEM
|
|
9
9
|
specs:
|
10
10
|
addressable (2.8.7)
|
11
11
|
public_suffix (>= 2.0.2, < 7.0)
|
12
|
-
bigdecimal (3.
|
12
|
+
bigdecimal (3.0.2)
|
13
13
|
byebug (11.1.3)
|
14
|
-
codecov (0.
|
15
|
-
|
16
|
-
simplecov
|
14
|
+
codecov (0.6.0)
|
15
|
+
simplecov (>= 0.15, < 0.22)
|
17
16
|
coderay (1.1.3)
|
18
17
|
crack (1.0.0)
|
19
18
|
bigdecimal
|
20
19
|
rexml
|
21
20
|
diff-lcs (1.5.1)
|
22
|
-
docile (1.
|
21
|
+
docile (1.3.5)
|
23
22
|
dotenv (2.8.1)
|
24
23
|
hashdiff (1.1.1)
|
25
24
|
httparty (0.21.0)
|
26
25
|
mini_mime (>= 1.0.0)
|
27
26
|
multi_xml (>= 0.5.2)
|
28
|
-
json (2.7.2)
|
29
27
|
method_source (1.1.0)
|
30
28
|
mini_mime (1.1.2)
|
31
29
|
multi_xml (0.6.0)
|
32
|
-
pry (0.
|
30
|
+
pry (0.14.2)
|
33
31
|
coderay (~> 1.1)
|
34
32
|
method_source (~> 1.0)
|
35
|
-
pry-byebug (3.
|
33
|
+
pry-byebug (3.8.0)
|
36
34
|
byebug (~> 11.0)
|
37
|
-
pry (~> 0.
|
35
|
+
pry (~> 0.10)
|
38
36
|
public_suffix (4.0.7)
|
39
37
|
rake (13.2.1)
|
40
|
-
rexml (3.
|
38
|
+
rexml (3.2.5)
|
41
39
|
rspec (3.13.0)
|
42
40
|
rspec-core (~> 3.13.0)
|
43
41
|
rspec-expectations (~> 3.13.0)
|
@@ -51,15 +49,13 @@ GEM
|
|
51
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
52
50
|
rspec-support (~> 3.13.0)
|
53
51
|
rspec-support (3.13.1)
|
54
|
-
simplecov (0.
|
52
|
+
simplecov (0.18.5)
|
55
53
|
docile (~> 1.1)
|
56
54
|
simplecov-html (~> 0.11)
|
57
|
-
simplecov_json_formatter (~> 0.1)
|
58
55
|
simplecov-html (0.13.1)
|
59
56
|
simplecov-lcov (0.8.0)
|
60
|
-
simplecov_json_formatter (0.1.4)
|
61
57
|
vcr (6.0.0)
|
62
|
-
webmock (3.
|
58
|
+
webmock (3.18.1)
|
63
59
|
addressable (>= 2.8.0)
|
64
60
|
crack (>= 0.3.2)
|
65
61
|
hashdiff (>= 0.4.0, < 2.0.0)
|
@@ -84,4 +80,4 @@ DEPENDENCIES
|
|
84
80
|
webmock (>= 3.0)
|
85
81
|
|
86
82
|
BUNDLED WITH
|
87
|
-
2.
|
83
|
+
2.2.34
|
data/lib/hubspot/api_client.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# HubSpot API Client
|
4
|
+
# Handles all HTTP interactions with the HubSpot API.
|
5
|
+
# It manages GET, POST, PATCH, and DELETE requests.
|
3
6
|
module Hubspot
|
4
7
|
# All interations with the Hubspot API happen here...
|
5
8
|
class ApiClient
|
@@ -57,10 +60,14 @@ module Hubspot
|
|
57
60
|
|
58
61
|
def log_request(http_method, url, response, start_time, extra = nil)
|
59
62
|
d = Time.now - start_time
|
60
|
-
|
63
|
+
|
64
|
+
Hubspot.logger.info(
|
65
|
+
"#{http_method.to_s.upcase} #{url} took #{d.round(2)}s with status #{response.code}"
|
66
|
+
)
|
67
|
+
|
61
68
|
return unless Hubspot.logger.debug?
|
62
69
|
|
63
|
-
Hubspot.logger.debug("Request body: #{extra}") if extra
|
70
|
+
Hubspot.logger.debug("Request body: #{extra[:body]}") if extra
|
64
71
|
Hubspot.logger.debug("Response body: #{response.body}")
|
65
72
|
end
|
66
73
|
|
@@ -91,7 +98,7 @@ module Hubspot
|
|
91
98
|
|
92
99
|
def retry_request(request, retries)
|
93
100
|
# Re-issues the original request using the retry logic
|
94
|
-
http_method = request.http_method::METHOD.downcase
|
101
|
+
http_method = request.http_method::METHOD.downcase
|
95
102
|
response = HTTParty.send(http_method, request.uri, request.options)
|
96
103
|
handle_response(response, retries)
|
97
104
|
end
|
data/lib/hubspot/batch.rb
CHANGED
@@ -31,8 +31,24 @@ module Hubspot
|
|
31
31
|
CONTACT_LIMIT = 10
|
32
32
|
DEFAULT_LIMIT = 100
|
33
33
|
|
34
|
+
def inspect
|
35
|
+
"#<#{self.class.name} " \
|
36
|
+
"@resource_count=#{@resources.size}, " \
|
37
|
+
"@id_property=#{@id_property.inspect}, " \
|
38
|
+
"@resource_type=#{@resources.first&.resource_name}, " \
|
39
|
+
"@responses_count=#{@responses.size}>"
|
40
|
+
end
|
41
|
+
|
34
42
|
# rubocop:disable Lint/MissingSuper
|
35
|
-
def initialize(resources = [], id_property: 'id')
|
43
|
+
def initialize(resources = [], id_property: 'id', resource_matcher: nil)
|
44
|
+
if resource_matcher
|
45
|
+
unless resource_matcher.is_a?(Proc) && resource_matcher.arity == 2
|
46
|
+
raise ArgumentError, 'resource_matcher must be a proc that accepts exactly 2 arguments'
|
47
|
+
end
|
48
|
+
|
49
|
+
@resource_matcher = resource_matcher
|
50
|
+
end
|
51
|
+
|
36
52
|
@resources = []
|
37
53
|
@id_property = id_property # Set id_property for the batch (default: 'id')
|
38
54
|
@responses = [] # Store multiple BatchResponse objects here
|
@@ -122,9 +138,12 @@ module Hubspot
|
|
122
138
|
next if resource.changes.empty?
|
123
139
|
|
124
140
|
{
|
125
|
-
|
126
|
-
|
127
|
-
|
141
|
+
# Dynamically get the ID based on the batch's id_property
|
142
|
+
id: resource.public_send(@id_property),
|
143
|
+
# Use the helper method to decide whether to include idProperty
|
144
|
+
idProperty: determine_id_property,
|
145
|
+
# Gather the changes for the resource
|
146
|
+
properties: resource.changes
|
128
147
|
}.compact # Removes nil keys
|
129
148
|
end.compact # Removes nil entries
|
130
149
|
end
|
@@ -146,7 +165,8 @@ module Hubspot
|
|
146
165
|
|
147
166
|
# Perform batch request based on the provided action (upsert, update, create, or archive)
|
148
167
|
def batch_request(type, inputs, action)
|
149
|
-
response = self.class.post("/crm/v3/objects/#{type}/batch/#{action}",
|
168
|
+
response = self.class.post("/crm/v3/objects/#{type}/batch/#{action}",
|
169
|
+
body: { inputs: inputs }.to_json)
|
150
170
|
BatchResponse.new(response.code, handle_response(response))
|
151
171
|
end
|
152
172
|
|
@@ -157,7 +177,8 @@ module Hubspot
|
|
157
177
|
# check if there are any resources without a value from the id_property
|
158
178
|
return unless @resources.any? { |resource| resource.public_send(id_property).blank? }
|
159
179
|
|
160
|
-
raise ArgumentError,
|
180
|
+
raise ArgumentError,
|
181
|
+
"All resources must have a non-blank value for #{@id_property} to perform upsert"
|
161
182
|
end
|
162
183
|
|
163
184
|
# Return the appropriate batch size limit for the resource type
|
@@ -167,6 +188,8 @@ module Hubspot
|
|
167
188
|
|
168
189
|
# Process responses from the batch API call
|
169
190
|
def process_responses
|
191
|
+
# TODO: issue a warning if the id_property is email and the action is upsert*
|
192
|
+
# people may have more than one email address abd Hubspot views that as one record
|
170
193
|
@responses.each do |response|
|
171
194
|
next unless response['results']
|
172
195
|
|
@@ -192,36 +215,56 @@ module Hubspot
|
|
192
215
|
end
|
193
216
|
|
194
217
|
def find_resource_from_result(result)
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
end
|
218
|
+
action_method = method_for_action
|
219
|
+
send(action_method, result) if action_method
|
220
|
+
end
|
221
|
+
|
222
|
+
def method_for_action
|
223
|
+
{
|
224
|
+
'create' => :find_resource_for_created_result,
|
225
|
+
'update' => :find_resource_from_updated_result,
|
226
|
+
'upsert' => :find_resource_from_upserted_result
|
227
|
+
}[@action]
|
228
|
+
end
|
229
|
+
|
230
|
+
def find_resource_for_created_result(result)
|
231
|
+
properties = result['properties']
|
232
|
+
|
233
|
+
@resources.reject(&:persisted?).find do |resource|
|
234
|
+
next unless resource.changes.any?
|
235
|
+
|
236
|
+
resource.changes.all? { |key, value| properties[key.to_s] == value }
|
215
237
|
end
|
216
238
|
end
|
217
239
|
|
240
|
+
def find_resource_from_updated_result(result)
|
241
|
+
resource_id = id_property == 'id' ? result['id'].to_i : result.dig('properties', id_property)
|
242
|
+
find_resource_from_id(resource_id)
|
243
|
+
end
|
244
|
+
|
218
245
|
def find_resource_from_id(resource_id)
|
219
|
-
|
246
|
+
return find_resource_from_id_property(resource_id) unless @id_property == 'id'
|
247
|
+
|
248
|
+
@resources.find { |resource| resource.id == resource_id }
|
220
249
|
end
|
221
250
|
|
222
|
-
|
223
|
-
|
224
|
-
|
251
|
+
def find_resource_from_id_property(resource_id)
|
252
|
+
@resources.find do |resource|
|
253
|
+
resource.respond_to?(@id_property) && resource.public_send(@id_property) == resource_id
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def find_resource_from_upserted_result(result)
|
258
|
+
# if this was inserted then match on all the fields
|
259
|
+
return find_resource_for_created_result(result['properties']) if result['new']
|
260
|
+
|
261
|
+
# call the custom resource matcher if specified
|
262
|
+
if @resource_matcher
|
263
|
+
@resources.find { |resource| @resource_matcher.call(resource, result) }
|
264
|
+
else
|
265
|
+
find_resource_from_updated_result(result)
|
266
|
+
end
|
267
|
+
end
|
225
268
|
|
226
269
|
def update_resource_properties(resource, properties)
|
227
270
|
properties.each do |key, value|
|
@@ -238,7 +281,9 @@ module Hubspot
|
|
238
281
|
|
239
282
|
class << self
|
240
283
|
def read(object_class, object_ids = [], id_property: 'id')
|
241
|
-
|
284
|
+
unless object_class < Hubspot::Resource
|
285
|
+
raise ArgumentError, 'Must be a valid Hubspot resource class'
|
286
|
+
end
|
242
287
|
|
243
288
|
# fetch all the matching resources with paging handled
|
244
289
|
resources = object_class.batch_read(object_ids, id_property: id_property).all
|
data/lib/hubspot/config.rb
CHANGED
@@ -15,6 +15,11 @@ module Hubspot
|
|
15
15
|
apply_log_level
|
16
16
|
end
|
17
17
|
|
18
|
+
# Apply the log level to the logger
|
19
|
+
def apply_log_level
|
20
|
+
@logger.level = @log_level
|
21
|
+
end
|
22
|
+
|
18
23
|
private
|
19
24
|
|
20
25
|
# Initialize the default logger
|
@@ -43,11 +48,6 @@ module Hubspot
|
|
43
48
|
end
|
44
49
|
# rubocop:enable Metrics/MethodLength
|
45
50
|
|
46
|
-
# Apply the log level to the logger
|
47
|
-
def apply_log_level
|
48
|
-
@logger.level = @log_level
|
49
|
-
end
|
50
|
-
|
51
51
|
# Set the default log level based on environment
|
52
52
|
def default_log_level
|
53
53
|
if defined?(Rails) && Rails.env.test?
|
data/lib/hubspot/form.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hubspot
|
4
|
+
class Form < Resource
|
5
|
+
METADATA_FIELDS = %w[createdAt updatedAt archived].freeze
|
6
|
+
|
7
|
+
def inspect
|
8
|
+
"#<#{self.class.name} " \
|
9
|
+
"@name=#{name}, " \
|
10
|
+
"@fieldGroups=#{respond_to?('fieldGroups') ? fieldGroups.size : '-'}>"
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def api_root
|
15
|
+
'/marketing/v3'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Extract ID from data and leave as a string
|
22
|
+
def extract_id(data)
|
23
|
+
data.delete('id')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/hubspot/paged_batch.rb
CHANGED
@@ -10,6 +10,15 @@ module Hubspot
|
|
10
10
|
|
11
11
|
MAX_LIMIT = 100 # HubSpot max items per page
|
12
12
|
|
13
|
+
# customised inspect
|
14
|
+
def inspect
|
15
|
+
"#<#{self.class.name} " \
|
16
|
+
"@url=#{@url.inspect}, " \
|
17
|
+
"@params=#{@params.inspect}, " \
|
18
|
+
"@resource_class=#{@resource_class.inspect}, " \
|
19
|
+
"@object_ids_count=#{@object_ids.size}>"
|
20
|
+
end
|
21
|
+
|
13
22
|
# rubocop:disable Lint/MissingSuper
|
14
23
|
def initialize(url:, params: {}, resource_class: nil, object_ids: [])
|
15
24
|
@url = url
|
@@ -22,8 +31,7 @@ module Hubspot
|
|
22
31
|
def each_page
|
23
32
|
@object_ids.each_slice(MAX_LIMIT) do |ids|
|
24
33
|
response = fetch_page(ids)
|
25
|
-
|
26
|
-
mapped_results = @resource_class ? results.map { |result| @resource_class.new(result) } : results
|
34
|
+
mapped_results = process_results(response)
|
27
35
|
yield mapped_results unless mapped_results.empty?
|
28
36
|
end
|
29
37
|
end
|
@@ -45,12 +53,19 @@ module Hubspot
|
|
45
53
|
private
|
46
54
|
|
47
55
|
def fetch_page(object_ids)
|
48
|
-
params_with_ids = @params.dup
|
56
|
+
params_with_ids = @params.dup || {}
|
49
57
|
params_with_ids[:inputs] = object_ids.map { |id| { id: id } }
|
50
58
|
|
51
59
|
response = self.class.post(@url, body: params_with_ids.to_json)
|
52
60
|
|
53
61
|
handle_response(response)
|
54
62
|
end
|
63
|
+
|
64
|
+
def process_results(response)
|
65
|
+
results = response['results'] || []
|
66
|
+
return results unless @resource_class
|
67
|
+
|
68
|
+
results.map { |result| @resource_class.new(result) }
|
69
|
+
end
|
55
70
|
end
|
56
71
|
end
|
@@ -10,21 +10,18 @@ module Hubspot
|
|
10
10
|
|
11
11
|
MAX_LIMIT = 100 # HubSpot max items per page
|
12
12
|
|
13
|
-
# rubocop:disable Lint/MissingSuper
|
14
13
|
def initialize(url:, params: {}, resource_class: nil, method: :get)
|
15
14
|
@url = url
|
16
15
|
@params = params
|
17
16
|
@resource_class = resource_class
|
18
17
|
@method = method.to_sym
|
19
18
|
end
|
20
|
-
# rubocop:enable Lint/MissingSuper
|
21
19
|
|
22
20
|
def each_page
|
23
21
|
offset = nil
|
24
22
|
loop do
|
25
23
|
response = fetch_page(offset)
|
26
|
-
|
27
|
-
mapped_results = @resource_class ? results.map { |result| @resource_class.new(result) } : results
|
24
|
+
mapped_results = process_results(response)
|
28
25
|
yield mapped_results unless mapped_results.empty?
|
29
26
|
offset = response.dig('paging', 'next', 'after')
|
30
27
|
break unless offset
|
@@ -83,5 +80,12 @@ module Hubspot
|
|
83
80
|
self.class.send(@method, @url, body: params.to_json)
|
84
81
|
end
|
85
82
|
end
|
83
|
+
|
84
|
+
def process_results(response)
|
85
|
+
results = response['results'] || []
|
86
|
+
return results unless @resource_class
|
87
|
+
|
88
|
+
results.map { |result| @resource_class.new(result) }
|
89
|
+
end
|
86
90
|
end
|
87
91
|
end
|
data/lib/hubspot/resource.rb
CHANGED
@@ -8,13 +8,14 @@ module Hubspot
|
|
8
8
|
# rubocop:disable Metrics/ClassLength
|
9
9
|
|
10
10
|
# HubSpot Resource Base Class
|
11
|
-
# This class provides common functionality for interacting with
|
11
|
+
# This class provides common functionality for interacting with
|
12
|
+
# HubSpot API resources such as Contacts, Companies, etc
|
12
13
|
#
|
13
|
-
# It supports common operations like finding, creating, updating,
|
14
|
+
# It supports common operations like finding, creating, updating,
|
15
|
+
# and deleting resources, as well as batch operations.
|
14
16
|
#
|
15
|
-
# This class is meant to be inherited by specific resources
|
16
|
-
#
|
17
|
-
# You can access the properties of a resource instance by calling the property name as method
|
17
|
+
# This class is meant to be inherited by specific resources
|
18
|
+
# like `Hubspot::Contact`.
|
18
19
|
#
|
19
20
|
# Example Usage:
|
20
21
|
# Hubspot::Contact.find(1)
|
@@ -49,8 +50,12 @@ module Hubspot
|
|
49
50
|
# contact = Hubspot::Contact.find(1)
|
50
51
|
#
|
51
52
|
# Returns An instance of the resource.
|
52
|
-
def find(id)
|
53
|
-
|
53
|
+
def find(id, properties = nil)
|
54
|
+
all_properties = build_property_list(properties)
|
55
|
+
if all_properties.is_a?(Array) && !all_properties.empty?
|
56
|
+
params = { query: { properties: all_properties } }
|
57
|
+
end
|
58
|
+
response = get("#{api_root}/#{resource_name}/#{id}", params || {})
|
54
59
|
instantiate_from_response(response)
|
55
60
|
end
|
56
61
|
|
@@ -67,8 +72,11 @@ module Hubspot
|
|
67
72
|
# Returns An instance of the resource.
|
68
73
|
def find_by(property, value, properties = nil)
|
69
74
|
params = { idProperty: property }
|
70
|
-
|
71
|
-
|
75
|
+
|
76
|
+
all_properties = build_property_list(properties)
|
77
|
+
params[:properties] = all_properties unless all_properties.empty?
|
78
|
+
|
79
|
+
response = get("#{api_root}/#{resource_name}/#{value}", query: params)
|
72
80
|
instantiate_from_response(response)
|
73
81
|
end
|
74
82
|
|
@@ -81,7 +89,7 @@ module Hubspot
|
|
81
89
|
#
|
82
90
|
# Returns [Resource] The newly created resource.
|
83
91
|
def create(params)
|
84
|
-
response = post("
|
92
|
+
response = post("#{api_root}/#{resource_name}", body: { properties: params }.to_json)
|
85
93
|
instantiate_from_response(response)
|
86
94
|
end
|
87
95
|
|
@@ -93,10 +101,11 @@ module Hubspot
|
|
93
101
|
# Example:
|
94
102
|
# contact.update(1, name: "Jane Doe")
|
95
103
|
#
|
96
|
-
# Returns True if the update was successful
|
104
|
+
# Returns True if the update was successful
|
97
105
|
def update(id, params)
|
98
|
-
response = patch("
|
99
|
-
|
106
|
+
response = patch("#{api_root}/#{resource_name}/#{id}",
|
107
|
+
body: { properties: params }.to_json)
|
108
|
+
handle_response(response)
|
100
109
|
|
101
110
|
true
|
102
111
|
end
|
@@ -108,10 +117,10 @@ module Hubspot
|
|
108
117
|
# Example:
|
109
118
|
# Hubspot::Contact.archive(1)
|
110
119
|
#
|
111
|
-
# Returns True if the deletion was successful
|
120
|
+
# Returns True if the deletion was successful
|
112
121
|
def archive(id)
|
113
|
-
response = delete("
|
114
|
-
|
122
|
+
response = delete("#{api_root}/#{resource_name}/#{id}")
|
123
|
+
handle_response(response)
|
115
124
|
|
116
125
|
true
|
117
126
|
end
|
@@ -125,8 +134,14 @@ module Hubspot
|
|
125
134
|
#
|
126
135
|
# Returns [PagedCollection] A collection of resources.
|
127
136
|
def list(params = {})
|
137
|
+
all_properties = build_property_list(params[:properties])
|
138
|
+
|
139
|
+
if all_properties.is_a?(Array) && !all_properties.empty?
|
140
|
+
params[:properties] = all_properties.join(',')
|
141
|
+
end
|
142
|
+
|
128
143
|
PagedCollection.new(
|
129
|
-
url:
|
144
|
+
url: list_page_uri,
|
130
145
|
params: params,
|
131
146
|
resource_class: self
|
132
147
|
)
|
@@ -142,13 +157,15 @@ module Hubspot
|
|
142
157
|
# Example:
|
143
158
|
# Hubspot::Contact.batch_read([1, 2, 3])
|
144
159
|
#
|
145
|
-
# Returns [PagedBatch] A paged batch of resources
|
146
|
-
def batch_read(object_ids = [], id_property: 'id')
|
147
|
-
params =
|
160
|
+
# Returns [PagedBatch] A paged batch of resources
|
161
|
+
def batch_read(object_ids = [], properties: [], id_property: 'id')
|
162
|
+
params = {}
|
163
|
+
params[:idProperty] = id_property unless id_property == 'id'
|
164
|
+
params[:properties] = properties unless properties.blank?
|
148
165
|
|
149
166
|
PagedBatch.new(
|
150
|
-
url: "
|
151
|
-
params: params,
|
167
|
+
url: "#{api_root}/#{resource_name}/batch/read",
|
168
|
+
params: params.empty? ? nil : params,
|
152
169
|
object_ids: object_ids,
|
153
170
|
resource_class: self
|
154
171
|
)
|
@@ -161,7 +178,7 @@ module Hubspot
|
|
161
178
|
# id_property - The property to use for identifying resources (default: 'id').
|
162
179
|
#
|
163
180
|
# Example:
|
164
|
-
# Hubspot::Contact.
|
181
|
+
# Hubspot::Contact.batch_read_all(hubspot_contact_ids)
|
165
182
|
#
|
166
183
|
# Returns [Hubspot::Batch] A batch of resources that can be operated on further
|
167
184
|
def batch_read_all(object_ids = [], id_property: 'id')
|
@@ -203,9 +220,11 @@ module Hubspot
|
|
203
220
|
#
|
204
221
|
# Example:
|
205
222
|
# property = Hubspot::Contact.property('industry_sector')
|
206
|
-
# values_for_select = property.options.each_with_object({})
|
223
|
+
# values_for_select = property.options.each_with_object({}) do |prop, hash|
|
224
|
+
# hash[prop['value']] = prop['label']
|
225
|
+
# end
|
207
226
|
#
|
208
|
-
# Returns [
|
227
|
+
# Returns [Hubspot::Property] A hubspot property
|
209
228
|
def property(property_name)
|
210
229
|
properties.detect { |prop| prop.name == property_name }
|
211
230
|
end
|
@@ -225,8 +244,10 @@ module Hubspot
|
|
225
244
|
|
226
245
|
# Search for resources using a flexible query format and optional properties.
|
227
246
|
#
|
228
|
-
# This method allows searching for resources by passing a query in the form of a string
|
229
|
-
# or a hash with special suffixes on the keys to
|
247
|
+
# This method allows searching for resources by passing a query in the form of a string
|
248
|
+
# (for full-text search) or a hash with special suffixes on the keys to
|
249
|
+
# define different comparison operators.
|
250
|
+
#
|
230
251
|
# You can also specify which properties to return and the number of results per page.
|
231
252
|
#
|
232
253
|
# Available suffixes for query keys (when using a hash):
|
@@ -244,14 +265,15 @@ module Hubspot
|
|
244
265
|
# - A String: for full-text search.
|
245
266
|
# - A Hash: where each key represents a property and may have suffixes for the comparison
|
246
267
|
# (e.g., `{ email_contains: 'example.org', age_gt: 30 }`).
|
247
|
-
# properties - An optional array of property names to return in the search results.
|
268
|
+
# properties - An optional array of property names to return in the search results.
|
248
269
|
# If not specified or empty, HubSpot will return the default set of properties.
|
249
|
-
# page_size - The number of results to return per page
|
270
|
+
# page_size - The number of results to return per page
|
271
|
+
# (default is 10 for contacts and 100 for everything else).
|
250
272
|
#
|
251
273
|
# Example Usage:
|
252
274
|
# # Full-text search for 'example.org':
|
253
|
-
#
|
254
|
-
#
|
275
|
+
# props = %w[email firstname lastname]
|
276
|
+
# contacts = Hubspot::Contact.search(query: "example.org", properties: props, page_size: 50)
|
255
277
|
#
|
256
278
|
# # Search for contacts whose email contains 'example.org' and are older than 30:
|
257
279
|
# contacts = Hubspot::Contact.search(
|
@@ -282,7 +304,7 @@ module Hubspot
|
|
282
304
|
|
283
305
|
# Perform the search and return a PagedCollection
|
284
306
|
PagedCollection.new(
|
285
|
-
url: "
|
307
|
+
url: "#{api_root}/#{resource_name}/search",
|
286
308
|
params: search_body,
|
287
309
|
resource_class: self,
|
288
310
|
method: :post
|
@@ -291,6 +313,10 @@ module Hubspot
|
|
291
313
|
|
292
314
|
# rubocop:enable Metrics/MethodLength
|
293
315
|
|
316
|
+
# The root of the api call. Mostly this will be "crm"
|
317
|
+
# but you can override this to account for a different
|
318
|
+
# object hierarchy
|
319
|
+
|
294
320
|
# Define the resource name based on the class
|
295
321
|
def resource_name
|
296
322
|
name = self.name.split('::').last.downcase
|
@@ -301,8 +327,22 @@ module Hubspot
|
|
301
327
|
end
|
302
328
|
end
|
303
329
|
|
330
|
+
# List of properties that will always be retrieved
|
331
|
+
# should be overridden in specific resource class
|
332
|
+
def required_properties
|
333
|
+
[]
|
334
|
+
end
|
335
|
+
|
304
336
|
private
|
305
337
|
|
338
|
+
def api_root
|
339
|
+
'/crm/v3/objects'
|
340
|
+
end
|
341
|
+
|
342
|
+
def list_page_uri
|
343
|
+
"#{api_root}/#{resource_name}"
|
344
|
+
end
|
345
|
+
|
306
346
|
# Instantiate a single resource object from the response
|
307
347
|
def instantiate_from_response(response)
|
308
348
|
data = handle_response(response)
|
@@ -337,9 +377,18 @@ module Hubspot
|
|
337
377
|
# Default to 'EQ' operator if no suffix is found
|
338
378
|
{ propertyName: key.to_s, operator: 'EQ' }
|
339
379
|
end
|
380
|
+
|
381
|
+
# Internal make a list of properties to request from the API
|
382
|
+
# will be merged with any required_properties defined on the class
|
383
|
+
def build_property_list(properties)
|
384
|
+
properties = [] unless properties.is_a?(Array)
|
385
|
+
raise 'Must be an array' unless required_properties.is_a?(Array)
|
386
|
+
|
387
|
+
properties.concat(required_properties).uniq
|
388
|
+
end
|
340
389
|
end
|
341
390
|
|
342
|
-
# rubocop:disable
|
391
|
+
# rubocop:disable Lint/MissingSuper
|
343
392
|
|
344
393
|
# Public: Initialize a resouce
|
345
394
|
#
|
@@ -350,13 +399,14 @@ module Hubspot
|
|
350
399
|
# of the object against your own data
|
351
400
|
#
|
352
401
|
# Example:
|
353
|
-
#
|
402
|
+
# attrs = { firstname: 'Luke', lastname: 'Skywalker', email: 'luke@jedi.org' }
|
403
|
+
# contact = Hubspot::Contact.new(attrs)
|
354
404
|
# contact.persisted? # false
|
355
405
|
# contact.save # creates the record in Hubspot
|
356
406
|
# contact.persisted? # true
|
357
407
|
# puts "Contact saved with hubspot id #{contact.id}"
|
358
408
|
#
|
359
|
-
# existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.
|
409
|
+
# existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)
|
360
410
|
def initialize(data = {})
|
361
411
|
data.transform_keys!(&:to_s)
|
362
412
|
@id = extract_id(data)
|
@@ -368,8 +418,7 @@ module Hubspot
|
|
368
418
|
initialize_new_object(data)
|
369
419
|
end
|
370
420
|
end
|
371
|
-
|
372
|
-
# rubocop:enable Ling/MissingSuper
|
421
|
+
# rubocop:enable Lint/MissingSuper
|
373
422
|
|
374
423
|
# Determine the state of the object
|
375
424
|
#
|
@@ -405,25 +454,44 @@ module Hubspot
|
|
405
454
|
@id ? true : false
|
406
455
|
end
|
407
456
|
|
408
|
-
# Update the resource
|
457
|
+
# Public - Update the resource and persist to the api
|
409
458
|
#
|
410
|
-
#
|
459
|
+
# attributes - hash of properties to update in key value pairs
|
411
460
|
#
|
412
461
|
# Example:
|
413
462
|
# contact = Hubspot::Contact.find(hubspot_contact_id)
|
414
463
|
# contact.update(status: 'gold customer', last_contacted_at: Time.now.utc.iso8601)
|
415
464
|
#
|
416
465
|
# Returns Boolean
|
417
|
-
def update(
|
466
|
+
def update(attributes)
|
418
467
|
raise 'Not able to update as not persisted' unless persisted?
|
419
468
|
|
420
|
-
|
421
|
-
send("#{key}=", value) # This will trigger the @changes tracking via method_missing
|
422
|
-
end
|
469
|
+
update_attributes(attributes)
|
423
470
|
|
424
471
|
save
|
425
472
|
end
|
426
473
|
|
474
|
+
# Public - Update resource attributes
|
475
|
+
#
|
476
|
+
# Does not persist to the api but processes each attribute correctly
|
477
|
+
#
|
478
|
+
# Example:
|
479
|
+
# contact = Hubspot::Contact.find(hubspot_contact_id)
|
480
|
+
# contact.changes? # false
|
481
|
+
# contact.update_attributes(education: 'Graduate', university: 'Life')
|
482
|
+
# contact.education # Graduate
|
483
|
+
# contact.changes? # true
|
484
|
+
# contact.changes # { "education" => "Graduate", "university" => "Life" }
|
485
|
+
#
|
486
|
+
# Returns Hash of changes
|
487
|
+
def update_attributes(attributes)
|
488
|
+
raise ArgumentError, 'must be a hash' unless attributes.is_a?(Hash)
|
489
|
+
|
490
|
+
attributes.each do |key, value|
|
491
|
+
send("#{key}=", value) # This will trigger the @changes tracking via method_missing
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
427
495
|
# Archive the object in Hubspot
|
428
496
|
#
|
429
497
|
# Example:
|
@@ -488,9 +556,17 @@ module Hubspot
|
|
488
556
|
|
489
557
|
# Initialize from API response, separating metadata from properties
|
490
558
|
def initialize_from_api(data)
|
491
|
-
|
492
|
-
|
559
|
+
if data['properties']
|
560
|
+
@metadata = data.reject { |key, _v| key == 'properties' }
|
561
|
+
handle_properties(data['properties'])
|
562
|
+
else
|
563
|
+
handle_properties(data)
|
564
|
+
end
|
493
565
|
|
566
|
+
@changes = {}
|
567
|
+
end
|
568
|
+
|
569
|
+
def handle_properties(properties_data)
|
494
570
|
properties_data.each do |key, value|
|
495
571
|
if METADATA_FIELDS.include?(key)
|
496
572
|
@metadata[key] = value
|
@@ -498,8 +574,6 @@ module Hubspot
|
|
498
574
|
@properties[key] = value
|
499
575
|
end
|
500
576
|
end
|
501
|
-
|
502
|
-
@changes = {}
|
503
577
|
end
|
504
578
|
|
505
579
|
# Initialize a new object (no API response)
|
data/lib/hubspot/user.rb
CHANGED
@@ -1,8 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hubspot
|
4
|
+
# ORM for hubspot users
|
5
|
+
#
|
6
|
+
# Hubspot users consist mostly of read_only attributes (you can add custom properties).
|
7
|
+
# As such we extend this class to ensure that we retrieve useful data back from the API
|
8
|
+
# and provide helper methods to resolve hubspot fields e.g. user.email calls user.hs_email etc
|
4
9
|
class User < Resource
|
10
|
+
class << self
|
11
|
+
def required_properties
|
12
|
+
%w[hs_email hs_given_name hs_family_name]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def first_name
|
17
|
+
hs_given_name
|
18
|
+
end
|
19
|
+
alias firstname first_name
|
20
|
+
|
21
|
+
def last_name
|
22
|
+
hs_family_name
|
23
|
+
end
|
24
|
+
alias lastname last_name
|
25
|
+
|
26
|
+
def email
|
27
|
+
hs_email
|
28
|
+
end
|
5
29
|
end
|
6
30
|
|
7
|
-
Owner = User
|
31
|
+
Owner = User
|
8
32
|
end
|
data/lib/hubspot/version.rb
CHANGED
data/lib/hubspot.rb
CHANGED
data/lib/ruby_hubspot_api.rb
CHANGED
@@ -12,13 +12,18 @@ require_relative 'hubspot/config'
|
|
12
12
|
require_relative 'hubspot/exceptions'
|
13
13
|
require_relative 'hubspot/api_client'
|
14
14
|
|
15
|
-
# load base class then
|
15
|
+
# load base class then models
|
16
16
|
require_relative 'hubspot/resource'
|
17
17
|
require_relative 'hubspot/property'
|
18
|
+
|
19
|
+
# load CRM models
|
18
20
|
require_relative 'hubspot/contact'
|
19
21
|
require_relative 'hubspot/company'
|
20
22
|
require_relative 'hubspot/user'
|
21
23
|
|
24
|
+
# load marketing models
|
25
|
+
require_relative 'hubspot/form'
|
26
|
+
|
22
27
|
# Load other components
|
23
28
|
require_relative 'hubspot/batch'
|
24
29
|
require_relative 'hubspot/paged_collection'
|
data/lib/support/patches.rb
CHANGED
@@ -9,3 +9,30 @@ class Object
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
# At some point this will seem like a bad idea ;)
|
14
|
+
|
15
|
+
# :nocov:
|
16
|
+
if RUBY_VERSION < '2.5.0'
|
17
|
+
class Hash
|
18
|
+
# Non-mutating version (returns a new hash with transformed keys)
|
19
|
+
def transform_keys
|
20
|
+
return enum_for(:transform_keys) unless block_given?
|
21
|
+
result = {}
|
22
|
+
each_key do |key|
|
23
|
+
result[yield(key)] = self[key]
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
# Mutating version (modifies the hash in place)
|
29
|
+
def transform_keys!
|
30
|
+
return enum_for(:transform_keys!) unless block_given?
|
31
|
+
keys.each do |key|
|
32
|
+
self[yield(key)] = delete(key)
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
# :nocov:end
|
data/ruby_hubspot_api.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.homepage = 'https://github.com/sensadrome/ruby_hubspot_api'
|
17
17
|
spec.license = 'MIT'
|
18
18
|
|
19
|
-
spec.required_ruby_version = '>= 2.
|
19
|
+
spec.required_ruby_version = '>= 2.4'
|
20
20
|
|
21
21
|
# Prevent pushing this gem to RubyGems.org.
|
22
22
|
# To allow pushes either set the 'allowed_push_host'
|
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.2.
|
4
|
+
version: 0.2.2
|
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-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -223,6 +223,7 @@ files:
|
|
223
223
|
- lib/hubspot/config.rb
|
224
224
|
- lib/hubspot/contact.rb
|
225
225
|
- lib/hubspot/exceptions.rb
|
226
|
+
- lib/hubspot/form.rb
|
226
227
|
- lib/hubspot/paged_batch.rb
|
227
228
|
- lib/hubspot/paged_collection.rb
|
228
229
|
- lib/hubspot/property.rb
|
@@ -246,7 +247,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
246
247
|
requirements:
|
247
248
|
- - ">="
|
248
249
|
- !ruby/object:Gem::Version
|
249
|
-
version: '2.
|
250
|
+
version: '2.4'
|
250
251
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
251
252
|
requirements:
|
252
253
|
- - ">="
|