ruby_hubspot_api 0.2.1.1 → 0.3.0
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/.github/workflows/ruby.yml +4 -4
- data/.gitignore +1 -1
- data/.rubocop.yml +17 -4
- data/CHANGELOG.md +72 -22
- data/Gemfile.lock +1 -1
- data/lib/hubspot/api_client.rb +9 -4
- data/lib/hubspot/batch.rb +105 -36
- data/lib/hubspot/contact.rb +63 -0
- data/lib/hubspot/form.rb +28 -0
- data/lib/hubspot/paged_batch.rb +19 -3
- data/lib/hubspot/paged_collection.rb +8 -4
- data/lib/hubspot/resource.rb +367 -59
- data/lib/hubspot/user.rb +25 -1
- data/lib/hubspot/version.rb +1 -1
- data/lib/ruby_hubspot_api.rb +6 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6052dfd71294e988924beeab1b89662673ceaf93
|
4
|
+
data.tar.gz: 4b2e295931295d639e473a9b139df03ec7df1554
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: caee0d8350205b6bcf45bb4ed4b5421f3fdaacaff87acfcb52c4845858bbafb664f70acbca1a564a34a99a166fd1072a838702d4bf4044b076cfd0b77e87f6a7
|
7
|
+
data.tar.gz: 28cbb5b1ea5ebc076d7177e8f4ee35f450bbe7fbb33224a31c55f0801fc566b705cf9b65faf95453b91154675139fb33cfce557ea74e06afd001a09f710190bf
|
data/.github/workflows/ruby.yml
CHANGED
@@ -41,7 +41,7 @@ jobs:
|
|
41
41
|
env:
|
42
42
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
43
43
|
|
44
|
-
- name: Upload coverage to Codacy
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
# - name: Upload coverage to Codacy
|
45
|
+
# run: bash <(curl -Ls https://coverage.codacy.com/get.sh)
|
46
|
+
# env:
|
47
|
+
# CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
data/.gitignore
CHANGED
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/CHANGELOG.md
CHANGED
@@ -1,4 +1,61 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
## v0.3.0
|
3
|
+
|
4
|
+
- Clarify usage
|
5
|
+
- More usage clarification
|
6
|
+
- Create ruby.yml
|
7
|
+
- fix the github workflow
|
8
|
+
- fix the github workflow properly
|
9
|
+
- add plaatforms to Gemfile.lock
|
10
|
+
- add ruby 2.5 and reduce log output
|
11
|
+
- try calling rspec directly
|
12
|
+
- Use ERB in VCR tests so as to be independent of the env vars
|
13
|
+
- ignore ruby version file
|
14
|
+
- test on ruby 3.0 too
|
15
|
+
- determine if a Hubspot property is read_only (or by negation updatable)
|
16
|
+
- adding codecov
|
17
|
+
- Using earlier bundler
|
18
|
+
- Adding lcov format
|
19
|
+
- Add read-only properties to resource class
|
20
|
+
- Add property check to the contact spec
|
21
|
+
- Only apply lcov formatter if running on github
|
22
|
+
- try to upload the coverage results to codacy too
|
23
|
+
- Adding Codacy badge
|
24
|
+
- Ensure we always apply the right log_level
|
25
|
+
- Tidy up documentation of resource
|
26
|
+
- Yep. Back ported to 2.4
|
27
|
+
- adjust some rubocop settings
|
28
|
+
- Tidy up somer doc comments
|
29
|
+
- Add some handling of required properties
|
30
|
+
- Update user model to force specific properties to be retrieved
|
31
|
+
- Improve logic of resource matching
|
32
|
+
- Adds :sparkle: attributes method to a resource
|
33
|
+
- Tidy comments
|
34
|
+
- Update the hierarchy to allow more flexibility
|
35
|
+
- Fix find resources
|
36
|
+
- Clear up processing results
|
37
|
+
- Update batch spec
|
38
|
+
- Bump the version
|
39
|
+
- Adds validation for resource matcher
|
40
|
+
- Dynamically add a method to batch to allow "resources" to be referred to as the resource_name
|
41
|
+
- Ignore gem files
|
42
|
+
- Drop cadacy for now
|
43
|
+
- Update batch spec to check resource_matcher works
|
44
|
+
- Ensure properties are passed as named argument
|
45
|
+
- No cov for inspect
|
46
|
+
- Allow ERB in json fixtures
|
47
|
+
- Contact find_by_token spec
|
48
|
+
- find_by_token method - uses v1 API
|
49
|
+
- Tests the method missing setter for resource
|
50
|
+
- Sanitize web mock output
|
51
|
+
- Finish specs
|
52
|
+
- check the env vars before sanitising
|
53
|
+
- make erb explicitly determined by the file exension (.json.erb)
|
54
|
+
- use safe navigation for extracting id
|
55
|
+
- bump version
|
56
|
+
- update gem version in lock file
|
57
|
+
|
58
|
+
## v0.2.0
|
2
59
|
|
3
60
|
- Get the development dependencies right!
|
4
61
|
- Bump the version again
|
@@ -21,11 +78,23 @@
|
|
21
78
|
- Simplify mocked responses in batch spec
|
22
79
|
- Adds PagedBatch as pager for batch/read request
|
23
80
|
- Update the Readme to add Batch operations
|
24
|
-
-
|
81
|
+
- bump version
|
25
82
|
|
26
83
|
## v0.1.2
|
27
84
|
|
28
|
-
-
|
85
|
+
- Fix the Readme
|
86
|
+
- Sure the search param is values where passing an array
|
87
|
+
- update changelog and Gemfile.lock
|
88
|
+
- bump version
|
89
|
+
|
90
|
+
## v0.1.1
|
91
|
+
|
92
|
+
- adds the version numbers to the gemspec
|
93
|
+
- Fix dependencies
|
94
|
+
- bump version
|
95
|
+
|
96
|
+
## v0.1.0
|
97
|
+
|
29
98
|
- Setup the configuration block
|
30
99
|
- Adds spec for config
|
31
100
|
- Set the auth headers when access_token configured
|
@@ -67,23 +136,4 @@
|
|
67
136
|
- Flatten the properties array into a comma separated list
|
68
137
|
- Improve the intialiser
|
69
138
|
- Update the changeling and link in gem spec
|
70
|
-
- adds the version numbers to the gemspec
|
71
|
-
- Fix dependencies
|
72
|
-
- bump version
|
73
|
-
- Fix the Readme
|
74
|
-
- Sure the search param is values where passing an array
|
75
|
-
- update changelog and Gemfile.lock
|
76
|
-
- bump version
|
77
|
-
|
78
|
-
## v0.1.1
|
79
|
-
|
80
|
-
- Fix the Readme
|
81
|
-
- Sure the search param is values where passing an array
|
82
|
-
- update changelog and Gemfile.lock
|
83
|
-
- bump version
|
84
139
|
|
85
|
-
## v0.1.0
|
86
|
-
|
87
|
-
- adds the version numbers to the gemspec
|
88
|
-
- Fix dependencies
|
89
|
-
- bump version
|
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,20 @@ module Hubspot
|
|
31
31
|
CONTACT_LIMIT = 10
|
32
32
|
DEFAULT_LIMIT = 100
|
33
33
|
|
34
|
+
# :nocov:
|
35
|
+
def inspect
|
36
|
+
"#<#{self.class.name} " \
|
37
|
+
"@resource_count=#{@resources.size}, " \
|
38
|
+
"@id_property=#{@id_property.inspect}, " \
|
39
|
+
"@resource_type=#{@resources.first&.resource_name}, " \
|
40
|
+
"@responses_count=#{@responses.size}>"
|
41
|
+
end
|
42
|
+
# :nocov:
|
43
|
+
|
34
44
|
# rubocop:disable Lint/MissingSuper
|
35
|
-
def initialize(resources = [], id_property: 'id')
|
45
|
+
def initialize(resources = [], id_property: 'id', resource_matcher: nil)
|
46
|
+
validate_resource_matcher(resource_matcher)
|
47
|
+
|
36
48
|
@resources = []
|
37
49
|
@id_property = id_property # Set id_property for the batch (default: 'id')
|
38
50
|
@responses = [] # Store multiple BatchResponse objects here
|
@@ -51,11 +63,23 @@ module Hubspot
|
|
51
63
|
end
|
52
64
|
|
53
65
|
# Upsert method that calls save with upsert action
|
54
|
-
def upsert
|
66
|
+
def upsert(resource_matcher: nil)
|
67
|
+
validate_resource_matcher(resource_matcher)
|
68
|
+
|
55
69
|
validate_upsert_conditions
|
56
70
|
save(action: 'upsert')
|
57
71
|
end
|
58
72
|
|
73
|
+
def validate_resource_matcher(resource_matcher)
|
74
|
+
return if resource_matcher.blank?
|
75
|
+
|
76
|
+
unless resource_matcher.is_a?(Proc) && resource_matcher.arity == 2
|
77
|
+
raise ArgumentError, 'resource_matcher must be a proc that accepts exactly 2 arguments'
|
78
|
+
end
|
79
|
+
|
80
|
+
@resource_matcher = resource_matcher
|
81
|
+
end
|
82
|
+
|
59
83
|
# Archive method
|
60
84
|
def archive
|
61
85
|
save(action: 'archive')
|
@@ -77,15 +101,29 @@ module Hubspot
|
|
77
101
|
end
|
78
102
|
|
79
103
|
def add_resource(resource)
|
80
|
-
if @resources.any?
|
81
|
-
|
104
|
+
if @resources.any?
|
105
|
+
if @resources.first.resource_name != resource.resource_name
|
106
|
+
raise ArgumentError, 'All resources in a batch must be of the same type'
|
107
|
+
end
|
108
|
+
else
|
109
|
+
add_resource_method(resource.resource_name)
|
82
110
|
end
|
83
111
|
|
84
112
|
@resources << resource
|
85
113
|
end
|
86
114
|
|
115
|
+
def any_changes?
|
116
|
+
@resources.any?(&:changes?)
|
117
|
+
end
|
118
|
+
|
87
119
|
private
|
88
120
|
|
121
|
+
def add_resource_method(resource_name)
|
122
|
+
self.class.class_eval do
|
123
|
+
alias_method resource_name.to_sym, :resources
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
89
127
|
# rubocop:disable Metrics/MethodLength
|
90
128
|
def save(action: 'update')
|
91
129
|
@action = action
|
@@ -122,9 +160,12 @@ module Hubspot
|
|
122
160
|
next if resource.changes.empty?
|
123
161
|
|
124
162
|
{
|
125
|
-
|
126
|
-
|
127
|
-
|
163
|
+
# Dynamically get the ID based on the batch's id_property
|
164
|
+
id: resource.public_send(@id_property),
|
165
|
+
# Use the helper method to decide whether to include idProperty
|
166
|
+
idProperty: determine_id_property,
|
167
|
+
# Gather the changes for the resource
|
168
|
+
properties: resource.changes
|
128
169
|
}.compact # Removes nil keys
|
129
170
|
end.compact # Removes nil entries
|
130
171
|
end
|
@@ -146,7 +187,8 @@ module Hubspot
|
|
146
187
|
|
147
188
|
# Perform batch request based on the provided action (upsert, update, create, or archive)
|
148
189
|
def batch_request(type, inputs, action)
|
149
|
-
response = self.class.post("/crm/v3/objects/#{type}/batch/#{action}",
|
190
|
+
response = self.class.post("/crm/v3/objects/#{type}/batch/#{action}",
|
191
|
+
body: { inputs: inputs }.to_json)
|
150
192
|
BatchResponse.new(response.code, handle_response(response))
|
151
193
|
end
|
152
194
|
|
@@ -157,7 +199,8 @@ module Hubspot
|
|
157
199
|
# check if there are any resources without a value from the id_property
|
158
200
|
return unless @resources.any? { |resource| resource.public_send(id_property).blank? }
|
159
201
|
|
160
|
-
raise ArgumentError,
|
202
|
+
raise ArgumentError,
|
203
|
+
"All resources must have a non-blank value for #{@id_property} to perform upsert"
|
161
204
|
end
|
162
205
|
|
163
206
|
# Return the appropriate batch size limit for the resource type
|
@@ -167,6 +210,8 @@ module Hubspot
|
|
167
210
|
|
168
211
|
# Process responses from the batch API call
|
169
212
|
def process_responses
|
213
|
+
# TODO: issue a warning if the id_property is email and the action is upsert*
|
214
|
+
# people may have more than one email address abd Hubspot views that as one record
|
170
215
|
@responses.each do |response|
|
171
216
|
next unless response['results']
|
172
217
|
|
@@ -192,36 +237,56 @@ module Hubspot
|
|
192
237
|
end
|
193
238
|
|
194
239
|
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
|
240
|
+
action_method = method_for_action
|
241
|
+
send(action_method, result) if action_method
|
242
|
+
end
|
243
|
+
|
244
|
+
def method_for_action
|
245
|
+
{
|
246
|
+
'create' => :find_resource_for_created_result,
|
247
|
+
'update' => :find_resource_from_updated_result,
|
248
|
+
'upsert' => :find_resource_from_upserted_result
|
249
|
+
}[@action]
|
250
|
+
end
|
251
|
+
|
252
|
+
def find_resource_for_created_result(result)
|
253
|
+
properties = result['properties']
|
254
|
+
|
255
|
+
@resources.reject(&:persisted?).find do |resource|
|
256
|
+
next unless resource.changes.any?
|
257
|
+
|
258
|
+
resource.changes.all? { |key, value| properties[key.to_s] == value }
|
215
259
|
end
|
216
260
|
end
|
217
261
|
|
262
|
+
def find_resource_from_updated_result(result)
|
263
|
+
resource_id = id_property == 'id' ? result['id'].to_i : result.dig('properties', id_property)
|
264
|
+
find_resource_from_id(resource_id)
|
265
|
+
end
|
266
|
+
|
218
267
|
def find_resource_from_id(resource_id)
|
219
|
-
|
268
|
+
return find_resource_from_id_property(resource_id) unless @id_property == 'id'
|
269
|
+
|
270
|
+
@resources.find { |resource| resource.id == resource_id }
|
271
|
+
end
|
272
|
+
|
273
|
+
def find_resource_from_id_property(resource_id)
|
274
|
+
@resources.find do |resource|
|
275
|
+
resource.respond_to?(@id_property) && resource.public_send(@id_property) == resource_id
|
276
|
+
end
|
220
277
|
end
|
221
278
|
|
222
|
-
|
223
|
-
|
224
|
-
|
279
|
+
def find_resource_from_upserted_result(result)
|
280
|
+
# if this was inserted then match on all the fields
|
281
|
+
return find_resource_for_created_result(result['properties']) if result['new']
|
282
|
+
|
283
|
+
# call the custom resource matcher if specified
|
284
|
+
if @resource_matcher
|
285
|
+
@resources.find { |resource| @resource_matcher.call(resource, result) }
|
286
|
+
else
|
287
|
+
find_resource_from_updated_result(result)
|
288
|
+
end
|
289
|
+
end
|
225
290
|
|
226
291
|
def update_resource_properties(resource, properties)
|
227
292
|
properties.each do |key, value|
|
@@ -237,11 +302,15 @@ module Hubspot
|
|
237
302
|
end
|
238
303
|
|
239
304
|
class << self
|
240
|
-
def read(object_class, object_ids = [], id_property: 'id')
|
241
|
-
|
305
|
+
def read(object_class, object_ids = [], properties: [], id_property: 'id')
|
306
|
+
unless object_class < Hubspot::Resource
|
307
|
+
raise ArgumentError, 'Must be a valid Hubspot resource class'
|
308
|
+
end
|
242
309
|
|
243
310
|
# fetch all the matching resources with paging handled
|
244
|
-
resources = object_class.batch_read(object_ids,
|
311
|
+
resources = object_class.batch_read(object_ids,
|
312
|
+
properties: properties,
|
313
|
+
id_property: id_property).all
|
245
314
|
|
246
315
|
# return instance of Hubspot::Batch with the resources set
|
247
316
|
new(resources, id_property: id_property)
|
data/lib/hubspot/contact.rb
CHANGED
@@ -2,5 +2,68 @@
|
|
2
2
|
|
3
3
|
module Hubspot
|
4
4
|
class Contact < Resource
|
5
|
+
# def required_properties
|
6
|
+
# %w[email firstname lastname]
|
7
|
+
# end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def metadata_field?(key)
|
12
|
+
METADATA_FIELDS.include?(key) || key.start_with?('hs_')
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Finds a contact by the hubspotutk cookie
|
17
|
+
#
|
18
|
+
# token - the hubspot tracking token (stored from the hubspotutk cookie value)
|
19
|
+
# properties: - Optional list of properties to return.
|
20
|
+
# Note: If properties are specified 2 calls to the api will be made because
|
21
|
+
# at this time you can only search by the token using the v1 api
|
22
|
+
# from which we
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
# properties = %w[firstname lastname email last_contacted]
|
26
|
+
# contact = Hubspot::Contact.find_by_token(hubspotutk_cookie_value, properties)
|
27
|
+
#
|
28
|
+
# Returns An instance of the resource.
|
29
|
+
def find_by_token(token, properties: [])
|
30
|
+
all_properties = build_property_list(properties)
|
31
|
+
query_props = all_properties.map { |prop| "property=#{prop}" }
|
32
|
+
query_string = query_props.concat(['propertyMode=value_only']).join('&')
|
33
|
+
|
34
|
+
# Make the original API request, manually appending the query string
|
35
|
+
response = get("/contacts/v1/contact/utk/#{token}/profile?#{query_string}")
|
36
|
+
|
37
|
+
# Only modify the response if it's successful (status 200 OK)
|
38
|
+
if response.success?
|
39
|
+
# Convert the v1 response body (parsed_response) to a v3 structure
|
40
|
+
v3_response_hash = convert_v1_response(response.parsed_response, all_properties)
|
41
|
+
|
42
|
+
# Modify the existing response object by updating its `parsed_response`
|
43
|
+
response.instance_variable_set(:@parsed_response, v3_response_hash)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Pass the (potentially modified) HTTParty response to the next step
|
47
|
+
instantiate_from_response(response)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def convert_v1_response(v1_response, property_list)
|
53
|
+
# Extract the `vid` as `id`
|
54
|
+
v3_response = {
|
55
|
+
'id' => v1_response['vid']
|
56
|
+
}
|
57
|
+
|
58
|
+
properties = property_list.each_with_object({}) do |property, hash|
|
59
|
+
hash[property] = v1_response.dig('properties', property, 'value')
|
60
|
+
end
|
61
|
+
|
62
|
+
# Build the v3 structure
|
63
|
+
v3_response['properties'] = properties
|
64
|
+
|
65
|
+
v3_response
|
66
|
+
end
|
67
|
+
end
|
5
68
|
end
|
6
69
|
end
|
data/lib/hubspot/form.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hubspot
|
4
|
+
class Form < Resource
|
5
|
+
METADATA_FIELDS = %w[createdAt updatedAt archived].freeze
|
6
|
+
|
7
|
+
# :nocov:
|
8
|
+
def inspect
|
9
|
+
"#<#{self.class.name} " \
|
10
|
+
"@name=#{name}, " \
|
11
|
+
"@fieldGroups=#{respond_to?('fieldGroups') ? fieldGroups.size : '-'}>"
|
12
|
+
end
|
13
|
+
# :nocov:
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def api_root
|
17
|
+
'/marketing/v3'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# dont convert (from string)
|
24
|
+
def extract_id(id)
|
25
|
+
id
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/hubspot/paged_batch.rb
CHANGED
@@ -10,6 +10,16 @@ module Hubspot
|
|
10
10
|
|
11
11
|
MAX_LIMIT = 100 # HubSpot max items per page
|
12
12
|
|
13
|
+
# :nocov:
|
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
|
+
# :nocov:
|
22
|
+
|
13
23
|
# rubocop:disable Lint/MissingSuper
|
14
24
|
def initialize(url:, params: {}, resource_class: nil, object_ids: [])
|
15
25
|
@url = url
|
@@ -22,8 +32,7 @@ module Hubspot
|
|
22
32
|
def each_page
|
23
33
|
@object_ids.each_slice(MAX_LIMIT) do |ids|
|
24
34
|
response = fetch_page(ids)
|
25
|
-
|
26
|
-
mapped_results = @resource_class ? results.map { |result| @resource_class.new(result) } : results
|
35
|
+
mapped_results = process_results(response)
|
27
36
|
yield mapped_results unless mapped_results.empty?
|
28
37
|
end
|
29
38
|
end
|
@@ -45,12 +54,19 @@ module Hubspot
|
|
45
54
|
private
|
46
55
|
|
47
56
|
def fetch_page(object_ids)
|
48
|
-
params_with_ids = @params.dup
|
57
|
+
params_with_ids = @params.dup || {}
|
49
58
|
params_with_ids[:inputs] = object_ids.map { |id| { id: id } }
|
50
59
|
|
51
60
|
response = self.class.post(@url, body: params_with_ids.to_json)
|
52
61
|
|
53
62
|
handle_response(response)
|
54
63
|
end
|
64
|
+
|
65
|
+
def process_results(response)
|
66
|
+
results = response['results'] || []
|
67
|
+
return results unless @resource_class
|
68
|
+
|
69
|
+
results.map { |result| @resource_class.new(result) }
|
70
|
+
end
|
55
71
|
end
|
56
72
|
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,190 @@ 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
|
+
# properties - an array of property names to fetch in the result
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
# contact = Hubspot::Contact.find(1)
|
52
|
+
# contact = Hubspot::Contact.find(1, properties: %w[email firstname lastname custom_field])
|
53
|
+
#
|
54
|
+
# Returns An instance of the resource.
|
55
|
+
def find(id, properties: nil)
|
56
|
+
all_properties = build_property_list(properties)
|
57
|
+
if all_properties.is_a?(Array) && !all_properties.empty?
|
58
|
+
params = { query: { properties: all_properties } }
|
59
|
+
end
|
60
|
+
response = get("#{api_root}/#{resource_name}/#{id}", params || {})
|
20
61
|
instantiate_from_response(response)
|
21
62
|
end
|
22
63
|
|
64
|
+
# Finds a resource by a given property and value.
|
65
|
+
#
|
66
|
+
# property - The property to search by (e.g., "email").
|
67
|
+
# value - The value of the property to match.
|
68
|
+
# properties - Optional list of properties to return.
|
69
|
+
#
|
70
|
+
# Example:
|
71
|
+
# properties = %w[firstname lastname email last_contacted]
|
72
|
+
# contact = Hubspot::Contact.find_by("email", "john@example.com", properties)
|
73
|
+
#
|
74
|
+
# Returns An instance of the resource.
|
23
75
|
def find_by(property, value, properties = nil)
|
24
76
|
params = { idProperty: property }
|
25
|
-
|
26
|
-
|
77
|
+
|
78
|
+
all_properties = build_property_list(properties)
|
79
|
+
params[:properties] = all_properties unless all_properties.empty?
|
80
|
+
|
81
|
+
response = get("#{api_root}/#{resource_name}/#{value}", query: params)
|
27
82
|
instantiate_from_response(response)
|
28
83
|
end
|
29
84
|
|
30
|
-
#
|
85
|
+
# Creates a new resource with the given parameters.
|
86
|
+
#
|
87
|
+
# params - The properties to create the resource with.
|
88
|
+
#
|
89
|
+
# Example:
|
90
|
+
# contact = Hubspot::Contact.create(name: "John Doe", email: "john@example.com")
|
91
|
+
#
|
92
|
+
# Returns [Resource] The newly created resource.
|
31
93
|
def create(params)
|
32
|
-
response = post("
|
94
|
+
response = post("#{api_root}/#{resource_name}", body: { properties: params }.to_json)
|
33
95
|
instantiate_from_response(response)
|
34
96
|
end
|
35
97
|
|
98
|
+
# Updates an existing resource by ID.
|
99
|
+
#
|
100
|
+
# id - The ID of the resource to update.
|
101
|
+
# params - The properties to update.
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
# contact.update(1, name: "Jane Doe")
|
105
|
+
#
|
106
|
+
# Returns True if the update was successful
|
36
107
|
def update(id, params)
|
37
|
-
response = patch("
|
38
|
-
|
108
|
+
response = patch("#{api_root}/#{resource_name}/#{id}",
|
109
|
+
body: { properties: params }.to_json)
|
110
|
+
handle_response(response)
|
39
111
|
|
40
112
|
true
|
41
113
|
end
|
42
114
|
|
115
|
+
# Deletes a resource by ID.
|
116
|
+
#
|
117
|
+
# id - The ID of the resource to delete.
|
118
|
+
#
|
119
|
+
# Example:
|
120
|
+
# Hubspot::Contact.archive(1)
|
121
|
+
#
|
122
|
+
# Returns True if the deletion was successful
|
43
123
|
def archive(id)
|
44
|
-
response = delete("
|
45
|
-
|
124
|
+
response = delete("#{api_root}/#{resource_name}/#{id}")
|
125
|
+
handle_response(response)
|
46
126
|
|
47
127
|
true
|
48
128
|
end
|
49
129
|
|
130
|
+
# Lists all resources with optional filters and pagination.
|
131
|
+
#
|
132
|
+
# params - Optional parameters to filter or paginate the results.
|
133
|
+
#
|
134
|
+
# Example:
|
135
|
+
# contacts = Hubspot::Contact.list(limit: 100)
|
136
|
+
#
|
137
|
+
# Returns [PagedCollection] A collection of resources.
|
50
138
|
def list(params = {})
|
139
|
+
all_properties = build_property_list(params[:properties])
|
140
|
+
|
141
|
+
if all_properties.is_a?(Array) && !all_properties.empty?
|
142
|
+
params[:properties] = all_properties.join(',')
|
143
|
+
end
|
144
|
+
|
51
145
|
PagedCollection.new(
|
52
|
-
url:
|
146
|
+
url: list_page_uri,
|
53
147
|
params: params,
|
54
148
|
resource_class: self
|
55
149
|
)
|
56
150
|
end
|
57
151
|
|
58
|
-
|
59
|
-
|
152
|
+
# Performs a batch read operation to retrieve multiple resources by their IDs.
|
153
|
+
#
|
154
|
+
# object_ids - A list of resource IDs to fetch.
|
155
|
+
#
|
156
|
+
# id_property - The property to use for identifying resources (default: 'id').
|
157
|
+
#
|
158
|
+
#
|
159
|
+
# Example:
|
160
|
+
# Hubspot::Contact.batch_read([1, 2, 3])
|
161
|
+
#
|
162
|
+
# Returns [PagedBatch] A paged batch of resources
|
163
|
+
def batch_read(object_ids = [], properties: [], id_property: 'id')
|
164
|
+
params = {}
|
165
|
+
params[:idProperty] = id_property unless id_property == 'id'
|
166
|
+
params[:properties] = properties unless properties.blank?
|
60
167
|
|
61
168
|
PagedBatch.new(
|
62
|
-
url: "
|
63
|
-
params: params,
|
169
|
+
url: "#{api_root}/#{resource_name}/batch/read",
|
170
|
+
params: params.empty? ? nil : params,
|
64
171
|
object_ids: object_ids,
|
65
172
|
resource_class: self
|
66
173
|
)
|
67
174
|
end
|
68
175
|
|
69
|
-
|
70
|
-
|
176
|
+
# Performs a batch read operation to retrieve multiple resources by their IDs
|
177
|
+
# until there are none left
|
178
|
+
#
|
179
|
+
# object_ids - A list of resource IDs to fetch. [Array<Integer>]
|
180
|
+
# id_property - The property to use for identifying resources (default: 'id').
|
181
|
+
#
|
182
|
+
# Example:
|
183
|
+
# Hubspot::Contact.batch_read_all(hubspot_contact_ids)
|
184
|
+
#
|
185
|
+
# Returns [Hubspot::Batch] A batch of resources that can be operated on further
|
186
|
+
def batch_read_all(object_ids = [], properties: [], id_property: 'id')
|
187
|
+
Hubspot::Batch.read(self, object_ids, properties: properties, id_property: id_property)
|
71
188
|
end
|
72
189
|
|
73
|
-
#
|
190
|
+
# Retrieve the complete list of properties for this resource class
|
191
|
+
#
|
192
|
+
# Returns [Array<Hubspot::Property>] An array of hubspot properties
|
74
193
|
def properties
|
75
194
|
@properties ||= begin
|
76
195
|
response = get("/crm/v3/properties/#{resource_name}")
|
@@ -78,18 +197,36 @@ module Hubspot
|
|
78
197
|
end
|
79
198
|
end
|
80
199
|
|
200
|
+
# Retrieve the complete list of user defined properties for this resource class
|
201
|
+
#
|
202
|
+
# Returns [Array<Hubspot::Property>] An array of hubspot properties
|
81
203
|
def custom_properties
|
82
204
|
properties.reject { |property| property['hubspotDefined'] }
|
83
205
|
end
|
84
206
|
|
207
|
+
# Retrieve the complete list of updatable properties for this resource class
|
208
|
+
#
|
209
|
+
# Returns [Array<Hubspot::Property>] An array of updateable hubspot properties
|
85
210
|
def updatable_properties
|
86
211
|
properties.reject(&:read_only?)
|
87
212
|
end
|
88
213
|
|
214
|
+
# Retrieve the complete list of read-only properties for this resource class
|
215
|
+
#
|
216
|
+
# Returns [Array<Hubspot::Property>] An array of read-only hubspot properties
|
89
217
|
def read_only_properties
|
90
|
-
properties.select(&:read_only
|
218
|
+
properties.select(&:read_only)
|
91
219
|
end
|
92
220
|
|
221
|
+
# Retrieve information about a specific property
|
222
|
+
#
|
223
|
+
# Example:
|
224
|
+
# property = Hubspot::Contact.property('industry_sector')
|
225
|
+
# values_for_select = property.options.each_with_object({}) do |prop, hash|
|
226
|
+
# hash[prop['value']] = prop['label']
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# Returns [Hubspot::Property] A hubspot property
|
93
230
|
def property(property_name)
|
94
231
|
properties.detect { |prop| prop.name == property_name }
|
95
232
|
end
|
@@ -106,6 +243,48 @@ module Hubspot
|
|
106
243
|
}.freeze
|
107
244
|
|
108
245
|
# rubocop:disable Metrics/MethodLength
|
246
|
+
|
247
|
+
# Search for resources using a flexible query format and optional properties.
|
248
|
+
#
|
249
|
+
# This method allows searching for resources by passing a query in the form of a string
|
250
|
+
# (for full-text search) or a hash with special suffixes on the keys to
|
251
|
+
# define different comparison operators.
|
252
|
+
#
|
253
|
+
# You can also specify which properties to return and the number of results per page.
|
254
|
+
#
|
255
|
+
# Available suffixes for query keys (when using a hash):
|
256
|
+
# - `_contains`: Matches values that contain the given string.
|
257
|
+
# - `_gt`: Greater than comparison.
|
258
|
+
# - `_lt`: Less than comparison.
|
259
|
+
# - `_gte`: Greater than or equal to comparison.
|
260
|
+
# - `_lte`: Less than or equal to comparison.
|
261
|
+
# - `_neq`: Not equal to comparison.
|
262
|
+
# - `_in`: Matches any of the values in the given array.
|
263
|
+
#
|
264
|
+
# If no suffix is provided, the default comparison is equality (`EQ`).
|
265
|
+
#
|
266
|
+
# query - [String, Hash] The query for searching. This can be either:
|
267
|
+
# - A String: for full-text search.
|
268
|
+
# - A Hash: where each key represents a property and may have suffixes for the comparison
|
269
|
+
# (e.g., `{ email_contains: 'example.org', age_gt: 30 }`).
|
270
|
+
# properties - An optional array of property names to return in the search results.
|
271
|
+
# If not specified or empty, HubSpot will return the default set of properties.
|
272
|
+
# page_size - The number of results to return per page
|
273
|
+
# (default is 10 for contacts and 100 for everything else).
|
274
|
+
#
|
275
|
+
# Example Usage:
|
276
|
+
# # Full-text search for 'example.org':
|
277
|
+
# props = %w[email firstname lastname]
|
278
|
+
# contacts = Hubspot::Contact.search(query: "example.org", properties: props, page_size: 50)
|
279
|
+
#
|
280
|
+
# # Search for contacts whose email contains 'example.org' and are older than 30:
|
281
|
+
# contacts = Hubspot::Contact.search(
|
282
|
+
# query: { email_contains: 'example.org', age_gt: 30 },
|
283
|
+
# properties: ["email", "firstname", "lastname"],
|
284
|
+
# page_size: 50
|
285
|
+
# )
|
286
|
+
#
|
287
|
+
# Returns [PagedCollection] A paged collection of results that can be iterated over.
|
109
288
|
def search(query:, properties: [], page_size: 100)
|
110
289
|
search_body = {}
|
111
290
|
|
@@ -127,7 +306,7 @@ module Hubspot
|
|
127
306
|
|
128
307
|
# Perform the search and return a PagedCollection
|
129
308
|
PagedCollection.new(
|
130
|
-
url: "
|
309
|
+
url: "#{api_root}/#{resource_name}/search",
|
131
310
|
params: search_body,
|
132
311
|
resource_class: self,
|
133
312
|
method: :post
|
@@ -136,6 +315,10 @@ module Hubspot
|
|
136
315
|
|
137
316
|
# rubocop:enable Metrics/MethodLength
|
138
317
|
|
318
|
+
# The root of the api call. Mostly this will be "crm"
|
319
|
+
# but you can override this to account for a different
|
320
|
+
# object hierarchy
|
321
|
+
|
139
322
|
# Define the resource name based on the class
|
140
323
|
def resource_name
|
141
324
|
name = self.name.split('::').last.downcase
|
@@ -146,8 +329,22 @@ module Hubspot
|
|
146
329
|
end
|
147
330
|
end
|
148
331
|
|
332
|
+
# List of properties that will always be retrieved
|
333
|
+
# should be overridden in specific resource class
|
334
|
+
def required_properties
|
335
|
+
[]
|
336
|
+
end
|
337
|
+
|
149
338
|
private
|
150
339
|
|
340
|
+
def api_root
|
341
|
+
'/crm/v3/objects'
|
342
|
+
end
|
343
|
+
|
344
|
+
def list_page_uri
|
345
|
+
"#{api_root}/#{resource_name}"
|
346
|
+
end
|
347
|
+
|
151
348
|
# Instantiate a single resource object from the response
|
152
349
|
def instantiate_from_response(response)
|
153
350
|
data = handle_response(response)
|
@@ -182,12 +379,44 @@ module Hubspot
|
|
182
379
|
# Default to 'EQ' operator if no suffix is found
|
183
380
|
{ propertyName: key.to_s, operator: 'EQ' }
|
184
381
|
end
|
382
|
+
|
383
|
+
# Internal make a list of properties to request from the API
|
384
|
+
# will be merged with any required_properties defined on the class
|
385
|
+
def build_property_list(properties)
|
386
|
+
properties = [] unless properties.is_a?(Array)
|
387
|
+
raise 'Must be an array' unless required_properties.is_a?(Array)
|
388
|
+
|
389
|
+
properties.concat(required_properties).uniq
|
390
|
+
end
|
185
391
|
end
|
186
392
|
|
187
|
-
# rubocop:disable
|
393
|
+
# rubocop:disable Lint/MissingSuper
|
394
|
+
|
395
|
+
# Public: Initialize a resouce
|
396
|
+
#
|
397
|
+
# data - [2D Hash, nested Hash] data to initialise:
|
398
|
+
# - The response from the api will be of the form:
|
399
|
+
# { id: <hs_object_id>, properties: { "email": "john@example.org" ... }, ... }
|
400
|
+
#
|
401
|
+
# - A Simple 2D Hash, key value pairs in the form:
|
402
|
+
# { email: 'john@example.org', firstname: 'John', lastname: 'Smith' }
|
403
|
+
#
|
404
|
+
# - A structured hash consisting of { id: <hs_object_id>, properties: {}, ... }
|
405
|
+
# This is the same structure as per the API, and can be rebuilt if you store the id
|
406
|
+
# of the object against your own data
|
407
|
+
#
|
408
|
+
# Example:
|
409
|
+
# attrs = { firstname: 'Luke', lastname: 'Skywalker', email: 'luke@jedi.org' }
|
410
|
+
# contact = Hubspot::Contact.new(attrs)
|
411
|
+
# contact.persisted? # false
|
412
|
+
# contact.save # creates the record in Hubspot
|
413
|
+
# contact.persisted? # true
|
414
|
+
# puts "Contact saved with hubspot id #{contact.id}"
|
415
|
+
#
|
416
|
+
# existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)
|
188
417
|
def initialize(data = {})
|
189
418
|
data.transform_keys!(&:to_s)
|
190
|
-
@id = extract_id(data)
|
419
|
+
@id = extract_id(data.delete('id'))
|
191
420
|
@properties = {}
|
192
421
|
@metadata = {}
|
193
422
|
if @id
|
@@ -196,13 +425,22 @@ module Hubspot
|
|
196
425
|
initialize_new_object(data)
|
197
426
|
end
|
198
427
|
end
|
199
|
-
# rubocop:enable
|
428
|
+
# rubocop:enable Lint/MissingSuper
|
200
429
|
|
430
|
+
# Determine the state of the object
|
431
|
+
#
|
432
|
+
# Returns Boolean
|
201
433
|
def changes?
|
202
434
|
!@changes.empty?
|
203
435
|
end
|
204
436
|
|
205
|
-
#
|
437
|
+
# Create or Update the resource.
|
438
|
+
# If the resource was already persisted (e.g. it was retrieved from the API)
|
439
|
+
# it will be updated using values from @changes
|
440
|
+
#
|
441
|
+
# If the resource is new (no id) it will be created
|
442
|
+
#
|
443
|
+
# Returns Boolean
|
206
444
|
def save
|
207
445
|
if persisted?
|
208
446
|
self.class.update(@id, @changes).tap do |result|
|
@@ -216,21 +454,57 @@ module Hubspot
|
|
216
454
|
end
|
217
455
|
end
|
218
456
|
|
457
|
+
# If the resource exists in Hubspot
|
458
|
+
#
|
459
|
+
# Returns Boolean
|
219
460
|
def persisted?
|
220
461
|
@id ? true : false
|
221
462
|
end
|
222
463
|
|
223
|
-
# Update the resource
|
224
|
-
|
464
|
+
# Public - Update the resource and persist to the api
|
465
|
+
#
|
466
|
+
# attributes - hash of properties to update in key value pairs
|
467
|
+
#
|
468
|
+
# Example:
|
469
|
+
# contact = Hubspot::Contact.find(hubspot_contact_id)
|
470
|
+
# contact.update(status: 'gold customer', last_contacted_at: Time.now.utc.iso8601)
|
471
|
+
#
|
472
|
+
# Returns Boolean
|
473
|
+
def update(attributes)
|
225
474
|
raise 'Not able to update as not persisted' unless persisted?
|
226
475
|
|
227
|
-
|
228
|
-
send("#{key}=", value) # This will trigger the @changes tracking via method_missing
|
229
|
-
end
|
476
|
+
update_attributes(attributes)
|
230
477
|
|
231
478
|
save
|
232
479
|
end
|
233
480
|
|
481
|
+
# Public - Update resource attributes
|
482
|
+
#
|
483
|
+
# Does not persist to the api but processes each attribute correctly
|
484
|
+
#
|
485
|
+
# Example:
|
486
|
+
# contact = Hubspot::Contact.find(hubspot_contact_id)
|
487
|
+
# contact.changes? # false
|
488
|
+
# contact.update_attributes(education: 'Graduate', university: 'Life')
|
489
|
+
# contact.education # Graduate
|
490
|
+
# contact.changes? # true
|
491
|
+
# contact.changes # { "education" => "Graduate", "university" => "Life" }
|
492
|
+
#
|
493
|
+
# Returns Hash of changes
|
494
|
+
def update_attributes(attributes)
|
495
|
+
raise ArgumentError, 'must be a hash' unless attributes.is_a?(Hash)
|
496
|
+
|
497
|
+
attributes.each do |key, value|
|
498
|
+
send("#{key}=", value) # This will trigger the @changes tracking via method_missing
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
# Archive the object in Hubspot
|
503
|
+
#
|
504
|
+
# Example:
|
505
|
+
# company = Hubspot::Company.find(hubspot_company_id)
|
506
|
+
# company.delete
|
507
|
+
#
|
234
508
|
def delete
|
235
509
|
self.class.archive(id)
|
236
510
|
end
|
@@ -241,7 +515,11 @@ module Hubspot
|
|
241
515
|
end
|
242
516
|
|
243
517
|
# rubocop:disable Metrics/MethodLength
|
244
|
-
|
518
|
+
|
519
|
+
# getter: Check the properties and changes hashes to see if the method
|
520
|
+
# being called is a key, and return the corresponding value
|
521
|
+
# setter: If the method ends in "=" persist the value in the changes hash
|
522
|
+
# (when it is different from the corresponding value in properties if set)
|
245
523
|
def method_missing(method, *args)
|
246
524
|
method_name = method.to_s
|
247
525
|
|
@@ -249,15 +527,8 @@ module Hubspot
|
|
249
527
|
if method_name.end_with?('=')
|
250
528
|
attribute = method_name.chomp('=')
|
251
529
|
new_value = args.first
|
252
|
-
|
253
|
-
#
|
254
|
-
if @properties[attribute] != new_value
|
255
|
-
@changes[attribute] = new_value
|
256
|
-
else
|
257
|
-
@changes.delete(attribute) # Remove from changes if it reverts to the original value
|
258
|
-
end
|
259
|
-
|
260
|
-
return new_value
|
530
|
+
add_accessors attribute
|
531
|
+
return send("#{attribute}=", new_value)
|
261
532
|
# Handle getters
|
262
533
|
else
|
263
534
|
return @changes[method_name] if @changes.key?(method_name)
|
@@ -267,35 +538,77 @@ 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
|
276
548
|
end
|
277
549
|
|
550
|
+
# Initialize from API response, separating metadata from properties
|
551
|
+
def initialize_from_api(data)
|
552
|
+
@changes = data.delete('changes')&.transform_keys!(&:to_s) || {}
|
553
|
+
|
554
|
+
if data['properties']
|
555
|
+
@metadata = data.reject { |key, _v| key == 'properties' }
|
556
|
+
handle_properties(data['properties'])
|
557
|
+
else
|
558
|
+
handle_properties(data)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
278
562
|
private
|
279
563
|
|
280
564
|
# Extract ID from data and convert to integer
|
281
|
-
def extract_id(
|
282
|
-
|
565
|
+
def extract_id(id)
|
566
|
+
id&.to_i
|
283
567
|
end
|
284
568
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
569
|
+
def handle_properties(properties_data)
|
570
|
+
properties_data.each do |attribute, value|
|
571
|
+
if metadata_field?(attribute)
|
572
|
+
@metadata[attribute.to_s] = value
|
573
|
+
else
|
574
|
+
add_accessors attribute.to_s
|
575
|
+
@properties[attribute.to_s] = value
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
def add_accessors(attribute)
|
581
|
+
add_accessors_setter(attribute)
|
582
|
+
add_accessors_getter(attribute)
|
583
|
+
end
|
289
584
|
|
290
|
-
|
291
|
-
|
292
|
-
|
585
|
+
def add_accessors_setter(attribute)
|
586
|
+
# Define the setter method
|
587
|
+
define_singleton_method("#{attribute}=") do |new_value|
|
588
|
+
# Track changes only if the value has actually changed
|
589
|
+
if @properties[attribute] != new_value
|
590
|
+
@changes[attribute] = new_value
|
293
591
|
else
|
294
|
-
@
|
592
|
+
@changes.delete(attribute) # Remove from changes if it reverts to the original value
|
295
593
|
end
|
594
|
+
|
595
|
+
new_value
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def add_accessors_getter(attribute)
|
600
|
+
# Define the getter method
|
601
|
+
define_singleton_method(attribute) do
|
602
|
+
# Return from changes if available, else return from properties
|
603
|
+
return @changes[attribute] if @changes.key?(attribute)
|
604
|
+
|
605
|
+
@properties[attribute] if @properties.key?(attribute)
|
296
606
|
end
|
607
|
+
end
|
297
608
|
|
298
|
-
|
609
|
+
# allows overwriting in other resource classes
|
610
|
+
def metadata_field?(key)
|
611
|
+
METADATA_FIELDS.include?(key)
|
299
612
|
end
|
300
613
|
|
301
614
|
# Initialize a new object (no API response)
|
@@ -305,11 +618,6 @@ module Hubspot
|
|
305
618
|
@metadata = {}
|
306
619
|
end
|
307
620
|
|
308
|
-
# Extract metadata from data, excluding properties
|
309
|
-
def extract_metadata(data)
|
310
|
-
data.reject { |key, _| key == 'properties' }
|
311
|
-
end
|
312
|
-
|
313
621
|
# Create a new resource
|
314
622
|
def create_new
|
315
623
|
created_resource = self.class.create(@changes)
|
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.
|
4
|
+
version: 0.3.0
|
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-15 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
|
@@ -254,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
255
|
version: '0'
|
255
256
|
requirements: []
|
256
257
|
rubyforge_project:
|
257
|
-
rubygems_version: 2.6.14
|
258
|
+
rubygems_version: 2.6.14
|
258
259
|
signing_key:
|
259
260
|
specification_version: 4
|
260
261
|
summary: ruby_hubspot_api is an ORM-like wrapper for the Hubspot API
|