Class: Hubspot::Resource

Inherits:
ApiClient show all
Extended by:
Hubspot::ResourceFilter::FilterGroupMethods
Defined in:
lib/hubspot/resource.rb

Overview

HubSpot Resource Base Class This class provides common functionality for interacting with HubSpot API resources such as Contacts, Companies, etc

It supports common operations like finding, creating, updating, and deleting resources, as well as batch operations.

This class is meant to be inherited by specific resources like ‘Hubspot::Contact`.

Example Usage:

Hubspot::Contact.find(1)
contact.name # 'Luke'

company = Hubspot::Company.create(name: "Acme Corp")
company.id.nil? # false

Direct Known Subclasses

Company, Contact, Form, User

Constant Summary collapse

METADATA_FIELDS =
%w[createdate hs_object_id lastmodifieddate].freeze

Constants included from Hubspot::ResourceFilter::FilterGroupMethods

Hubspot::ResourceFilter::FilterGroupMethods::OPERATOR_MAP

Constants inherited from ApiClient

ApiClient::MAX_RETRIES, ApiClient::RETRY_WAIT_TIME

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Hubspot::ResourceFilter::FilterGroupMethods

build_filter_groups, extract_property_and_operator

Methods inherited from ApiClient

delete, get, #handle_response, handle_response, log_request, patch, post

Constructor Details

#initialize(data = {}) ⇒ Resource

Public: Initialize a resouce

data - [2D Hash, nested Hash] data to initialise:

- The response from the api will be of the form:
    { id: <hs_object_id>, properties: { "email": "[email protected]" ... }, ... }

- A Simple 2D Hash, key value pairs in the form:
    { email: '[email protected]', firstname: 'John', lastname: 'Smith' }

- A structured hash consisting of { id: <hs_object_id>, properties: {}, ... }
  This is the same structure as per the API, and can be rebuilt if you store the id
  of the object against your own data

Example:

attrs = { firstname: 'Luke', lastname: 'Skywalker', email: '[email protected]' }
contact = Hubspot::Contact.new(attrs)
contact.persisted? # false
contact.save # creates the record in Hubspot
contact.persisted? # true
puts "Contact saved with hubspot id #{contact.id}"

existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)


462
463
464
465
466
467
468
469
470
471
472
# File 'lib/hubspot/resource.rb', line 462

def initialize(data = {})
  data.transform_keys!(&:to_s)
  @id = extract_id(data.delete(api_id_field))
  @properties = {}
  @metadata = {}
  if @id
    initialize_from_api(data)
  else
    initialize_new_object(data)
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

getter: Check the properties and changes hashes to see if the method being called is a key, and return the corresponding value setter: If the method ends in “=” persist the value in the changes hash (when it is different from the corresponding value in properties if set)



574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
# File 'lib/hubspot/resource.rb', line 574

def method_missing(method, *args)
  method_name = method.to_s

  # Handle setters
  if method_name.end_with?('=')
    attribute = method_name.chomp('=')
    new_value = args.first
    add_accessors attribute
    return send("#{attribute}=", new_value)
  # Handle getters
  else
    return @changes[method_name] if @changes.key?(method_name)
    return @properties[method_name] if @properties.key?(method_name)
  end

  # Fallback if the method or attribute is not found
  super
end

Instance Attribute Details

#changesObject

track any changes made to properties before saving etc



41
42
43
# File 'lib/hubspot/resource.rb', line 41

def changes
  @changes
end

#idObject

the id of the object in hubspot



35
36
37
# File 'lib/hubspot/resource.rb', line 35

def id
  @id
end

#metadataObject

any other data sent from the api about the resource



44
45
46
# File 'lib/hubspot/resource.rb', line 44

def 
  @metadata
end

#propertiesObject

the properties as if read from the api



38
39
40
# File 'lib/hubspot/resource.rb', line 38

def properties
  @properties
end

Class Method Details

.allObject

Return a paged_collection - similar to an ActiveRecord Relation

Example:

contacts_collection = Hubspot::Contact.all
  <PagedCollection>

contacts_collection.where(email_contains: 'hubspot.com')
  <PagedCollection, @params={:filterGroups=> ....


55
56
57
58
59
60
61
62
63
# File 'lib/hubspot/resource.rb', line 55

def all
  PagedCollection.new(
    url: "#{api_root}/#{resource_name}/search",
    params: {},
    resource_class: self,
    method: :post,
    results_param: results_param
  )
end

.archive(id) ⇒ Object

Deletes a resource by ID.

id - The ID of the resource to delete.

Example:

Hubspot::Contact.archive(1)

Returns True if the deletion was successful



184
185
186
187
188
189
# File 'lib/hubspot/resource.rb', line 184

def archive(id)
  response = delete("#{api_root}/#{resource_name}/#{id}")
  handle_response(response)

  true
end

.batch_read(object_ids = [], properties: [], id_property: 'id') ⇒ Object

Performs a batch read operation to retrieve multiple resources by their IDs.

object_ids - A list of resource IDs to fetch.

id_property - The property to use for identifying resources (default: ‘id’).

Example:

Hubspot::Contact.batch_read([1, 2, 3])

Returns [PagedBatch] A paged batch of resources



224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/hubspot/resource.rb', line 224

def batch_read(object_ids = [], properties: [], id_property: 'id')
  params = {}
  params[:idProperty] = id_property unless id_property == 'id'
  params[:properties] = properties unless properties.blank?

  PagedBatch.new(
    url: "#{api_root}/#{resource_name}/batch/read",
    params: params.empty? ? nil : params,
    object_ids: object_ids,
    resource_class: self
  )
end

.batch_read_all(object_ids = [], properties: [], id_property: 'id') ⇒ Object

Performs a batch read operation to retrieve multiple resources by their IDs until there are none left

object_ids - A list of resource IDs to fetch. [Array<Integer>] id_property - The property to use for identifying resources (default: ‘id’).

Example:

Hubspot::Contact.batch_read_all(hubspot_contact_ids)

Returns [Hubspot::Batch] A batch of resources that can be operated on further



247
248
249
# File 'lib/hubspot/resource.rb', line 247

def batch_read_all(object_ids = [], properties: [], id_property: 'id')
  Hubspot::Batch.read(self, object_ids, properties: properties, id_property: id_property)
end

.create(params) ⇒ Object

Creates a new resource with the given parameters.

params - The properties to create the resource with.

Example:

contact = Hubspot::Contact.create(name: "John Doe", email: "[email protected]")

Returns [Resource] The newly created resource.



154
155
156
157
# File 'lib/hubspot/resource.rb', line 154

def create(params)
  response = post("#{api_root}/#{resource_name}", body: { properties: params }.to_json)
  instantiate_from_response(response)
end

.custom_propertiesObject

Retrieve the complete list of user defined properties for this resource class

Returns [Array<Hubspot::Property>] An array of hubspot properties



264
265
266
# File 'lib/hubspot/resource.rb', line 264

def custom_properties
  properties.reject { |property| property['hubspotDefined'] }
end

.find(id, properties: nil) ⇒ Object

Find a resource by ID and return an instance of the class

id - [Integer] The ID (or hs_object_id) of the resource to fetch. properties - an array of property names to fetch in the result

Example:

contact = Hubspot::Contact.find(1)
contact = Hubspot::Contact.find(1, properties: %w[email firstname lastname custom_field])

Returns An instance of the resource.



113
114
115
116
117
118
# File 'lib/hubspot/resource.rb', line 113

def find(id, properties: nil)
  response = response_for_find_by_id(id, properties: properties)
  return if response.not_found?

  instantiate_from_response(response)
end

.find!(id, properties: nil) ⇒ Object



120
121
122
123
# File 'lib/hubspot/resource.rb', line 120

def find!(id, properties: nil)
  response = response_for_find_by_id(id, properties: properties)
  instantiate_from_response(response)
end

.find_by(property, value, properties: nil) ⇒ Object

Finds a resource by a given property and value.

property - The property to search by (e.g., “email”). value - The value of the property to match. properties - Optional list of properties to return.

Example:

properties = %w[firstname lastname email last_contacted]
contact = Hubspot::Contact.find_by("email", "[email protected]", properties: properties)

Returns An instance of the resource.



136
137
138
139
# File 'lib/hubspot/resource.rb', line 136

def find_by(property, value, properties: nil)
  response = response_for_find_by_property(property, value, properties: properties)
  instantiate_from_response(response) unless response.not_found?
end

.find_by!(property, value, properties: nil) ⇒ Object



141
142
143
144
# File 'lib/hubspot/resource.rb', line 141

def find_by!(property, value, properties: nil)
  response = response_for_find_by_property(property, value, properties: properties)
  instantiate_from_response(response)
end

.list(params = {}) ⇒ Object

Lists all resources with optional filters and pagination.

params - Optional parameters to filter or paginate the results.

Example:

contacts = Hubspot::Contact.list(limit: 100)

Returns [PagedCollection] A collection of resources.



199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/hubspot/resource.rb', line 199

def list(params = {})
  all_properties = build_property_list(params[:properties])

  if all_properties.is_a?(Array) && !all_properties.empty?
    params[:properties] = all_properties.join(',')
  end

  PagedCollection.new(
    url: list_page_uri,
    params: params,
    resource_class: self
  )
end

.propertiesObject

Retrieve the complete list of properties for this resource class

Returns [Array<Hubspot::Property>] An array of hubspot properties



254
255
256
257
258
259
# File 'lib/hubspot/resource.rb', line 254

def properties
  @properties ||= begin
    response = get("/crm/v3/properties/#{resource_name}")
    handle_response(response)['results'].map { |hash| Property.new(hash) }
  end
end

.property(property_name) ⇒ Object

Retrieve information about a specific property

Example:

property = Hubspot::Contact.property('industry_sector')
values_for_select = property.options.each_with_object({}) do |prop, hash|
  hash[prop['value']] = prop['label']
end

Returns [Hubspot::Property] A hubspot property



291
292
293
# File 'lib/hubspot/resource.rb', line 291

def property(property_name)
  properties.detect { |prop| prop.name == property_name }
end

.read_only_propertiesObject

Retrieve the complete list of read-only properties for this resource class

Returns [Array<Hubspot::Property>] An array of read-only hubspot properties



278
279
280
# File 'lib/hubspot/resource.rb', line 278

def read_only_properties
  properties.select(&:read_only)
end

.required_propertiesObject

List of properties that will always be retrieved should be overridden in specific resource class



382
383
384
# File 'lib/hubspot/resource.rb', line 382

def required_properties
  []
end

.resource_nameObject

Define the resource name based on the class



371
372
373
374
375
376
377
378
# File 'lib/hubspot/resource.rb', line 371

def resource_name
  name = self.name.split('::').last.downcase
  if name.end_with?('y')
    name.gsub(/y$/, 'ies') # Company -> companies
  else
    "#{name}s" # Contact -> contacts, Deal -> deals
  end
end

.search(query, properties: [], page_size: 200) ⇒ Object

Search for resources using a flexible query format and optional properties.

This method allows searching for resources by passing a query in the form of a string (for full-text search) or a hash with special suffixes on the keys to define different comparison operators.

You can also specify which properties to return and the number of results per page.

Available suffixes for query keys (when using a hash):

- `_contains`: Matches values that contain the given string.
- `_gt`: Greater than comparison.
- `_lt`: Less than comparison.
- `_gte`: Greater than or equal to comparison.
- `_lte`: Less than or equal to comparison.
- `_neq`: Not equal to comparison.
- `_in`: Matches any of the values in the given array.

If no suffix is provided, the default comparison is equality (‘EQ`).

If no value is provided, or is empty the NOT_HAS_PROPERTY operator will be used

query - [String, Hash] The query for searching. This can be either:

- A String: for full-text search.
- A Hash: where each key represents a property and may have suffixes for the comparison
  (e.g., `{ email_contains: 'example.org', age_gt: 30 }`).

properties - An optional array of property names to return in the search results.

If not specified or empty, HubSpot will return the default set of properties.

page_size - The number of results to return per page

(default is 10 for contacts and 100 for everything else).

Example Usage:

# Full-text search for 'example.org':
props = %w[email firstname lastname]
contacts = Hubspot::Contact.search(query: "example.org", properties: props, page_size: 50)

# Search for contacts whose email contains 'example.org' and are older than 30:
contacts = Hubspot::Contact.search(
  query: { email_contains: 'example.org', age_gt: 30 },
  properties: ["email", "firstname", "lastname"],
  page_size: 50
)

Returns [PagedCollection] A paged collection of results that can be iterated over.



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/hubspot/resource.rb', line 340

def search(query, properties: [], page_size: 200)
  search_body = {}

  # Add properties if specified
  search_body[:properties] = build_property_list(properties) unless properties.empty?

  # Handle the query using case-when for RuboCop compliance
  case query
  when String
    search_body[:query] = query
  when Hash
    search_body[:filterGroups] = build_filter_groups(query)
  else
    raise ArgumentError, 'query must be either a string or a hash'
  end

  # Add the page size (passed as limit to the API)
  search_body[:limit] = page_size

  # Perform the search and return a PagedCollection
  PagedCollection.new(
    url: "#{api_root}/#{resource_name}/search",
    params: search_body,
    resource_class: self,
    method: :post,
    results_param: results_param
  )
end

.select(*properties) ⇒ Object

Select which properties to return from the api - allows chaining



99
100
101
# File 'lib/hubspot/resource.rb', line 99

def select(*properties)
  all.select(*properties)
end

.updatable_propertiesObject

Retrieve the complete list of updatable properties for this resource class

Returns [Array<Hubspot::Property>] An array of updateable hubspot properties



271
272
273
# File 'lib/hubspot/resource.rb', line 271

def updatable_properties
  properties.reject(&:read_only?)
end

.update(id, params) ⇒ Object

Updates an existing resource by ID.

id - The ID of the resource to update. params - The properties to update.

Example:

contact.update(1, name: "Jane Doe")

Returns True if the update was successful



168
169
170
171
172
173
174
# File 'lib/hubspot/resource.rb', line 168

def update(id, params)
  response = patch("#{api_root}/#{resource_name}/#{id}",
                   body: { properties: params }.to_json)
  handle_response(response)

  true
end

.where(filters = {}) ⇒ Object

Example:

big_companies = Hubspot:Company.where(number_of_employees_gte: 100 )
live_contacts = Hubspot::Contact.where(hs_lead_status: %w[NEW OPEN IN_PROGRESS])

Returns a PagedCollection



94
95
96
# File 'lib/hubspot/resource.rb', line 94

def where(filters = {})
  all.where!(filters)
end

Instance Method Details

#changes?Boolean

Determine the state of the object

Returns Boolean

Returns:

  • (Boolean)


478
479
480
# File 'lib/hubspot/resource.rb', line 478

def changes?
  !@changes.empty?
end

#deleteObject Also known as: archive

Archive the object in Hubspot

Example:

company = Hubspot::Company.find(hubspot_company_id)
company.delete


559
560
561
# File 'lib/hubspot/resource.rb', line 559

def delete
  self.class.archive(id)
end

#initialize_from_api(data) ⇒ Object

Initialize from API response, separating metadata from properties



602
603
604
605
606
607
608
609
610
611
# File 'lib/hubspot/resource.rb', line 602

def initialize_from_api(data)
  @changes = data.delete('changes')&.transform_keys!(&:to_s) || {}

  if data['properties']
    @metadata = data.reject { |key, _v| key == 'properties' }
    handle_properties(data['properties'])
  else
    handle_properties(data)
  end
end

#persisted?Boolean

If the resource exists in Hubspot

Returns Boolean

Returns:

  • (Boolean)


511
512
513
# File 'lib/hubspot/resource.rb', line 511

def persisted?
  @id ? true : false
end

#resource_nameObject



564
565
566
# File 'lib/hubspot/resource.rb', line 564

def resource_name
  self.class.resource_name
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Ensure respond_to_missing? handles existing keys in the properties anc changes hashes

Returns:

  • (Boolean)


596
597
598
599
# File 'lib/hubspot/resource.rb', line 596

def respond_to_missing?(method_name, include_private = false)
  property_name = method_name.to_s.chomp('=')
  @properties.key?(property_name) || @changes.key?(property_name) || super
end

#saveObject

Create or Update the resource. If the resource was already persisted (e.g. it was retrieved from the API) it will be updated using values from @changes

If the resource is new (no id) it will be created

Returns Boolean



489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/hubspot/resource.rb', line 489

def save
  if persisted?
    self.class.update(@id, @changes).tap do |result|
      return false unless result

      @properties.merge!(@changes)
      @changes = {}
    end
  else
    create_new
  end
end

#save!Object

Raises:



502
503
504
505
506
# File 'lib/hubspot/resource.rb', line 502

def save!
  raise NothingToDoError, 'Nothing to save' unless changes?

  save
end

#update(attributes) ⇒ Object

Public - Update the resource and persist to the api

attributes - hash of properties to update in key value pairs

Example:

contact = Hubspot::Contact.find(hubspot_contact_id)
contact.update(status: 'gold customer', last_contacted_at: Time.now.utc.iso8601)

Returns Boolean



524
525
526
527
528
529
530
# File 'lib/hubspot/resource.rb', line 524

def update(attributes)
  raise 'Not able to update as not persisted' unless persisted?

  update_attributes(attributes)

  save
end

#update_attributes(attributes) ⇒ Object

Public - Update resource attributes

Does not persist to the api but processes each attribute correctly

Example:

contact = Hubspot::Contact.find(hubspot_contact_id)
contact.changes? # false
contact.update_attributes(education: 'Graduate', university: 'Life')
contact.education # Graduate
contact.changes? # true
contact.changes # { "education" => "Graduate", "university" => "Life" }

Returns Hash of changes

Raises:



545
546
547
548
549
550
551
# File 'lib/hubspot/resource.rb', line 545

def update_attributes(attributes)
  raise ArgumentError, 'must be a hash' unless attributes.is_a?(Hash)

  attributes.each do |key, value|
    send("#{key}=", value) # This will trigger the @changes tracking via method_missing
  end
end