lib/hubspot/resource.rb in ruby_hubspot_api-0.3.2 vs lib/hubspot/resource.rb in ruby_hubspot_api-0.3.3
- old
+ new
@@ -1,10 +1,11 @@
# frozen_string_literal: true
require_relative './api_client'
require_relative './paged_collection'
require_relative './paged_batch'
+require_relative './resource_filter'
module Hubspot
# rubocop:disable Metrics/ClassLength
# HubSpot Resource Base Class
@@ -24,10 +25,11 @@
# company = Hubspot::Company.create(name: "Acme Corp")
# company.id.nil? # false
#
class Resource < ApiClient
METADATA_FIELDS = %w[createdate hs_object_id lastmodifieddate].freeze
+ extend ResourceFilter::FilterGroupMethods
# Allow read/write access to id, properties, changes and metadata
# the id of the object in hubspot
attr_accessor :id
@@ -40,10 +42,66 @@
# any other data sent from the api about the resource
attr_accessor :metadata
class << self
+ # 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=> ....
+ def all
+ PagedCollection.new(
+ url: "#{api_root}/#{resource_name}/search",
+ params: {},
+ resource_class: self,
+ method: :post,
+ results_param: results_param
+ )
+ end
+
+ # Filter resources - allows chaining
+ #
+ # This method allows searching for resources by passing a hash with special suffixes
+ # on the keys to define different comparison operators.
+ #
+ # 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.
+ #
+ # If value is an array the 'IN' operator will be used
+ #
+ # Otherwise 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
+ #
+ # filters - [Hash] The query for searching.
+ # each key represents a property and may have suffixes for the comparison
+ # (e.g., `{ email_contains: 'example.org', age_gt: 30 }`).
+
+ # 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
+ def where(filters = {})
+ all.where!(filters)
+ end
+
+ # Select which properties to return from the api - allows chaining
+ def select(*properties)
+ all.select(*properties)
+ end
+
# 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
#
@@ -51,36 +109,39 @@
# contact = Hubspot::Contact.find(1)
# contact = Hubspot::Contact.find(1, properties: %w[email firstname lastname custom_field])
#
# Returns An instance of the resource.
def find(id, properties: nil)
- all_properties = build_property_list(properties)
- if all_properties.is_a?(Array) && !all_properties.empty?
- params = { query: { properties: all_properties } }
- end
- response = get("#{api_root}/#{resource_name}/#{id}", params || {})
+ response = response_for_find_by_id(id, properties: properties)
+ return if response.not_found?
+
instantiate_from_response(response)
end
+ def find!(id, properties: nil)
+ response = response_for_find_by_id(id, properties: properties)
+ instantiate_from_response(response)
+ end
+
# 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", "john@example.com", properties)
+ # contact = Hubspot::Contact.find_by("email", "john@example.com", properties: properties)
#
# Returns An instance of the resource.
- def find_by(property, value, properties = nil)
- params = { idProperty: property }
+ 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
- all_properties = build_property_list(properties)
- params[:properties] = all_properties unless all_properties.empty?
-
- response = get("#{api_root}/#{resource_name}/#{value}", query: params)
+ def find_by!(property, value, properties: nil)
+ response = response_for_find_by_property(property, value, properties: properties)
instantiate_from_response(response)
end
# Creates a new resource with the given parameters.
#
@@ -229,21 +290,10 @@
# Returns [Hubspot::Property] A hubspot property
def property(property_name)
properties.detect { |prop| prop.name == property_name }
end
- # Simplified search interface
- OPERATOR_MAP = {
- '_contains' => 'CONTAINS_TOKEN',
- '_gt' => 'GT',
- '_lt' => 'LT',
- '_gte' => 'GTE',
- '_lte' => 'LTE',
- '_neq' => 'NEQ',
- '_in' => 'IN'
- }.freeze
-
# rubocop:disable Metrics/MethodLength
# 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
@@ -285,15 +335,15 @@
# properties: ["email", "firstname", "lastname"],
# page_size: 50
# )
#
# Returns [PagedCollection] A paged collection of results that can be iterated over.
- def search(query:, properties: [], page_size: 100)
+ def search(query, properties: [], page_size: 200)
search_body = {}
# Add properties if specified
- search_body[:properties] = properties unless properties.empty?
+ 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
@@ -309,11 +359,12 @@
# Perform the search and return a PagedCollection
PagedCollection.new(
url: "#{api_root}/#{resource_name}/search",
params: search_body,
resource_class: self,
- method: :post
+ method: :post,
+ results_param: results_param
)
end
# rubocop:enable Metrics/MethodLength
# Define the resource name based on the class
@@ -339,51 +390,43 @@
# object hierarchy
def api_root
'/crm/v3/objects'
end
+ # In the response from the api the resources returned in this key
+ def results_param
+ 'results'
+ end
+
def list_page_uri
"#{api_root}/#{resource_name}"
end
- # Instantiate a single resource object from the response
- def instantiate_from_response(response)
- data = handle_response(response)
- new(data) # Passing full response data to initialize
- end
-
- # Convert simple filters to HubSpot's filterGroups format
- def build_filter_groups(filters)
- filter_groups = [{ filters: [] }]
-
- filters.each do |key, value|
- filter = extract_property_and_operator(key, value)
- value_key = value.is_a?(Array) ? :values : :value
- filter[value_key] = value unless value.blank?
- filter_groups.first[:filters] << filter
+ def response_for_find_by_id(id, properties: nil)
+ all_properties = build_property_list(properties)
+ if all_properties.is_a?(Array) && !all_properties.empty?
+ params = { query: { properties: all_properties } }
end
- filter_groups
+ get("#{api_root}/#{resource_name}/#{id}", params || {})
end
- # Extract property name and operator from the key
- def extract_property_and_operator(key, value)
- return { propertyName: key.to_s, operator: 'NOT_HAS_PROPERTY' } if value.blank?
+ def response_for_find_by_property(property, value, properties: nil)
+ params = { idProperty: property }
- OPERATOR_MAP.each do |suffix, hubspot_operator|
- if key.to_s.end_with?(suffix)
- return {
- propertyName: key.to_s.sub(suffix, ''),
- operator: hubspot_operator
- }
- end
- end
+ all_properties = build_property_list(properties)
+ params[:properties] = all_properties unless all_properties.empty?
- # Default to 'EQ' operator if no suffix is found
- { propertyName: key.to_s, operator: 'EQ' }
+ get("#{api_root}/#{resource_name}/#{value}", query: params)
end
+ # Instantiate a single resource object from the response
+ def instantiate_from_response(response)
+ data = handle_response(response)
+ new(data) # Passing full response data to initialize
+ end
+
# Internal make a list of properties to request from the API
# will be merged with any required_properties defined on the class
def build_property_list(properties)
properties = [] unless properties.is_a?(Array)
raise 'Must be an array' unless required_properties.is_a?(Array)
@@ -416,11 +459,11 @@
# puts "Contact saved with hubspot id #{contact.id}"
#
# existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)
def initialize(data = {})
data.transform_keys!(&:to_s)
- @id = extract_id(data.delete('id'))
+ @id = extract_id(data.delete(api_id_field))
@properties = {}
@metadata = {}
if @id
initialize_from_api(data)
else
@@ -567,10 +610,14 @@
end
end
private
+ def api_id_field
+ 'id'
+ end
+
# Extract ID from data and convert to integer
def extract_id(id)
id&.to_i
end
@@ -612,12 +659,16 @@
@properties[attribute] if @properties.key?(attribute)
end
end
- # allows overwriting in other resource classes
def metadata_field?(key)
- METADATA_FIELDS.include?(key)
+ metadata_fields.include?(key)
+ end
+
+ # allows overwriting in other resource classes
+ def metadata_fields
+ METADATA_FIELDS
end
# Initialize a new object (no API response)
def initialize_new_object(data)
@properties = {}