Class: Hubspot::Resource
- 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
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
-
#changes ⇒ Object
track any changes made to properties before saving etc.
-
#id ⇒ Object
the id of the object in hubspot.
-
#metadata ⇒ Object
any other data sent from the api about the resource.
-
#properties ⇒ Object
the properties as if read from the api.
Class Method Summary collapse
-
.all ⇒ Object
Return a paged_collection - similar to an ActiveRecord Relation.
-
.archive(id) ⇒ Object
Deletes a resource by ID.
-
.batch_read(object_ids = [], properties: [], id_property: 'id') ⇒ Object
Performs a batch read operation to retrieve multiple resources by their IDs.
-
.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.
-
.create(params) ⇒ Object
Creates a new resource with the given parameters.
-
.custom_properties ⇒ Object
Retrieve the complete list of user defined properties for this resource class.
-
.find(id, properties: nil) ⇒ Object
Find a resource by ID and return an instance of the class.
- .find!(id, properties: nil) ⇒ Object
-
.find_by(property, value, properties: nil) ⇒ Object
Finds a resource by a given property and value.
- .find_by!(property, value, properties: nil) ⇒ Object
-
.list(params = {}) ⇒ Object
Lists all resources with optional filters and pagination.
-
.properties ⇒ Object
Retrieve the complete list of properties for this resource class.
-
.property(property_name) ⇒ Object
Retrieve information about a specific property.
-
.read_only_properties ⇒ Object
Retrieve the complete list of read-only properties for this resource class.
-
.required_properties ⇒ Object
List of properties that will always be retrieved should be overridden in specific resource class.
-
.resource_name ⇒ Object
Define the resource name based on the class.
-
.search(query, properties: [], page_size: 200) ⇒ Object
Search for resources using a flexible query format and optional properties.
-
.select(*properties) ⇒ Object
Select which properties to return from the api - allows chaining.
-
.updatable_properties ⇒ Object
Retrieve the complete list of updatable properties for this resource class.
-
.update(id, params) ⇒ Object
Updates an existing resource by ID.
-
.where(filters = {}) ⇒ Object
Example:.
Instance Method Summary collapse
-
#changes? ⇒ Boolean
Determine the state of the object.
-
#delete ⇒ Object
(also: #archive)
Archive the object in Hubspot.
-
#initialize(data = {}) ⇒ Resource
constructor
Public: Initialize a resouce.
-
#initialize_from_api(data) ⇒ Object
Initialize from API response, separating metadata from properties.
-
#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).
-
#persisted? ⇒ Boolean
If the resource exists in Hubspot.
- #resource_name ⇒ Object
-
#respond_to_missing?(method_name, include_private = false) ⇒ Boolean
Ensure respond_to_missing? handles existing keys in the properties anc changes hashes.
-
#save ⇒ Object
Create or Update the resource.
- #save! ⇒ Object
-
#update(attributes) ⇒ Object
Public - Update the resource and persist to the api.
-
#update_attributes(attributes) ⇒ Object
Public - Update resource attributes.
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
#changes ⇒ Object
track any changes made to properties before saving etc
41 42 43 |
# File 'lib/hubspot/resource.rb', line 41 def changes @changes end |
#id ⇒ Object
the id of the object in hubspot
35 36 37 |
# File 'lib/hubspot/resource.rb', line 35 def id @id end |
#metadata ⇒ Object
any other data sent from the api about the resource
44 45 46 |
# File 'lib/hubspot/resource.rb', line 44 def @metadata end |
#properties ⇒ Object
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
.all ⇒ Object
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
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
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_properties ⇒ Object
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
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 |
.properties ⇒ Object
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
291 292 293 |
# File 'lib/hubspot/resource.rb', line 291 def property(property_name) properties.detect { |prop| prop.name == property_name } end |
.read_only_properties ⇒ Object
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_properties ⇒ Object
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_name ⇒ Object
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_properties ⇒ Object
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
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
478 479 480 |
# File 'lib/hubspot/resource.rb', line 478 def changes? !@changes.empty? end |
#delete ⇒ Object Also known as: archive
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
511 512 513 |
# File 'lib/hubspot/resource.rb', line 511 def persisted? @id ? true : false end |
#resource_name ⇒ Object
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
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 |
#save ⇒ Object
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
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
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
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 |