ruby_hubspot_api 0.2.1.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 +5 -5
- data/.rubocop.yml +17 -4
- data/Gemfile.lock +1 -1
- data/lib/hubspot/api_client.rb +9 -4
- data/lib/hubspot/batch.rb +76 -31
- 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 +313 -35
- data/lib/hubspot/user.rb +25 -1
- data/lib/hubspot/version.rb +1 -1
- data/lib/ruby_hubspot_api.rb +6 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
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
data/lib/hubspot/api_client.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# HubSpot API Client
|
4
|
-
# Handles all HTTP interactions with the HubSpot API.
|
4
|
+
# Handles all HTTP interactions with the HubSpot API.
|
5
|
+
# It manages GET, POST, PATCH, and DELETE requests.
|
5
6
|
module Hubspot
|
6
7
|
# All interations with the Hubspot API happen here...
|
7
8
|
class ApiClient
|
@@ -59,10 +60,14 @@ module Hubspot
|
|
59
60
|
|
60
61
|
def log_request(http_method, url, response, start_time, extra = nil)
|
61
62
|
d = Time.now - start_time
|
62
|
-
|
63
|
+
|
64
|
+
Hubspot.logger.info(
|
65
|
+
"#{http_method.to_s.upcase} #{url} took #{d.round(2)}s with status #{response.code}"
|
66
|
+
)
|
67
|
+
|
63
68
|
return unless Hubspot.logger.debug?
|
64
69
|
|
65
|
-
Hubspot.logger.debug("Request body: #{extra}") if extra
|
70
|
+
Hubspot.logger.debug("Request body: #{extra[:body]}") if extra
|
66
71
|
Hubspot.logger.debug("Response body: #{response.body}")
|
67
72
|
end
|
68
73
|
|
@@ -93,7 +98,7 @@ module Hubspot
|
|
93
98
|
|
94
99
|
def retry_request(request, retries)
|
95
100
|
# Re-issues the original request using the retry logic
|
96
|
-
http_method = request.http_method::METHOD.downcase
|
101
|
+
http_method = request.http_method::METHOD.downcase
|
97
102
|
response = HTTParty.send(http_method, request.uri, request.options)
|
98
103
|
handle_response(response, retries)
|
99
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/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
@@ -6,71 +6,188 @@ require_relative './paged_batch'
|
|
6
6
|
|
7
7
|
module Hubspot
|
8
8
|
# rubocop:disable Metrics/ClassLength
|
9
|
-
|
9
|
+
|
10
|
+
# HubSpot Resource Base Class
|
11
|
+
# This class provides common functionality for interacting with
|
12
|
+
# HubSpot API resources such as Contacts, Companies, etc
|
13
|
+
#
|
14
|
+
# It supports common operations like finding, creating, updating,
|
15
|
+
# and deleting resources, as well as batch operations.
|
16
|
+
#
|
17
|
+
# This class is meant to be inherited by specific resources
|
18
|
+
# like `Hubspot::Contact`.
|
19
|
+
#
|
20
|
+
# Example Usage:
|
21
|
+
# Hubspot::Contact.find(1)
|
22
|
+
# contact.name # 'Luke'
|
23
|
+
#
|
24
|
+
# company = Hubspot::Company.create(name: "Acme Corp")
|
25
|
+
# company.id.nil? # false
|
26
|
+
#
|
10
27
|
class Resource < ApiClient
|
11
28
|
METADATA_FIELDS = %w[createdate hs_object_id lastmodifieddate].freeze
|
12
29
|
|
13
|
-
# Allow read/write access to properties and metadata
|
14
|
-
|
30
|
+
# Allow read/write access to id, properties, changes and metadata
|
31
|
+
|
32
|
+
# the id of the object in hubspot
|
33
|
+
attr_accessor :id
|
34
|
+
|
35
|
+
# the properties as if read from the api
|
36
|
+
attr_accessor :properties
|
37
|
+
|
38
|
+
# track any changes made to properties before saving etc
|
39
|
+
attr_accessor :changes
|
40
|
+
|
41
|
+
# any other data sent from the api about the resource
|
42
|
+
attr_accessor :metadata
|
15
43
|
|
16
44
|
class << self
|
17
45
|
# Find a resource by ID and return an instance of the class
|
18
|
-
|
19
|
-
|
46
|
+
#
|
47
|
+
# id - [Integer] The ID (or hs_object_id) of the resource to fetch.
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
# contact = Hubspot::Contact.find(1)
|
51
|
+
#
|
52
|
+
# Returns An instance of the resource.
|
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 || {})
|
20
59
|
instantiate_from_response(response)
|
21
60
|
end
|
22
61
|
|
62
|
+
# Finds a resource by a given property and value.
|
63
|
+
#
|
64
|
+
# property - The property to search by (e.g., "email").
|
65
|
+
# value - The value of the property to match.
|
66
|
+
# properties - Optional list of properties to return.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
# properties = %w[firstname lastname email last_contacted]
|
70
|
+
# contact = Hubspot::Contact.find_by("email", "john@example.com", properties)
|
71
|
+
#
|
72
|
+
# Returns An instance of the resource.
|
23
73
|
def find_by(property, value, properties = nil)
|
24
74
|
params = { idProperty: property }
|
25
|
-
|
26
|
-
|
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)
|
27
80
|
instantiate_from_response(response)
|
28
81
|
end
|
29
82
|
|
30
|
-
#
|
83
|
+
# Creates a new resource with the given parameters.
|
84
|
+
#
|
85
|
+
# params - The properties to create the resource with.
|
86
|
+
#
|
87
|
+
# Example:
|
88
|
+
# contact = Hubspot::Contact.create(name: "John Doe", email: "john@example.com")
|
89
|
+
#
|
90
|
+
# Returns [Resource] The newly created resource.
|
31
91
|
def create(params)
|
32
|
-
response = post("
|
92
|
+
response = post("#{api_root}/#{resource_name}", body: { properties: params }.to_json)
|
33
93
|
instantiate_from_response(response)
|
34
94
|
end
|
35
95
|
|
96
|
+
# Updates an existing resource by ID.
|
97
|
+
#
|
98
|
+
# id - The ID of the resource to update.
|
99
|
+
# params - The properties to update.
|
100
|
+
#
|
101
|
+
# Example:
|
102
|
+
# contact.update(1, name: "Jane Doe")
|
103
|
+
#
|
104
|
+
# Returns True if the update was successful
|
36
105
|
def update(id, params)
|
37
|
-
response = patch("
|
38
|
-
|
106
|
+
response = patch("#{api_root}/#{resource_name}/#{id}",
|
107
|
+
body: { properties: params }.to_json)
|
108
|
+
handle_response(response)
|
39
109
|
|
40
110
|
true
|
41
111
|
end
|
42
112
|
|
113
|
+
# Deletes a resource by ID.
|
114
|
+
#
|
115
|
+
# id - The ID of the resource to delete.
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
# Hubspot::Contact.archive(1)
|
119
|
+
#
|
120
|
+
# Returns True if the deletion was successful
|
43
121
|
def archive(id)
|
44
|
-
response = delete("
|
45
|
-
|
122
|
+
response = delete("#{api_root}/#{resource_name}/#{id}")
|
123
|
+
handle_response(response)
|
46
124
|
|
47
125
|
true
|
48
126
|
end
|
49
127
|
|
128
|
+
# Lists all resources with optional filters and pagination.
|
129
|
+
#
|
130
|
+
# params - Optional parameters to filter or paginate the results.
|
131
|
+
#
|
132
|
+
# Example:
|
133
|
+
# contacts = Hubspot::Contact.list(limit: 100)
|
134
|
+
#
|
135
|
+
# Returns [PagedCollection] A collection of resources.
|
50
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
|
+
|
51
143
|
PagedCollection.new(
|
52
|
-
url:
|
144
|
+
url: list_page_uri,
|
53
145
|
params: params,
|
54
146
|
resource_class: self
|
55
147
|
)
|
56
148
|
end
|
57
149
|
|
58
|
-
|
59
|
-
|
150
|
+
# Performs a batch read operation to retrieve multiple resources by their IDs.
|
151
|
+
#
|
152
|
+
# object_ids - A list of resource IDs to fetch.
|
153
|
+
#
|
154
|
+
# id_property - The property to use for identifying resources (default: 'id').
|
155
|
+
#
|
156
|
+
#
|
157
|
+
# Example:
|
158
|
+
# Hubspot::Contact.batch_read([1, 2, 3])
|
159
|
+
#
|
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?
|
60
165
|
|
61
166
|
PagedBatch.new(
|
62
|
-
url: "
|
63
|
-
params: params,
|
167
|
+
url: "#{api_root}/#{resource_name}/batch/read",
|
168
|
+
params: params.empty? ? nil : params,
|
64
169
|
object_ids: object_ids,
|
65
170
|
resource_class: self
|
66
171
|
)
|
67
172
|
end
|
68
173
|
|
174
|
+
# Performs a batch read operation to retrieve multiple resources by their IDs
|
175
|
+
# until there are none left
|
176
|
+
#
|
177
|
+
# object_ids - A list of resource IDs to fetch. [Array<Integer>]
|
178
|
+
# id_property - The property to use for identifying resources (default: 'id').
|
179
|
+
#
|
180
|
+
# Example:
|
181
|
+
# Hubspot::Contact.batch_read_all(hubspot_contact_ids)
|
182
|
+
#
|
183
|
+
# Returns [Hubspot::Batch] A batch of resources that can be operated on further
|
69
184
|
def batch_read_all(object_ids = [], id_property: 'id')
|
70
185
|
Hubspot::Batch.read(self, object_ids, id_property: id_property)
|
71
186
|
end
|
72
187
|
|
73
|
-
#
|
188
|
+
# Retrieve the complete list of properties for this resource class
|
189
|
+
#
|
190
|
+
# Returns [Array<Hubspot::Property>] An array of hubspot properties
|
74
191
|
def properties
|
75
192
|
@properties ||= begin
|
76
193
|
response = get("/crm/v3/properties/#{resource_name}")
|
@@ -78,18 +195,36 @@ module Hubspot
|
|
78
195
|
end
|
79
196
|
end
|
80
197
|
|
198
|
+
# Retrieve the complete list of user defined properties for this resource class
|
199
|
+
#
|
200
|
+
# Returns [Array<Hubspot::Property>] An array of hubspot properties
|
81
201
|
def custom_properties
|
82
202
|
properties.reject { |property| property['hubspotDefined'] }
|
83
203
|
end
|
84
204
|
|
205
|
+
# Retrieve the complete list of updatable properties for this resource class
|
206
|
+
#
|
207
|
+
# Returns [Array<Hubspot::Property>] An array of updateable hubspot properties
|
85
208
|
def updatable_properties
|
86
209
|
properties.reject(&:read_only?)
|
87
210
|
end
|
88
211
|
|
212
|
+
# Retrieve the complete list of read-only properties for this resource class
|
213
|
+
#
|
214
|
+
# Returns [Array<Hubspot::Property>] An array of read-only hubspot properties
|
89
215
|
def read_only_properties
|
90
|
-
properties.select(&:read_only
|
216
|
+
properties.select(&:read_only)
|
91
217
|
end
|
92
218
|
|
219
|
+
# Retrieve information about a specific property
|
220
|
+
#
|
221
|
+
# Example:
|
222
|
+
# property = Hubspot::Contact.property('industry_sector')
|
223
|
+
# values_for_select = property.options.each_with_object({}) do |prop, hash|
|
224
|
+
# hash[prop['value']] = prop['label']
|
225
|
+
# end
|
226
|
+
#
|
227
|
+
# Returns [Hubspot::Property] A hubspot property
|
93
228
|
def property(property_name)
|
94
229
|
properties.detect { |prop| prop.name == property_name }
|
95
230
|
end
|
@@ -106,6 +241,48 @@ module Hubspot
|
|
106
241
|
}.freeze
|
107
242
|
|
108
243
|
# rubocop:disable Metrics/MethodLength
|
244
|
+
|
245
|
+
# Search for resources using a flexible query format and optional properties.
|
246
|
+
#
|
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
|
+
#
|
251
|
+
# You can also specify which properties to return and the number of results per page.
|
252
|
+
#
|
253
|
+
# Available suffixes for query keys (when using a hash):
|
254
|
+
# - `_contains`: Matches values that contain the given string.
|
255
|
+
# - `_gt`: Greater than comparison.
|
256
|
+
# - `_lt`: Less than comparison.
|
257
|
+
# - `_gte`: Greater than or equal to comparison.
|
258
|
+
# - `_lte`: Less than or equal to comparison.
|
259
|
+
# - `_neq`: Not equal to comparison.
|
260
|
+
# - `_in`: Matches any of the values in the given array.
|
261
|
+
#
|
262
|
+
# If no suffix is provided, the default comparison is equality (`EQ`).
|
263
|
+
#
|
264
|
+
# query - [String, Hash] The query for searching. This can be either:
|
265
|
+
# - A String: for full-text search.
|
266
|
+
# - A Hash: where each key represents a property and may have suffixes for the comparison
|
267
|
+
# (e.g., `{ email_contains: 'example.org', age_gt: 30 }`).
|
268
|
+
# properties - An optional array of property names to return in the search results.
|
269
|
+
# If not specified or empty, HubSpot will return the default set of properties.
|
270
|
+
# page_size - The number of results to return per page
|
271
|
+
# (default is 10 for contacts and 100 for everything else).
|
272
|
+
#
|
273
|
+
# Example Usage:
|
274
|
+
# # Full-text search for 'example.org':
|
275
|
+
# props = %w[email firstname lastname]
|
276
|
+
# contacts = Hubspot::Contact.search(query: "example.org", properties: props, page_size: 50)
|
277
|
+
#
|
278
|
+
# # Search for contacts whose email contains 'example.org' and are older than 30:
|
279
|
+
# contacts = Hubspot::Contact.search(
|
280
|
+
# query: { email_contains: 'example.org', age_gt: 30 },
|
281
|
+
# properties: ["email", "firstname", "lastname"],
|
282
|
+
# page_size: 50
|
283
|
+
# )
|
284
|
+
#
|
285
|
+
# Returns [PagedCollection] A paged collection of results that can be iterated over.
|
109
286
|
def search(query:, properties: [], page_size: 100)
|
110
287
|
search_body = {}
|
111
288
|
|
@@ -127,7 +304,7 @@ module Hubspot
|
|
127
304
|
|
128
305
|
# Perform the search and return a PagedCollection
|
129
306
|
PagedCollection.new(
|
130
|
-
url: "
|
307
|
+
url: "#{api_root}/#{resource_name}/search",
|
131
308
|
params: search_body,
|
132
309
|
resource_class: self,
|
133
310
|
method: :post
|
@@ -136,6 +313,10 @@ module Hubspot
|
|
136
313
|
|
137
314
|
# rubocop:enable Metrics/MethodLength
|
138
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
|
+
|
139
320
|
# Define the resource name based on the class
|
140
321
|
def resource_name
|
141
322
|
name = self.name.split('::').last.downcase
|
@@ -146,8 +327,22 @@ module Hubspot
|
|
146
327
|
end
|
147
328
|
end
|
148
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
|
+
|
149
336
|
private
|
150
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
|
+
|
151
346
|
# Instantiate a single resource object from the response
|
152
347
|
def instantiate_from_response(response)
|
153
348
|
data = handle_response(response)
|
@@ -182,9 +377,36 @@ module Hubspot
|
|
182
377
|
# Default to 'EQ' operator if no suffix is found
|
183
378
|
{ propertyName: key.to_s, operator: 'EQ' }
|
184
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
|
185
389
|
end
|
186
390
|
|
187
|
-
# rubocop:disable
|
391
|
+
# rubocop:disable Lint/MissingSuper
|
392
|
+
|
393
|
+
# Public: Initialize a resouce
|
394
|
+
#
|
395
|
+
# data - [2D Hash, nested Hash] data to initialise the resourse This can be either:
|
396
|
+
# - A Simple 2D Hash, key value pairs of property => value (for the create option)
|
397
|
+
# - A structured hash consisting of { id: <hs_object_id>, properties: {}, ... }
|
398
|
+
# This is the same structure as per the API, and can be rebuilt if you store the id
|
399
|
+
# of the object against your own data
|
400
|
+
#
|
401
|
+
# Example:
|
402
|
+
# attrs = { firstname: 'Luke', lastname: 'Skywalker', email: 'luke@jedi.org' }
|
403
|
+
# contact = Hubspot::Contact.new(attrs)
|
404
|
+
# contact.persisted? # false
|
405
|
+
# contact.save # creates the record in Hubspot
|
406
|
+
# contact.persisted? # true
|
407
|
+
# puts "Contact saved with hubspot id #{contact.id}"
|
408
|
+
#
|
409
|
+
# existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)
|
188
410
|
def initialize(data = {})
|
189
411
|
data.transform_keys!(&:to_s)
|
190
412
|
@id = extract_id(data)
|
@@ -196,13 +418,22 @@ module Hubspot
|
|
196
418
|
initialize_new_object(data)
|
197
419
|
end
|
198
420
|
end
|
199
|
-
# rubocop:enable
|
421
|
+
# rubocop:enable Lint/MissingSuper
|
200
422
|
|
423
|
+
# Determine the state of the object
|
424
|
+
#
|
425
|
+
# Returns Boolean
|
201
426
|
def changes?
|
202
427
|
!@changes.empty?
|
203
428
|
end
|
204
429
|
|
205
|
-
#
|
430
|
+
# Create or Update the resource.
|
431
|
+
# If the resource was already persisted (e.g. it was retrieved from the API)
|
432
|
+
# it will be updated using values from @changes
|
433
|
+
#
|
434
|
+
# If the resource is new (no id) it will be created
|
435
|
+
#
|
436
|
+
# Returns Boolean
|
206
437
|
def save
|
207
438
|
if persisted?
|
208
439
|
self.class.update(@id, @changes).tap do |result|
|
@@ -216,21 +447,57 @@ module Hubspot
|
|
216
447
|
end
|
217
448
|
end
|
218
449
|
|
450
|
+
# If the resource exists in Hubspot
|
451
|
+
#
|
452
|
+
# Returns Boolean
|
219
453
|
def persisted?
|
220
454
|
@id ? true : false
|
221
455
|
end
|
222
456
|
|
223
|
-
# Update the resource
|
224
|
-
|
457
|
+
# Public - Update the resource and persist to the api
|
458
|
+
#
|
459
|
+
# attributes - hash of properties to update in key value pairs
|
460
|
+
#
|
461
|
+
# Example:
|
462
|
+
# contact = Hubspot::Contact.find(hubspot_contact_id)
|
463
|
+
# contact.update(status: 'gold customer', last_contacted_at: Time.now.utc.iso8601)
|
464
|
+
#
|
465
|
+
# Returns Boolean
|
466
|
+
def update(attributes)
|
225
467
|
raise 'Not able to update as not persisted' unless persisted?
|
226
468
|
|
227
|
-
|
228
|
-
send("#{key}=", value) # This will trigger the @changes tracking via method_missing
|
229
|
-
end
|
469
|
+
update_attributes(attributes)
|
230
470
|
|
231
471
|
save
|
232
472
|
end
|
233
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
|
+
|
495
|
+
# Archive the object in Hubspot
|
496
|
+
#
|
497
|
+
# Example:
|
498
|
+
# company = Hubspot::Company.find(hubspot_company_id)
|
499
|
+
# company.delete
|
500
|
+
#
|
234
501
|
def delete
|
235
502
|
self.class.archive(id)
|
236
503
|
end
|
@@ -241,7 +508,11 @@ module Hubspot
|
|
241
508
|
end
|
242
509
|
|
243
510
|
# rubocop:disable Metrics/MethodLength
|
244
|
-
|
511
|
+
|
512
|
+
# getter: Check the properties and changes hashes to see if the method
|
513
|
+
# being called is a key, and return the corresponding value
|
514
|
+
# setter: If the method ends in "=" persist the value in the changes hash
|
515
|
+
# (when it is different from the corresponding value in properties if set)
|
245
516
|
def method_missing(method, *args)
|
246
517
|
method_name = method.to_s
|
247
518
|
|
@@ -267,9 +538,10 @@ module Hubspot
|
|
267
538
|
# Fallback if the method or attribute is not found
|
268
539
|
super
|
269
540
|
end
|
541
|
+
|
270
542
|
# rubocop:enable Metrics/MethodLength
|
271
543
|
|
272
|
-
# Ensure respond_to_missing?
|
544
|
+
# Ensure respond_to_missing? handles existing keys in the properties anc changes hashes
|
273
545
|
def respond_to_missing?(method_name, include_private = false)
|
274
546
|
property_name = method_name.to_s.chomp('=')
|
275
547
|
@properties.key?(property_name) || @changes.key?(property_name) || super
|
@@ -284,9 +556,17 @@ module Hubspot
|
|
284
556
|
|
285
557
|
# Initialize from API response, separating metadata from properties
|
286
558
|
def initialize_from_api(data)
|
287
|
-
|
288
|
-
|
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
|
289
565
|
|
566
|
+
@changes = {}
|
567
|
+
end
|
568
|
+
|
569
|
+
def handle_properties(properties_data)
|
290
570
|
properties_data.each do |key, value|
|
291
571
|
if METADATA_FIELDS.include?(key)
|
292
572
|
@metadata[key] = value
|
@@ -294,8 +574,6 @@ module Hubspot
|
|
294
574
|
@properties[key] = value
|
295
575
|
end
|
296
576
|
end
|
297
|
-
|
298
|
-
@changes = {}
|
299
577
|
end
|
300
578
|
|
301
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/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'
|
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
|
@@ -253,8 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
253
254
|
- !ruby/object:Gem::Version
|
254
255
|
version: '0'
|
255
256
|
requirements: []
|
256
|
-
|
257
|
-
rubygems_version: 2.6.14.4
|
257
|
+
rubygems_version: 3.2.3
|
258
258
|
signing_key:
|
259
259
|
specification_version: 4
|
260
260
|
summary: ruby_hubspot_api is an ORM-like wrapper for the Hubspot API
|