ruby_hubspot_api 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 52c9ee03d417a29792dbd370399982a03662cb621c0ab3e5ec0a2abd7de89ac5
4
- data.tar.gz: aae2926993d4b6dcf78b8e728c031631225f667c882f0b6d0710601ff62c0772
2
+ SHA1:
3
+ metadata.gz: 6052dfd71294e988924beeab1b89662673ceaf93
4
+ data.tar.gz: 4b2e295931295d639e473a9b139df03ec7df1554
5
5
  SHA512:
6
- metadata.gz: 6d2c9d82565c619a189bfc0780c914bb1b10cf769eb94b66e1e702fa5268fedadf7e3b9a188f6a1b0caa67c1b2d432e8f6401ffb890ca01efc768abc16ae3d3a
7
- data.tar.gz: e66a9d5daa8373529a3a2ee07c474d288d94327fc40d83ed08e050e1dbb83c8f89217c95083035396f212ed78b97348f1a9437889d2719cd9dbf49d4624cbce3
6
+ metadata.gz: caee0d8350205b6bcf45bb4ed4b5421f3fdaacaff87acfcb52c4845858bbafb664f70acbca1a564a34a99a166fd1072a838702d4bf4044b076cfd0b77e87f6a7
7
+ data.tar.gz: 28cbb5b1ea5ebc076d7177e8f4ee35f450bbe7fbb33224a31c55f0801fc566b705cf9b65faf95453b91154675139fb33cfce557ea74e06afd001a09f710190bf
@@ -41,7 +41,7 @@ jobs:
41
41
  env:
42
42
  CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
43
43
 
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 }}
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
@@ -1,7 +1,7 @@
1
1
  /.bundle/
2
2
  /coverage/
3
3
  /tmp/
4
-
4
+ *.gem
5
5
  .env
6
6
  # rspec failure tracking
7
7
  .rspec_status
data/CHANGELOG.md CHANGED
@@ -1,4 +1,61 @@
1
- ## v.0.2.0
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
- - #5 batch_updating
81
+ - bump version
25
82
 
26
83
  ## v0.1.2
27
84
 
28
- - initial setup
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_hubspot_api (0.2.2)
4
+ ruby_hubspot_api (0.3.0)
5
5
  httparty (>= 0.1, < 1.0)
6
6
 
7
7
  GEM
data/lib/hubspot/batch.rb CHANGED
@@ -31,6 +31,7 @@ module Hubspot
31
31
  CONTACT_LIMIT = 10
32
32
  DEFAULT_LIMIT = 100
33
33
 
34
+ # :nocov:
34
35
  def inspect
35
36
  "#<#{self.class.name} " \
36
37
  "@resource_count=#{@resources.size}, " \
@@ -38,16 +39,11 @@ module Hubspot
38
39
  "@resource_type=#{@resources.first&.resource_name}, " \
39
40
  "@responses_count=#{@responses.size}>"
40
41
  end
42
+ # :nocov:
41
43
 
42
44
  # rubocop:disable Lint/MissingSuper
43
45
  def initialize(resources = [], id_property: 'id', resource_matcher: nil)
44
- if resource_matcher
45
- unless resource_matcher.is_a?(Proc) && resource_matcher.arity == 2
46
- raise ArgumentError, 'resource_matcher must be a proc that accepts exactly 2 arguments'
47
- end
48
-
49
- @resource_matcher = resource_matcher
50
- end
46
+ validate_resource_matcher(resource_matcher)
51
47
 
52
48
  @resources = []
53
49
  @id_property = id_property # Set id_property for the batch (default: 'id')
@@ -67,11 +63,23 @@ module Hubspot
67
63
  end
68
64
 
69
65
  # Upsert method that calls save with upsert action
70
- def upsert
66
+ def upsert(resource_matcher: nil)
67
+ validate_resource_matcher(resource_matcher)
68
+
71
69
  validate_upsert_conditions
72
70
  save(action: 'upsert')
73
71
  end
74
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
+
75
83
  # Archive method
76
84
  def archive
77
85
  save(action: 'archive')
@@ -93,15 +101,29 @@ module Hubspot
93
101
  end
94
102
 
95
103
  def add_resource(resource)
96
- if @resources.any? && @resources.first.resource_name != resource.resource_name
97
- raise ArgumentError, 'All resources in a batch must be of the same type'
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)
98
110
  end
99
111
 
100
112
  @resources << resource
101
113
  end
102
114
 
115
+ def any_changes?
116
+ @resources.any?(&:changes?)
117
+ end
118
+
103
119
  private
104
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
+
105
127
  # rubocop:disable Metrics/MethodLength
106
128
  def save(action: 'update')
107
129
  @action = action
@@ -280,13 +302,15 @@ module Hubspot
280
302
  end
281
303
 
282
304
  class << self
283
- def read(object_class, object_ids = [], id_property: 'id')
305
+ def read(object_class, object_ids = [], properties: [], id_property: 'id')
284
306
  unless object_class < Hubspot::Resource
285
307
  raise ArgumentError, 'Must be a valid Hubspot resource class'
286
308
  end
287
309
 
288
310
  # fetch all the matching resources with paging handled
289
- resources = object_class.batch_read(object_ids, id_property: id_property).all
311
+ resources = object_class.batch_read(object_ids,
312
+ properties: properties,
313
+ id_property: id_property).all
290
314
 
291
315
  # return instance of Hubspot::Batch with the resources set
292
316
  new(resources, id_property: id_property)
@@ -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 CHANGED
@@ -4,11 +4,13 @@ module Hubspot
4
4
  class Form < Resource
5
5
  METADATA_FIELDS = %w[createdAt updatedAt archived].freeze
6
6
 
7
+ # :nocov:
7
8
  def inspect
8
9
  "#<#{self.class.name} " \
9
10
  "@name=#{name}, " \
10
11
  "@fieldGroups=#{respond_to?('fieldGroups') ? fieldGroups.size : '-'}>"
11
12
  end
13
+ # :nocov:
12
14
 
13
15
  class << self
14
16
  def api_root
@@ -18,9 +20,9 @@ module Hubspot
18
20
 
19
21
  private
20
22
 
21
- # Extract ID from data and leave as a string
22
- def extract_id(data)
23
- data.delete('id')
23
+ # dont convert (from string)
24
+ def extract_id(id)
25
+ id
24
26
  end
25
27
  end
26
28
  end
@@ -10,7 +10,7 @@ module Hubspot
10
10
 
11
11
  MAX_LIMIT = 100 # HubSpot max items per page
12
12
 
13
- # customised inspect
13
+ # :nocov:
14
14
  def inspect
15
15
  "#<#{self.class.name} " \
16
16
  "@url=#{@url.inspect}, " \
@@ -18,6 +18,7 @@ module Hubspot
18
18
  "@resource_class=#{@resource_class.inspect}, " \
19
19
  "@object_ids_count=#{@object_ids.size}>"
20
20
  end
21
+ # :nocov:
21
22
 
22
23
  # rubocop:disable Lint/MissingSuper
23
24
  def initialize(url:, params: {}, resource_class: nil, object_ids: [])
@@ -45,12 +45,14 @@ module Hubspot
45
45
  # Find a resource by ID and return an instance of the class
46
46
  #
47
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
48
49
  #
49
50
  # Example:
50
51
  # contact = Hubspot::Contact.find(1)
52
+ # contact = Hubspot::Contact.find(1, properties: %w[email firstname lastname custom_field])
51
53
  #
52
54
  # Returns An instance of the resource.
53
- def find(id, properties = nil)
55
+ def find(id, properties: nil)
54
56
  all_properties = build_property_list(properties)
55
57
  if all_properties.is_a?(Array) && !all_properties.empty?
56
58
  params = { query: { properties: all_properties } }
@@ -181,8 +183,8 @@ module Hubspot
181
183
  # Hubspot::Contact.batch_read_all(hubspot_contact_ids)
182
184
  #
183
185
  # Returns [Hubspot::Batch] A batch of resources that can be operated on further
184
- def batch_read_all(object_ids = [], id_property: 'id')
185
- Hubspot::Batch.read(self, object_ids, id_property: id_property)
186
+ def batch_read_all(object_ids = [], properties: [], id_property: 'id')
187
+ Hubspot::Batch.read(self, object_ids, properties: properties, id_property: id_property)
186
188
  end
187
189
 
188
190
  # Retrieve the complete list of properties for this resource class
@@ -392,8 +394,13 @@ module Hubspot
392
394
 
393
395
  # Public: Initialize a resouce
394
396
  #
395
- # data - [2D Hash, nested Hash] data to initialise the resourse This can be either:
396
- # - A Simple 2D Hash, key value pairs of property => value (for the create option)
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
+ #
397
404
  # - A structured hash consisting of { id: <hs_object_id>, properties: {}, ... }
398
405
  # This is the same structure as per the API, and can be rebuilt if you store the id
399
406
  # of the object against your own data
@@ -409,7 +416,7 @@ module Hubspot
409
416
  # existing_contact = Hubspot::Contact.new(id: hubspot_id, properties: contact.to_hubspot)
410
417
  def initialize(data = {})
411
418
  data.transform_keys!(&:to_s)
412
- @id = extract_id(data)
419
+ @id = extract_id(data.delete('id'))
413
420
  @properties = {}
414
421
  @metadata = {}
415
422
  if @id
@@ -520,15 +527,8 @@ module Hubspot
520
527
  if method_name.end_with?('=')
521
528
  attribute = method_name.chomp('=')
522
529
  new_value = args.first
523
-
524
- # Track changes only if the value has actually changed
525
- if @properties[attribute] != new_value
526
- @changes[attribute] = new_value
527
- else
528
- @changes.delete(attribute) # Remove from changes if it reverts to the original value
529
- end
530
-
531
- return new_value
530
+ add_accessors attribute
531
+ return send("#{attribute}=", new_value)
532
532
  # Handle getters
533
533
  else
534
534
  return @changes[method_name] if @changes.key?(method_name)
@@ -547,35 +547,70 @@ module Hubspot
547
547
  @properties.key?(property_name) || @changes.key?(property_name) || super
548
548
  end
549
549
 
550
- private
551
-
552
- # Extract ID from data and convert to integer
553
- def extract_id(data)
554
- data['id'] ? data['id'].to_i : nil
555
- end
556
-
557
550
  # Initialize from API response, separating metadata from properties
558
551
  def initialize_from_api(data)
552
+ @changes = data.delete('changes')&.transform_keys!(&:to_s) || {}
553
+
559
554
  if data['properties']
560
555
  @metadata = data.reject { |key, _v| key == 'properties' }
561
556
  handle_properties(data['properties'])
562
557
  else
563
558
  handle_properties(data)
564
559
  end
560
+ end
565
561
 
566
- @changes = {}
562
+ private
563
+
564
+ # Extract ID from data and convert to integer
565
+ def extract_id(id)
566
+ id&.to_i
567
567
  end
568
568
 
569
569
  def handle_properties(properties_data)
570
- properties_data.each do |key, value|
571
- if METADATA_FIELDS.include?(key)
572
- @metadata[key] = value
570
+ properties_data.each do |attribute, value|
571
+ if metadata_field?(attribute)
572
+ @metadata[attribute.to_s] = value
573
573
  else
574
- @properties[key] = value
574
+ add_accessors attribute.to_s
575
+ @properties[attribute.to_s] = value
575
576
  end
576
577
  end
577
578
  end
578
579
 
580
+ def add_accessors(attribute)
581
+ add_accessors_setter(attribute)
582
+ add_accessors_getter(attribute)
583
+ end
584
+
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
591
+ else
592
+ @changes.delete(attribute) # Remove from changes if it reverts to the original value
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)
606
+ end
607
+ end
608
+
609
+ # allows overwriting in other resource classes
610
+ def metadata_field?(key)
611
+ METADATA_FIELDS.include?(key)
612
+ end
613
+
579
614
  # Initialize a new object (no API response)
580
615
  def initialize_new_object(data)
581
616
  @properties = {}
@@ -583,11 +618,6 @@ module Hubspot
583
618
  @metadata = {}
584
619
  end
585
620
 
586
- # Extract metadata from data, excluding properties
587
- def extract_metadata(data)
588
- data.reject { |key, _| key == 'properties' }
589
- end
590
-
591
621
  # Create a new resource
592
622
  def create_new
593
623
  created_resource = self.class.create(@changes)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hubspot
4
- VERSION = '0.2.2'
4
+ VERSION = '0.3.0'
5
5
  end
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.2.2
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-03 00:00:00.000000000 Z
11
+ date: 2024-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -254,7 +254,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
254
254
  - !ruby/object:Gem::Version
255
255
  version: '0'
256
256
  requirements: []
257
- rubygems_version: 3.2.3
257
+ rubyforge_project:
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