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