ruby_hubspot_api 0.2.2 → 0.3.0

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
- 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