ruby_hubspot_api 0.3.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 62b690c7e426369cd5979c4f6b5b0cec8b5a7620
4
- data.tar.gz: 26047bb4aa34d3a91bf6c1aff80b6e3775220f4f
3
+ metadata.gz: 1a2e9bcd6887d8ca6e6aadd20459732e94e76114
4
+ data.tar.gz: '02728d9515a0ef2b5d1eecd6b7b703c7ba9cddb1'
5
5
  SHA512:
6
- metadata.gz: a6512174b13fa2ef07740690864c79a6c3593c908c1fed37170ef35bf08ca7ae5ceb9a3e53fe12d53079eb4e4bb47a58801430103b96c9a7b78dfd67eaaabaa5
7
- data.tar.gz: 6ea719cb62b217fdbbdb2009d64215369608ba8d3423b59245472fb64fcd957174c5cc28a0cbcbd1f5eaf904260d19e1989da528df913292c139b5f4d893110e
6
+ metadata.gz: 054defa6af013acea911e6f671b4c4d0fb200c25bba22c2969f52cadefa7c5beb2d776989936fff62d573f173acdd85514d3ae1dc0a95d1d35958b5f7fab4f0b
7
+ data.tar.gz: 959a83f088e542877d37ed660b20a59c062c314c6ae9343bccbaf8eed6fd63e350566ce8591b982db6649b835c8c87257ca3dc726faeba7caf3da10a962246fc
data/.env.sample CHANGED
@@ -8,33 +8,25 @@ export HUBSPOT_ACCESS_TOKEN=your_private_app_access_token
8
8
  export HUBSPOT_CLIENT_SECRET=your_client_secret
9
9
  export HUBSPOT_PORTAL_ID=portal_id
10
10
 
11
+ export HUBSPOT_NO_AUTH_ACCESS_TOKEN=your_private_app_access_token_with_no_scopes
12
+
11
13
  # VCR configuration
12
14
  # record mode can be none, once, newepisodes or all (default none)
13
15
  # see https://andrewmcodes.gitbook.io/vcr/record_modes
14
- export VCR_RECORD_MODE=once
15
- # to allow new request set to true
16
- export VCR_ALLOW_REQUESTS=false
17
-
18
- # Used in specs....
19
- # The values of the following are the defaults if the env var is not set
20
16
 
21
- # Company id used to test find and update (should be real)
22
- export HUBSPOT_TEST_COMPANY_ID=1
17
+ # export VCR_RECORD_MODE=once # (default)
18
+ # export VCR_RECORD_MODE=all # always record http requests (overwrites)
23
19
 
24
- # Company name to test create company
25
- export HUBSPOT_TEST_COMPANY_NAME="Numenor Capital"
26
-
27
- # Company id to test deleting (hint: make a dummy one first!)
28
- export HUBSPOT_TEST_COMPANY_ID_DELETE=666
29
-
30
- # Contact if to test find and update
31
- export HUBSPOT_TEST_CONTACT_ID=1
20
+ # to allow new request set to true
21
+ export VCR_ALLOW_REQUESTS=false
32
22
 
33
- # domain name to test the search by email contains
34
- export HUBSPOT_SEARCH_TEST_DOMAIN=hubspot.com
23
+ # SPECS:
24
+ # the following can be used when running the specs
35
25
 
36
- # maximum number of records to fetch when testing lists
37
- export HUBSPOT_SEARCH_LIMIT=5
26
+ export HUBSPOT_LOG_LEVEL=FATAL # no output during testing (default)
27
+ # export HUBSPOT_LOG_LEVEL=WARN # will show exceptions and batch load wait message
28
+ # export HUBSPOT_LOG_LEVEL=INFO # adds api request descriptions
29
+ # export HUBSPOT_LOG_LEVEL=DEBUG # adds api request body and full response (lots of output!)
38
30
 
39
- # email address for search on email = <email>
40
- export HUBSPOT_TEST_CONTACT_EMAIL=test@hubspot.com
31
+ # How long to wait after a batch is created (for propagation)
32
+ export HUBSPOT_BATCH_WAIT=0
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --format documentation
2
2
  --color
3
3
  --require spec_helper
4
+ --order random
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_hubspot_api (0.3.1)
4
+ ruby_hubspot_api (0.3.3)
5
5
  httparty (>= 0.1, < 1.0)
6
6
 
7
7
  GEM
@@ -40,12 +40,12 @@ GEM
40
40
  rspec-core (~> 3.13.0)
41
41
  rspec-expectations (~> 3.13.0)
42
42
  rspec-mocks (~> 3.13.0)
43
- rspec-core (3.13.1)
43
+ rspec-core (3.13.2)
44
44
  rspec-support (~> 3.13.0)
45
45
  rspec-expectations (3.13.3)
46
46
  diff-lcs (>= 1.2.0, < 2.0)
47
47
  rspec-support (~> 3.13.0)
48
- rspec-mocks (3.13.1)
48
+ rspec-mocks (3.13.2)
49
49
  diff-lcs (>= 1.2.0, < 2.0)
50
50
  rspec-support (~> 3.13.0)
51
51
  rspec-support (3.13.1)
@@ -62,8 +62,6 @@ GEM
62
62
 
63
63
  PLATFORMS
64
64
  ruby
65
- x86_64-darwin-18
66
- x86_64-linux
67
65
 
68
66
  DEPENDENCIES
69
67
  bundler (>= 2.0, < 2.4.0)
@@ -80,4 +78,4 @@ DEPENDENCIES
80
78
  webmock (>= 3.0)
81
79
 
82
80
  BUNDLED WITH
83
- 2.2.34
81
+ 2.0.2
data/README.md CHANGED
@@ -78,7 +78,7 @@ Hubspot::Company # crm > companies
78
78
  Hubspot::User # hubspot users (also referred to as 'owners')
79
79
  Hubspot::Owner # alias of Hubspot::User if you prefer to use it
80
80
  ```
81
-
81
+
82
82
  however you can [add custom objects of your own](#user-content-custom-resources) based on your own custom defined Objects in Hubspot
83
83
 
84
84
  ### Creating and Saving an Object
@@ -254,7 +254,7 @@ Example:
254
254
 
255
255
  ```ruby
256
256
  # Search for contacts with email containing "hubspot.com"
257
- contacts = Hubspot::Contact.search(query: { email_contains: 'hubspot.com' })
257
+ contacts = Hubspot::Contact.search({ email_contains: 'hubspot.com' }, properties: %w[firstname lastname email])
258
258
 
259
259
  puts "Searching for Hubspot staff in the contacts CRM"
260
260
  puts ""
@@ -264,17 +264,18 @@ contacts.each do |contact|
264
264
  end
265
265
 
266
266
  # Search for companies with number of employees greater than or equal to 100
267
- companies = Hubspot::Company.search(query: { number_of_employees_gte: 100 })
268
-
269
267
  puts "Searching for medium to large companies"
270
268
  puts ""
271
269
 
272
- companies.each do |company|
273
- puts " Found: #{company.name} (#{company.number_of_employees} employees)"
270
+ companies = Hubspot::Company.search(number_of_employees_gte: 100).all # returns a PagedCollection
271
+ companies.each_page do |page_of_companies|
272
+ page_of_companies.each do |company|
273
+ puts " Found: #{company.name} (#{company.number_of_employees} employees)"
274
+ end
274
275
  end
275
276
 
276
277
  # Search for contacts with email in a specific list (IN operator)
277
- contacts = Hubspot::Contact.search(query: { email_in: ['user1@example.com', 'user2@example.com'] })
278
+ contacts = Hubspot::Contact.search(email: ['user1@example.com', 'user2@example.com'])
278
279
 
279
280
  contacts.each do |contact|
280
281
  puts "Found: #{contact.email}"
@@ -288,7 +289,6 @@ end
288
289
  - **gte**: Greater than or equal to.
289
290
  - **lt**: Less than.
290
291
  - **lte**: Less than or equal to.
291
- - **IN**: Matches any of the values in an array.
292
292
 
293
293
  #### Searching for empty values (NOT_HAS_PROPERTY)
294
294
 
@@ -296,7 +296,7 @@ Any empty value in your search will be matched using the correect filter in Hubs
296
296
 
297
297
  ```ruby
298
298
  # Search for companies with no value for a given field
299
- companies = Hubspot::Company.search(query: { client_category: nil }, properties: %w[name number_of_employees])
299
+ companies = Hubspot::Company.search({ client_category: nil }, properties: %w[name number_of_employees])
300
300
  # Request body: {"filterGroups":[{"filters":[{"propertyName":"client_category","operator":"NOT_HAS_PROPERTY"}]}]
301
301
 
302
302
  puts "Searching for uncategorised customers"
@@ -310,6 +310,47 @@ companies.each do |company|
310
310
  end
311
311
  ```
312
312
 
313
+ #### Searching for any value in an array (IN)
314
+
315
+ If value is an array the operator will be set to 'IN'
316
+
317
+ ```ruby
318
+ # Search for companies with no value for a given field
319
+ hot_contacts = Hubspot::Contact.search(hs_lead_status: %w[IN_PROGRESS OPEN_DEAL])
320
+ # Request body: {"filterGroups":[{"filters":[{"propertyName":"hs_lead_status","operator":"IN","values":["IN_PROGRESS", "OPEN_DEAL"]}]}]
321
+
322
+ puts "Current best leads"
323
+ puts ""
324
+
325
+ hot_contacts.each do |contact|
326
+ puts "#{contact.name} .... "
327
+ end
328
+
329
+ ```
330
+
331
+ #### Chaining search conditions (a la Active Record)
332
+
333
+ You can use the alternative Rails-like interface to return a PagedCollection and can chain calls. Use `where(search_params)` to filter and `.select('firstname', 'lastname', 'email', 'custom_property')` to determine the properties returned by the API.
334
+
335
+ ```ruby
336
+ contact = Hubspot::Contact.where(hs_lead_status: [])
337
+ ```
338
+
339
+ #### Getting the total number of records
340
+
341
+ Having created a search collection you can retrieve the total number of matching records from Hubspot by calling .total on the collection. This will make a single request to the api to retrieve the number of matching records
342
+
343
+ ```ruby
344
+ contacts_collection = Hubspot::Contact.search(lead_status: 'cold')
345
+
346
+ if contacts_collection.total > 200
347
+ puts "Contacts will be processed overnight"
348
+ # schedule_processing_job
349
+ else
350
+ puts "Found #{contacts_collection.total} contacts. Processing...."
351
+ process_contacts(contacts_collection)
352
+ ```
353
+
313
354
  #### Specifying Properties in Search
314
355
 
315
356
  When performing a search, you can also specify which properties to return.
@@ -320,7 +361,7 @@ Example:
320
361
  ```ruby
321
362
  # Search for contacts with email containing "hubspot.com" and only return specific properties
322
363
  contacts = Hubspot::Contact.search(
323
- query: { email_contains: 'hubspot.com' },
364
+ { email_contains: 'hubspot.com' },
324
365
  properties: ['firstname', 'lastname', 'email', 'mobile', 'custom_property_1']
325
366
  )
326
367
 
@@ -383,7 +424,7 @@ companies = batch.resources
383
424
  ```
384
425
 
385
426
  Example of reading contacts by email and the helper method `batch_read`
386
- By using this method you can page through the results as needed or collect them
427
+ By using this method you can page through the results as needed or collect them
387
428
 
388
429
  ```ruby
389
430
  email_addresses = my_selected_contacts.collect(&:email).compact
@@ -400,7 +441,7 @@ batch.each_page do |contacts|
400
441
  end
401
442
  ```
402
443
 
403
- Finally there is another helper method `batch_read_all` on any Hubspot::Resource class (Hubspot::Contact, Hubspot::Company, Hubspot::User etc) which will read all of the resources and return a HubSpot::Batch (with all of the resources).
444
+ Finally there is another helper method `batch_read_all` on any Hubspot::Resource class (Hubspot::Contact, Hubspot::Company, Hubspot::User etc) which will read all of the resources and return a HubSpot::Batch (with all of the resources).
404
445
 
405
446
  You can then update the resources and call `update` on the batch.... see below
406
447
 
@@ -456,7 +497,7 @@ In this example, if a contact with the given email already exists in HubSpot, it
456
497
  To archive objects in bulk, you can use the `archive` method. This removes the objects from HubSpot.
457
498
 
458
499
  ```ruby
459
- contacts = Hubspot::Contact.search(query: { email_contains: 'hubspot.com' }).all
500
+ contacts = Hubspot::Contact.where(email_contains: 'hubspot.com').all
460
501
 
461
502
  batch = Hubspot::Batch.new(contacts)
462
503
  batch.archive
@@ -489,7 +530,7 @@ require 'ruby_hubspot_api' # if not required by bundler already...
489
530
 
490
531
  module Hubspot
491
532
  class Project < Resource
492
-
533
+
493
534
  # resource_name (part of the url in the api) will default
494
535
  # to a simple plural of the class name - in this case 'projects'
495
536
  # if the url for your custom object is different you can override it
@@ -500,7 +541,7 @@ module Hubspot
500
541
  end
501
542
  end
502
543
 
503
- projects = Hubspot::Projects.search(query: { status_in: ['upcoming', 'active', 'overrun'] }).all
544
+ projects = Hubspot::Projects.where(status: ['upcoming', 'active', 'overrun']).all
504
545
 
505
546
  ```
506
547
 
@@ -7,7 +7,9 @@ module Hubspot
7
7
  attr_accessor :response
8
8
 
9
9
  def initialize(response, message = nil)
10
- message = response.parsed_response['message'] if !message && response.respond_to?(:parsed_response)
10
+ if !message && response.respond_to?(:parsed_response)
11
+ message = response.parsed_response['message']
12
+ end
11
13
  message += "\n" if message
12
14
  me = super("#{message}Response body: #{response.body}",)
13
15
  me.response = response
@@ -20,6 +22,7 @@ module Hubspot
20
22
  class NotConfiguredError < StandardError; end
21
23
  class ArgumentError < StandardError; end
22
24
  class NothingToDoError < StandardError; end
25
+ class NotImplementedError < StandardError; end
23
26
 
24
27
  class << self
25
28
  def error_from_response(response)
data/lib/hubspot/form.rb CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Hubspot
4
4
  class Form < Resource
5
- METADATA_FIELDS = %w[createdAt updatedAt archived].freeze
6
-
7
5
  # :nocov:
8
6
  def inspect
9
7
  "#<#{self.class.name} " \
@@ -13,6 +11,8 @@ module Hubspot
13
11
  # :nocov:
14
12
 
15
13
  class << self
14
+ private
15
+
16
16
  def api_root
17
17
  '/marketing/v3'
18
18
  end
@@ -24,5 +24,9 @@ module Hubspot
24
24
  def extract_id(id)
25
25
  id
26
26
  end
27
+
28
+ def metadata_fields
29
+ %w[createdAt updatedAt archived].freeze
30
+ end
27
31
  end
28
32
  end
@@ -3,27 +3,38 @@
3
3
  require 'json'
4
4
  require_relative './api_client'
5
5
  require_relative './exceptions'
6
+ require_relative './resource_filter'
6
7
 
7
8
  module Hubspot
8
9
  # Enumerable class for handling paged data from the API
9
10
  class PagedCollection < ApiClient
10
11
  include Enumerable
12
+ include ResourceFilter::FilterGroupMethods
11
13
 
12
- MAX_LIMIT = 100 # HubSpot max items per page
14
+ MAX_LIMIT = 200 # HubSpot max items per page
13
15
 
14
- def initialize(url:, params: {}, resource_class: nil, method: :get)
16
+ # rubocop:disable Lint/MissingSuper
17
+ def initialize(url:, params: {}, resource_class: nil, method: :get, results_param: 'results')
15
18
  @url = url
16
19
  @params = params
17
20
  @resource_class = resource_class
18
21
  @method = method.to_sym
22
+ @results_param = results_param
23
+ end
24
+ # rubocop:enable Lint/MissingSuper
25
+
26
+ def total
27
+ @total ||= determine_total
19
28
  end
20
29
 
21
30
  def each_page
22
31
  offset = nil
23
32
  loop do
24
33
  response = fetch_page(offset)
34
+ @total = response['total'] if response['total']
25
35
  mapped_results = process_results(response)
26
36
  yield mapped_results unless mapped_results.empty?
37
+ sleep wait_between_pages
27
38
  offset = response.dig('paging', 'next', 'after')
28
39
  break unless offset
29
40
  end
@@ -38,10 +49,12 @@ module Hubspot
38
49
  end
39
50
 
40
51
  # Override Enumerable's first method so as not to have to call each (via all)
52
+ # rubocop:disable Metrics/MethodLength
41
53
  def first(limit = 1)
42
54
  resources = []
43
55
  remaining = limit
44
56
 
57
+ original_limit = @params.delete(:limit)
45
58
  # Modify @params directly to set the limit
46
59
  @params[:limit] = [remaining, MAX_LIMIT].min
47
60
 
@@ -52,8 +65,10 @@ module Hubspot
52
65
  break if remaining <= 0
53
66
  end
54
67
 
68
+ @params[:limit] = original_limit
55
69
  limit == 1 ? resources.first : resources.first(limit)
56
70
  end
71
+ # rubocop:enable Metrics/MethodLength
57
72
 
58
73
  def each(&block)
59
74
  each_page do |page|
@@ -61,8 +76,63 @@ module Hubspot
61
76
  end
62
77
  end
63
78
 
79
+ def where(filters = {})
80
+ dup.where!(filters)
81
+ end
82
+
83
+ def where!(filters = {})
84
+ # Build new filter groups from the provided filters
85
+ new_filter_groups = build_filter_groups(filters)
86
+
87
+ # Merge new filters into the existing params structure
88
+ if @params[:filterGroups]&.any?
89
+ @params[:filterGroups][0][:filters].concat(new_filter_groups[0][:filters])
90
+ else
91
+ @params[:filterGroups] = new_filter_groups
92
+ end
93
+
94
+ self # Returning self for method chaining if needed
95
+ end
96
+
97
+ def select(*properties)
98
+ unless properties.blank?
99
+ @params ||= {}
100
+ @params[:properties] ||= []
101
+ @params[:properties].concat(properties.flatten)
102
+ end
103
+
104
+ self
105
+ end
106
+
64
107
  private
65
108
 
109
+ def determine_total
110
+ # We only get a response['total'] for the search endpoint
111
+ raise NotImplementedError, 'Total only available for search requests' unless search_request?
112
+
113
+ # if we don't already know the total we will make a single request and minimise the response
114
+ # size by asking for just one property and one record.
115
+
116
+ # store the current properties
117
+ original_properties = @params.delete(:properties)
118
+
119
+ # just request hs_object_id
120
+ @params[:properties] = ['hs_object_id']
121
+
122
+ # dummy request. @total will be set during the each_page evaluation
123
+ _first_page = first
124
+
125
+ # restore the original properties
126
+ @params[:properties] = original_properties
127
+
128
+ # return the now set total
129
+ @total
130
+ end
131
+
132
+ def search_request?
133
+ @url.include?('/search')
134
+ end
135
+
66
136
  def fetch_page(offset)
67
137
  params_with_offset = @params.dup
68
138
  params_with_offset.merge!(after: offset) if offset
@@ -82,8 +152,14 @@ module Hubspot
82
152
  end
83
153
  end
84
154
 
155
+ def wait_between_pages
156
+ return 0.2 if search_request?
157
+
158
+ 0
159
+ end
160
+
85
161
  def process_results(response)
86
- results = response['results'] || []
162
+ results = response[@results_param] || []
87
163
  return results unless @resource_class
88
164
 
89
165
  results.map { |result| @resource_class.new(result) }
@@ -3,6 +3,7 @@
3
3
  require_relative './api_client'
4
4
  require_relative './paged_collection'
5
5
  require_relative './paged_batch'
6
+ require_relative './resource_filter'
6
7
 
7
8
  module Hubspot
8
9
  # rubocop:disable Metrics/ClassLength
@@ -26,6 +27,7 @@ module Hubspot
26
27
  #
27
28
  class Resource < ApiClient
28
29
  METADATA_FIELDS = %w[createdate hs_object_id lastmodifieddate].freeze
30
+ extend ResourceFilter::FilterGroupMethods
29
31
 
30
32
  # Allow read/write access to id, properties, changes and metadata
31
33
 
@@ -42,6 +44,62 @@ module Hubspot
42
44
  attr_accessor :metadata
43
45
 
44
46
  class << self
47
+ # Return a paged_collection - similar to an ActiveRecord Relation
48
+ #
49
+ # Example:
50
+ # contacts_collection = Hubspot::Contact.all
51
+ # <PagedCollection>
52
+ #
53
+ # contacts_collection.where(email_contains: 'hubspot.com')
54
+ # <PagedCollection, @params={:filterGroups=> ....
55
+ def all
56
+ PagedCollection.new(
57
+ url: "#{api_root}/#{resource_name}/search",
58
+ params: {},
59
+ resource_class: self,
60
+ method: :post,
61
+ results_param: results_param
62
+ )
63
+ end
64
+
65
+ # Filter resources - allows chaining
66
+ #
67
+ # This method allows searching for resources by passing a hash with special suffixes
68
+ # on the keys to define different comparison operators.
69
+ #
70
+ # Available suffixes for query keys (when using a hash):
71
+ # - `_contains`: Matches values that contain the given string.
72
+ # - `_gt`: Greater than comparison.
73
+ # - `_lt`: Less than comparison.
74
+ # - `_gte`: Greater than or equal to comparison.
75
+ # - `_lte`: Less than or equal to comparison.
76
+ # - `_neq`: Not equal to comparison.
77
+ #
78
+ # If value is an array the 'IN' operator will be used
79
+ #
80
+ # Otherwise if no suffix is provided, the default comparison is equality (`EQ`).
81
+ #
82
+ # If no value is provided, or is empty the NOT_HAS_PROPERTY operator will be used
83
+ #
84
+ # filters - [Hash] The query for searching.
85
+ # each key represents a property and may have suffixes for the comparison
86
+ # (e.g., `{ email_contains: 'example.org', age_gt: 30 }`).
87
+
88
+ # Example:
89
+ #
90
+ # big_companies = Hubspot:Company.where(number_of_employees_gte: 100 )
91
+ # live_contacts = Hubspot::Contact.where(hs_lead_status: %w[NEW OPEN IN_PROGRESS])
92
+ #
93
+ # Returns a PagedCollection
94
+ def where(filters = {})
95
+ all.where!(filters)
96
+ end
97
+
98
+ # Select which properties to return from the api - allows chaining
99
+ def select(*properties)
100
+ all.select(*properties)
101
+ end
102
+
45
103
  # Find a resource by ID and return an instance of the class
46
104
  #
47
105
  # id - [Integer] The ID (or hs_object_id) of the resource to fetch.
@@ -53,11 +111,14 @@ module Hubspot
53
111
  #
54
112
  # Returns An instance of the resource.
55
113
  def find(id, properties: nil)
56
- 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 || {})
114
+ response = response_for_find_by_id(id, properties: properties)
115
+ return if response.not_found?
116
+
117
+ instantiate_from_response(response)
118
+ end
119
+
120
+ def find!(id, properties: nil)
121
+ response = response_for_find_by_id(id, properties: properties)
61
122
  instantiate_from_response(response)
62
123
  end
63
124
 
@@ -69,16 +130,16 @@ module Hubspot
69
130
  #
70
131
  # Example:
71
132
  # properties = %w[firstname lastname email last_contacted]
72
- # contact = Hubspot::Contact.find_by("email", "john@example.com", properties)
133
+ # contact = Hubspot::Contact.find_by("email", "john@example.com", properties: properties)
73
134
  #
74
135
  # Returns An instance of the resource.
75
- def find_by(property, value, properties = nil)
76
- params = { idProperty: property }
77
-
78
- all_properties = build_property_list(properties)
79
- params[:properties] = all_properties unless all_properties.empty?
136
+ def find_by(property, value, properties: nil)
137
+ response = response_for_find_by_property(property, value, properties: properties)
138
+ instantiate_from_response(response) unless response.not_found?
139
+ end
80
140
 
81
- response = get("#{api_root}/#{resource_name}/#{value}", query: params)
141
+ def find_by!(property, value, properties: nil)
142
+ response = response_for_find_by_property(property, value, properties: properties)
82
143
  instantiate_from_response(response)
83
144
  end
84
145
 
@@ -231,17 +292,6 @@ module Hubspot
231
292
  properties.detect { |prop| prop.name == property_name }
232
293
  end
233
294
 
234
- # Simplified search interface
235
- OPERATOR_MAP = {
236
- '_contains' => 'CONTAINS_TOKEN',
237
- '_gt' => 'GT',
238
- '_lt' => 'LT',
239
- '_gte' => 'GTE',
240
- '_lte' => 'LTE',
241
- '_neq' => 'NEQ',
242
- '_in' => 'IN'
243
- }.freeze
244
-
245
295
  # rubocop:disable Metrics/MethodLength
246
296
 
247
297
  # Search for resources using a flexible query format and optional properties.
@@ -287,11 +337,11 @@ module Hubspot
287
337
  # )
288
338
  #
289
339
  # Returns [PagedCollection] A paged collection of results that can be iterated over.
290
- def search(query:, properties: [], page_size: 100)
340
+ def search(query, properties: [], page_size: 200)
291
341
  search_body = {}
292
342
 
293
343
  # Add properties if specified
294
- search_body[:properties] = properties unless properties.empty?
344
+ search_body[:properties] = build_property_list(properties) unless properties.empty?
295
345
 
296
346
  # Handle the query using case-when for RuboCop compliance
297
347
  case query
@@ -311,16 +361,12 @@ module Hubspot
311
361
  url: "#{api_root}/#{resource_name}/search",
312
362
  params: search_body,
313
363
  resource_class: self,
314
- method: :post
364
+ method: :post,
365
+ results_param: results_param
315
366
  )
316
367
  end
317
-
318
368
  # rubocop:enable Metrics/MethodLength
319
369
 
320
- # The root of the api call. Mostly this will be "crm"
321
- # but you can override this to account for a different
322
- # object hierarchy
323
-
324
370
  # Define the resource name based on the class
325
371
  def resource_name
326
372
  name = self.name.split('::').last.downcase
@@ -339,49 +385,44 @@ module Hubspot
339
385
 
340
386
  private
341
387
 
388
+ # The root of the api call. Mostly this will be "crm"
389
+ # but you can override this to account for a different
390
+ # object hierarchy
342
391
  def api_root
343
392
  '/crm/v3/objects'
344
393
  end
345
394
 
346
- def list_page_uri
347
- "#{api_root}/#{resource_name}"
395
+ # In the response from the api the resources returned in this key
396
+ def results_param
397
+ 'results'
348
398
  end
349
399
 
350
- # Instantiate a single resource object from the response
351
- def instantiate_from_response(response)
352
- data = handle_response(response)
353
- new(data) # Passing full response data to initialize
400
+ def list_page_uri
401
+ "#{api_root}/#{resource_name}"
354
402
  end
355
403
 
356
- # Convert simple filters to HubSpot's filterGroups format
357
- def build_filter_groups(filters)
358
- filter_groups = [{ filters: [] }]
359
-
360
- filters.each do |key, value|
361
- filter = extract_property_and_operator(key, value)
362
- value_key = value.is_a?(Array) ? :values : :value
363
- filter[value_key] = value unless value.blank?
364
- filter_groups.first[:filters] << filter
404
+ def response_for_find_by_id(id, properties: nil)
405
+ all_properties = build_property_list(properties)
406
+ if all_properties.is_a?(Array) && !all_properties.empty?
407
+ params = { query: { properties: all_properties } }
365
408
  end
366
409
 
367
- filter_groups
410
+ get("#{api_root}/#{resource_name}/#{id}", params || {})
368
411
  end
369
412
 
370
- # Extract property name and operator from the key
371
- def extract_property_and_operator(key, value)
372
- return { propertyName: key.to_s, operator: 'NOT_HAS_PROPERTY' } if value.blank?
413
+ def response_for_find_by_property(property, value, properties: nil)
414
+ params = { idProperty: property }
415
+
416
+ all_properties = build_property_list(properties)
417
+ params[:properties] = all_properties unless all_properties.empty?
373
418
 
374
- OPERATOR_MAP.each do |suffix, hubspot_operator|
375
- if key.to_s.end_with?(suffix)
376
- return {
377
- propertyName: key.to_s.sub(suffix, ''),
378
- operator: hubspot_operator
379
- }
380
- end
381
- end
419
+ get("#{api_root}/#{resource_name}/#{value}", query: params)
420
+ end
382
421
 
383
- # Default to 'EQ' operator if no suffix is found
384
- { propertyName: key.to_s, operator: 'EQ' }
422
+ # Instantiate a single resource object from the response
423
+ def instantiate_from_response(response)
424
+ data = handle_response(response)
425
+ new(data) # Passing full response data to initialize
385
426
  end
386
427
 
387
428
  # Internal make a list of properties to request from the API
@@ -420,7 +461,7 @@ module Hubspot
420
461
  # existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)
421
462
  def initialize(data = {})
422
463
  data.transform_keys!(&:to_s)
423
- @id = extract_id(data.delete('id'))
464
+ @id = extract_id(data.delete(api_id_field))
424
465
  @properties = {}
425
466
  @metadata = {}
426
467
  if @id
@@ -571,6 +612,10 @@ module Hubspot
571
612
 
572
613
  private
573
614
 
615
+ def api_id_field
616
+ 'id'
617
+ end
618
+
574
619
  # Extract ID from data and convert to integer
575
620
  def extract_id(id)
576
621
  id&.to_i
@@ -616,9 +661,13 @@ module Hubspot
616
661
  end
617
662
  end
618
663
 
619
- # allows overwriting in other resource classes
620
664
  def metadata_field?(key)
621
- METADATA_FIELDS.include?(key)
665
+ metadata_fields.include?(key)
666
+ end
667
+
668
+ # allows overwriting in other resource classes
669
+ def metadata_fields
670
+ METADATA_FIELDS
622
671
  end
623
672
 
624
673
  # Initialize a new object (no API response)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hubspot
4
+ module ResourceFilter
5
+ module FilterGroupMethods
6
+ # Simplified search interface
7
+ OPERATOR_MAP = {
8
+ '_contains' => 'CONTAINS_TOKEN',
9
+ '_gt' => 'GT',
10
+ '_lt' => 'LT',
11
+ '_gte' => 'GTE',
12
+ '_lte' => 'LTE',
13
+ '_neq' => 'NEQ',
14
+ '_in' => 'IN'
15
+ }.freeze
16
+
17
+ # Convert simple filters to HubSpot's filterGroups format
18
+ def build_filter_groups(filters)
19
+ filter_groups = [{ filters: [] }]
20
+
21
+ filters.each do |key, value|
22
+ filter = extract_property_and_operator(key, value)
23
+ value_key = value.is_a?(Array) ? :values : :value
24
+ filter[value_key] = value unless value.blank?
25
+ filter_groups.first[:filters] << filter
26
+ end
27
+
28
+ filter_groups
29
+ end
30
+
31
+ # Extract property name and operator from the key
32
+ def extract_property_and_operator(key, value)
33
+ return { propertyName: key.to_s, operator: 'NOT_HAS_PROPERTY' } if value.blank?
34
+
35
+ OPERATOR_MAP.each do |suffix, hubspot_operator|
36
+ if key.to_s.end_with?(suffix)
37
+ return {
38
+ propertyName: key.to_s.sub(suffix, ''),
39
+ operator: hubspot_operator
40
+ }
41
+ end
42
+ end
43
+
44
+ # Default to 'EQ' operator if no suffix is found
45
+ { propertyName: key.to_s, operator: 'EQ' }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hubspot
4
- VERSION = '0.3.1'
4
+ VERSION = '0.3.3'
5
5
  end
data/lib/hubspot.rb CHANGED
@@ -24,14 +24,20 @@ module Hubspot
24
24
  end
25
25
 
26
26
  def configured?
27
- return true unless @config.nil?
27
+ true unless @config.nil?
28
28
  end
29
29
 
30
30
  private
31
31
 
32
- # Set Authorization header on Hubspot::ApiClient when access_token is configured
32
+ # Set Authorization header on all the resource classes when access_token is configured
33
33
  def set_client_headers
34
- Hubspot::ApiClient.headers 'Authorization' => "Bearer #{config.access_token}"
34
+ api_classes = ObjectSpace.each_object(Class).select do |klass|
35
+ klass < Hubspot::ApiClient && !klass.singleton_class?
36
+ end
37
+
38
+ api_classes.each do |subclass|
39
+ subclass.headers 'Authorization' => "Bearer #{config.access_token}"
40
+ end
35
41
  end
36
42
 
37
43
  def set_request_timeouts
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_hubspot_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Brook
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-16 00:00:00.000000000 Z
11
+ date: 2024-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -228,6 +228,7 @@ files:
228
228
  - lib/hubspot/paged_collection.rb
229
229
  - lib/hubspot/property.rb
230
230
  - lib/hubspot/resource.rb
231
+ - lib/hubspot/resource_filter.rb
231
232
  - lib/hubspot/user.rb
232
233
  - lib/hubspot/version.rb
233
234
  - lib/ruby_hubspot_api.rb