hubspot-ruby 0.4.0 → 0.5.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 +4 -4
- data/Gemfile +1 -1
- data/Guardfile +1 -1
- data/README.md +60 -5
- data/hubspot-ruby.gemspec +2 -2
- data/lib/hubspot-ruby.rb +1 -0
- data/lib/hubspot/company.rb +70 -15
- data/lib/hubspot/config.rb +20 -3
- data/lib/hubspot/connection.rb +5 -1
- data/lib/hubspot/contact.rb +24 -6
- data/lib/hubspot/deal.rb +39 -4
- data/lib/hubspot/engagement.rb +24 -4
- data/lib/hubspot/form.rb +1 -1
- data/lib/hubspot/oauth.rb +50 -0
- data/lib/hubspot/subscription.rb +22 -23
- data/spec/lib/hubspot/company_spec.rb +57 -3
- data/spec/lib/hubspot/contact_list_spec.rb +3 -3
- data/spec/lib/hubspot/contact_spec.rb +26 -0
- data/spec/lib/hubspot/engagement_spec.rb +14 -0
- data/spec/lib/hubspot/form_spec.rb +12 -0
- data/spec/live/companies_integration_spec.rb +9 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ed5f2aa1fbcdc87f14f1197d9b78dad4380f057
|
4
|
+
data.tar.gz: 2184008548937dbbbb103f30d3eb96c11f3c0d8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20f5233aa72b00ce366785e64b0ac385c8a48d86e2c79df9196958b668c573e32018cb2924e86c0216d489049535ce443dad9e7227a9b35941160e938f22023c
|
7
|
+
data.tar.gz: bea00d2e9baf596c71a2fcbaeaaa89b081dbbec0b900f03226bdfbbef8fc7ebab1bb0618739a660e1ccf624199341607f79412a97877ed87d1fde0732dec2feb
|
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# A sample Guardfile
|
2
2
|
# More info at https://github.com/guard/guard#readme
|
3
3
|
|
4
|
-
guard :rspec do
|
4
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
5
5
|
watch(%r{^spec/.+_spec\.rb$})
|
6
6
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
7
|
watch('spec/spec_helper.rb') { "spec" }
|
data/README.md
CHANGED
@@ -14,18 +14,66 @@ Or with bundler,
|
|
14
14
|
gem "hubspot-ruby"
|
15
15
|
```
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
## Authentication with an API key
|
18
|
+
|
19
|
+
Before using the library, you must initialize it with your HubSpot API key. If you're using Rails, put this code in an initializer:
|
19
20
|
|
20
21
|
```ruby
|
21
22
|
Hubspot.configure(hapikey: "YOUR_API_KEY")
|
22
23
|
```
|
23
24
|
|
24
|
-
If you
|
25
|
+
If you have a HubSpot account, you can get your api key by logging in and visiting this url: https://app.hubspot.com/keys/get
|
26
|
+
|
27
|
+
## Authentication with OAuth 2.0
|
28
|
+
|
29
|
+
Configure the library with the client ID and secret from your [HubSpot App](https://developers.hubspot.com/docs/faq/how-do-i-create-an-app-in-hubspot)
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Hubspot.configure(
|
33
|
+
client_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
34
|
+
client_secret: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
35
|
+
redirect_uri: "https://myapp.com/oauth")
|
36
|
+
```
|
37
|
+
|
38
|
+
To initiate an OAuth connection to your app, create a URL with the required scopes:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Hubspot::OAuth.authorize_url(["contacts", "content"])
|
42
|
+
```
|
43
|
+
|
44
|
+
After the user accepts the scopes and installs the integration with their HubSpot account, they will be redirected to the URI requested with the query parameter `code` appended to the URL. `code` can then be passed to HubSpot to generate an access token:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
Hubspot::OAuth.create(params[:code])
|
48
|
+
```
|
49
|
+
|
50
|
+
To use the returned `access_token` string for authentication, you'll need to update the configuration:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Hubspot.configure(
|
54
|
+
client_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
55
|
+
client_secret: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
56
|
+
redirect_uri: "https://myapp.com/oauth",
|
57
|
+
access_token: access_token)
|
58
|
+
```
|
59
|
+
|
60
|
+
Now all requests will use the provided access token when querying the API:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
Hubspot::Contact.all
|
64
|
+
```
|
65
|
+
|
66
|
+
### Refreshing the token
|
67
|
+
|
68
|
+
When you create a HubSpot OAuth token, it will have an expiration date given by the `expires_in` field returned from the create API. If you with to continue using the token without needing to create another, you'll need to refresh the token:
|
25
69
|
|
26
|
-
|
70
|
+
```ruby
|
71
|
+
Hubspot::OAuth.refresh(refresh_token)
|
72
|
+
```
|
27
73
|
|
28
|
-
|
74
|
+
### A note on OAuth credentials
|
75
|
+
|
76
|
+
At this time, OAuth tokens are configured globally rather than on a per-connection basis.
|
29
77
|
|
30
78
|
## Usage
|
31
79
|
|
@@ -65,6 +113,13 @@ contact.update!({firstname: "First", lastname: "Last"})
|
|
65
113
|
```ruby
|
66
114
|
Hubspot::Contact.create_or_update!([{vid: '12345', firstname: 'First', lastname: 'Last'}])
|
67
115
|
```
|
116
|
+
|
117
|
+
### Create a deal
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Hubspot::Deal.create!(nil, [company.vid], [contact.vid], pipeline: 'default', dealstage: 'initial_contact')
|
121
|
+
```
|
122
|
+
|
68
123
|
## Contributing to hubspot-ruby
|
69
124
|
|
70
125
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
data/hubspot-ruby.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "hubspot-ruby"
|
3
|
-
s.version = "0.
|
3
|
+
s.version = "0.5.0"
|
4
4
|
s.require_paths = ["lib"]
|
5
5
|
s.authors = ["Andrew DiMichele", "Chris Bisnett"]
|
6
6
|
s.description = "hubspot-ruby is a wrapper for the HubSpot REST API"
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_development_dependency("rake", "~> 11.0")
|
20
20
|
s.add_development_dependency("rspec", "~> 2.0")
|
21
21
|
s.add_development_dependency("rr")
|
22
|
-
s.add_development_dependency("webmock"
|
22
|
+
s.add_development_dependency("webmock")
|
23
23
|
s.add_development_dependency("vcr")
|
24
24
|
s.add_development_dependency("rdoc")
|
25
25
|
s.add_development_dependency("bundler")
|
data/lib/hubspot-ruby.rb
CHANGED
data/lib/hubspot/company.rb
CHANGED
@@ -5,15 +5,17 @@ module Hubspot
|
|
5
5
|
# {http://developers.hubspot.com/docs/methods/companies/companies-overview}
|
6
6
|
#
|
7
7
|
class Company
|
8
|
-
CREATE_COMPANY_PATH
|
9
|
-
RECENTLY_CREATED_COMPANIES_PATH
|
10
|
-
RECENTLY_MODIFIED_COMPANIES_PATH
|
11
|
-
GET_COMPANY_BY_ID_PATH
|
12
|
-
GET_COMPANY_BY_DOMAIN_PATH
|
13
|
-
UPDATE_COMPANY_PATH
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
CREATE_COMPANY_PATH = "/companies/v2/companies/"
|
9
|
+
RECENTLY_CREATED_COMPANIES_PATH = "/companies/v2/companies/recent/created"
|
10
|
+
RECENTLY_MODIFIED_COMPANIES_PATH = "/companies/v2/companies/recent/modified"
|
11
|
+
GET_COMPANY_BY_ID_PATH = "/companies/v2/companies/:company_id"
|
12
|
+
GET_COMPANY_BY_DOMAIN_PATH = "/companies/v2/domains/:domain/companies"
|
13
|
+
UPDATE_COMPANY_PATH = "/companies/v2/companies/:company_id"
|
14
|
+
GET_COMPANY_CONTACT_VIDS_PATH = "/companies/v2/companies/:company_id/vids"
|
15
|
+
ADD_CONTACT_TO_COMPANY_PATH = "/companies/v2/companies/:company_id/contacts/:vid"
|
16
|
+
DESTROY_COMPANY_PATH = "/companies/v2/companies/:company_id"
|
17
|
+
GET_COMPANY_CONTACTS_PATH = "/companies/v2/companies/:company_id/contacts"
|
18
|
+
BATCH_UPDATE_PATH = "/companies/v1/batch-async/update"
|
17
19
|
|
18
20
|
class << self
|
19
21
|
# Find all companies by created date (descending)
|
@@ -96,6 +98,46 @@ module Hubspot
|
|
96
98
|
response = Hubspot::Connection.post_json(CREATE_COMPANY_PATH, params: {}, body: post_data )
|
97
99
|
new(response)
|
98
100
|
end
|
101
|
+
|
102
|
+
# Updates the properties of companies
|
103
|
+
# NOTE: Up to 100 companies can be updated in a single request. There is no limit to the number of properties that can be updated per company.
|
104
|
+
# {https://developers.hubspot.com/docs/methods/companies/batch-update-companies}
|
105
|
+
# Returns a 202 Accepted response on success.
|
106
|
+
def batch_update!(companies)
|
107
|
+
query = companies.map do |company|
|
108
|
+
company_hash = company.with_indifferent_access
|
109
|
+
if company_hash[:vid]
|
110
|
+
# For consistency - Since vid has been used everywhere.
|
111
|
+
company_param = {
|
112
|
+
objectId: company_hash[:vid],
|
113
|
+
properties: Hubspot::Utils.hash_to_properties(company_hash.except(:vid).stringify_keys!, key_name: 'name'),
|
114
|
+
}
|
115
|
+
elsif company_hash[:objectId]
|
116
|
+
company_param = {
|
117
|
+
objectId: company_hash[:objectId],
|
118
|
+
properties: Hubspot::Utils.hash_to_properties(company_hash.except(:objectId).stringify_keys!, key_name: 'name'),
|
119
|
+
}
|
120
|
+
else
|
121
|
+
raise Hubspot::InvalidParams, 'expecting vid or objectId for company'
|
122
|
+
end
|
123
|
+
company_param
|
124
|
+
end
|
125
|
+
Hubspot::Connection.post_json(BATCH_UPDATE_PATH, params: {}, body: query)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Adds contact to a company
|
129
|
+
# {http://developers.hubspot.com/docs/methods/companies/add_contact_to_company}
|
130
|
+
# @param company_vid [Integer] The ID of a company to add a contact to
|
131
|
+
# @param contact_vid [Integer] contact id to add
|
132
|
+
# @return parsed response
|
133
|
+
def add_contact!(company_vid, contact_vid)
|
134
|
+
Hubspot::Connection.put_json(ADD_CONTACT_TO_COMPANY_PATH,
|
135
|
+
params: {
|
136
|
+
company_id: company_vid,
|
137
|
+
vid: contact_vid,
|
138
|
+
},
|
139
|
+
body: nil)
|
140
|
+
end
|
99
141
|
end
|
100
142
|
|
101
143
|
attr_reader :properties
|
@@ -122,6 +164,24 @@ module Hubspot
|
|
122
164
|
self
|
123
165
|
end
|
124
166
|
|
167
|
+
# Gets ALL contact vids of a company
|
168
|
+
# May make many calls if the company has a mega-ton of contacts
|
169
|
+
# {http://developers.hubspot.com/docs/methods/companies/get_company_contacts_by_id}
|
170
|
+
# @return [Array] contact vids
|
171
|
+
def get_contact_vids
|
172
|
+
vid_offset = nil
|
173
|
+
vids = []
|
174
|
+
loop do
|
175
|
+
data = Hubspot::Connection.get_json(GET_COMPANY_CONTACT_VIDS_PATH,
|
176
|
+
company_id: vid,
|
177
|
+
vidOffset: vid_offset)
|
178
|
+
vids += data['vids']
|
179
|
+
return vids unless data['hasMore']
|
180
|
+
vid_offset = data['vidOffset']
|
181
|
+
end
|
182
|
+
vids # this statement will never be executed.
|
183
|
+
end
|
184
|
+
|
125
185
|
# Adds contact to a company
|
126
186
|
# {http://developers.hubspot.com/docs/methods/companies/add_contact_to_company}
|
127
187
|
# @param id [Integer] contact id to add
|
@@ -132,12 +192,7 @@ module Hubspot
|
|
132
192
|
else
|
133
193
|
contact_or_vid
|
134
194
|
end
|
135
|
-
|
136
|
-
params: {
|
137
|
-
company_id: vid,
|
138
|
-
vid: contact_vid,
|
139
|
-
},
|
140
|
-
body: nil)
|
195
|
+
self.class.add_contact!(vid, contact_vid)
|
141
196
|
self
|
142
197
|
end
|
143
198
|
|
data/lib/hubspot/config.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'logger'
|
2
|
+
require 'hubspot/connection'
|
2
3
|
|
3
4
|
module Hubspot
|
4
5
|
class Config
|
5
6
|
|
6
|
-
CONFIG_KEYS = [
|
7
|
-
|
7
|
+
CONFIG_KEYS = [
|
8
|
+
:hapikey, :base_url, :portal_id, :logger, :access_token, :client_id,
|
9
|
+
:client_secret, :redirect_uri
|
10
|
+
]
|
11
|
+
DEFAULT_LOGGER = Logger.new(nil)
|
8
12
|
|
9
13
|
class << self
|
10
14
|
attr_accessor *CONFIG_KEYS
|
@@ -14,7 +18,18 @@ module Hubspot
|
|
14
18
|
@hapikey = config["hapikey"]
|
15
19
|
@base_url = config["base_url"] || "https://api.hubapi.com"
|
16
20
|
@portal_id = config["portal_id"]
|
17
|
-
@logger = config[
|
21
|
+
@logger = config["logger"] || DEFAULT_LOGGER
|
22
|
+
@access_token = config["access_token"]
|
23
|
+
@client_id = config["client_id"] if config["client_id"].present?
|
24
|
+
@client_secret = config["client_secret"] if config["client_secret"].present?
|
25
|
+
@redirect_uri = config["redirect_uri"] if config["redirect_uri"].present?
|
26
|
+
|
27
|
+
unless access_token.present? ^ hapikey.present?
|
28
|
+
Hubspot::ConfigurationError.new("You must provide either an access_token or an hapikey")
|
29
|
+
end
|
30
|
+
if access_token.present?
|
31
|
+
Hubspot::Connection.headers("Authorization" => "Bearer #{access_token}")
|
32
|
+
end
|
18
33
|
self
|
19
34
|
end
|
20
35
|
|
@@ -23,6 +38,8 @@ module Hubspot
|
|
23
38
|
@base_url = "https://api.hubapi.com"
|
24
39
|
@portal_id = nil
|
25
40
|
@logger = DEFAULT_LOGGER
|
41
|
+
@access_token = nil
|
42
|
+
Hubspot::Connection.headers({})
|
26
43
|
end
|
27
44
|
|
28
45
|
def ensure!(*params)
|
data/lib/hubspot/connection.rb
CHANGED
@@ -45,7 +45,11 @@ module Hubspot
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def generate_url(path, params={}, options={})
|
48
|
-
Hubspot::Config.
|
48
|
+
if Hubspot::Config.access_token.present?
|
49
|
+
options[:hapikey] = false
|
50
|
+
else
|
51
|
+
Hubspot::Config.ensure! :hapikey
|
52
|
+
end
|
49
53
|
path = path.clone
|
50
54
|
params = params.clone
|
51
55
|
base_url = options[:base_url] || Hubspot::Config.base_url
|
data/lib/hubspot/contact.rb
CHANGED
@@ -16,6 +16,7 @@ module Hubspot
|
|
16
16
|
GET_CONTACTS_BY_UTK_PATH = '/contacts/v1/contact/utks/batch'
|
17
17
|
UPDATE_CONTACT_PATH = '/contacts/v1/contact/vid/:contact_id/profile'
|
18
18
|
DESTROY_CONTACT_PATH = '/contacts/v1/contact/vid/:contact_id'
|
19
|
+
MERGE_CONTACT_PATH = '/contacts/v1/contact/merge-vids/:contact_id'
|
19
20
|
CONTACTS_PATH = '/contacts/v1/lists/all/contacts/all'
|
20
21
|
RECENTLY_UPDATED_PATH = '/contacts/v1/lists/recently_updated/contacts/recent'
|
21
22
|
RECENTLY_CREATED_PATH = '/contacts/v1/lists/all/contacts/recent'
|
@@ -69,13 +70,16 @@ module Hubspot
|
|
69
70
|
def create_or_update!(contacts)
|
70
71
|
query = contacts.map do |ch|
|
71
72
|
contact_hash = ch.with_indifferent_access
|
72
|
-
contact_param = {
|
73
|
-
properties: Hubspot::Utils.hash_to_properties(contact_hash.except(:vid))
|
74
|
-
}
|
75
73
|
if contact_hash[:vid]
|
76
|
-
contact_param
|
74
|
+
contact_param = {
|
75
|
+
vid: contact_hash[:vid],
|
76
|
+
properties: Hubspot::Utils.hash_to_properties(contact_hash.except(:vid))
|
77
|
+
}
|
77
78
|
elsif contact_hash[:email]
|
78
|
-
contact_param
|
79
|
+
contact_param = {
|
80
|
+
email: contact_hash[:email],
|
81
|
+
properties: Hubspot::Utils.hash_to_properties(contact_hash.except(:email))
|
82
|
+
}
|
79
83
|
else
|
80
84
|
raise Hubspot::InvalidParams, 'expecting vid or email for contact'
|
81
85
|
end
|
@@ -146,15 +150,29 @@ module Hubspot
|
|
146
150
|
response = Hubspot::Connection.get_json(QUERY_PATH, { q: query, count: count, offset: offset })
|
147
151
|
response.merge("contacts" => response["contacts"].map { |contact_hash| new(contact_hash) })
|
148
152
|
end
|
153
|
+
|
154
|
+
# Merge two contacts
|
155
|
+
# Properties of the secondary contact will be applied to the primary contact
|
156
|
+
# The main email will be the primary contact's
|
157
|
+
# The secondary email still won't be available for new contacts
|
158
|
+
# {https://developers.hubspot.com/docs/methods/contacts/merge-contacts}
|
159
|
+
def merge!(primary_contact_vid, secondary_contact_vid)
|
160
|
+
Hubspot::Connection.post_json(
|
161
|
+
MERGE_CONTACT_PATH,
|
162
|
+
params: { contact_id: primary_contact_vid, no_parse: true },
|
163
|
+
body: { vidToMerge: secondary_contact_vid }
|
164
|
+
)
|
165
|
+
end
|
149
166
|
end
|
150
167
|
|
151
168
|
attr_reader :properties, :vid, :is_new
|
152
|
-
attr_reader :is_contact
|
169
|
+
attr_reader :is_contact, :list_memberships
|
153
170
|
|
154
171
|
def initialize(response_hash)
|
155
172
|
props = response_hash['properties']
|
156
173
|
@properties = Hubspot::Utils.properties_to_hash(props) unless props.blank?
|
157
174
|
@is_contact = response_hash["is-contact"]
|
175
|
+
@list_memberships = response_hash["list-memberships"] || []
|
158
176
|
@vid = response_hash['vid']
|
159
177
|
end
|
160
178
|
|
data/lib/hubspot/deal.rb
CHANGED
@@ -7,6 +7,7 @@ module Hubspot
|
|
7
7
|
# {http://developers.hubspot.com/docs/methods/deals/deals_overview}
|
8
8
|
#
|
9
9
|
class Deal
|
10
|
+
ALL_DEALS_PATH = "/deals/v1/deal/paged"
|
10
11
|
CREATE_DEAL_PATH = "/deals/v1/deal"
|
11
12
|
DEAL_PATH = "/deals/v1/deal/:deal_id"
|
12
13
|
RECENT_UPDATED_PATH = "/deals/v1/deal/recent/modified"
|
@@ -47,13 +48,26 @@ module Hubspot
|
|
47
48
|
object_ids = (company_ids.any? ? company_ids : vids).join('&id=')
|
48
49
|
Hubspot::Connection.put_json(ASSOCIATE_DEAL_PATH, params: { deal_id: deal_id, OBJECTTYPE: objecttype, objectId: object_ids}, body: {})
|
49
50
|
end
|
50
|
-
|
51
|
+
|
51
52
|
|
52
53
|
def find(deal_id)
|
53
54
|
response = Hubspot::Connection.get_json(DEAL_PATH, { deal_id: deal_id })
|
54
55
|
new(response)
|
55
56
|
end
|
56
57
|
|
58
|
+
def all(opts = {})
|
59
|
+
path = ALL_DEALS_PATH
|
60
|
+
|
61
|
+
opts[:includeAssociations] = true # Needed for initialize to work
|
62
|
+
response = Hubspot::Connection.get_json(path, opts)
|
63
|
+
|
64
|
+
result = {}
|
65
|
+
result['deals'] = response['deals'].map { |d| new(d) }
|
66
|
+
result['offset'] = response['offset']
|
67
|
+
result['hasMore'] = response['hasMore']
|
68
|
+
return result
|
69
|
+
end
|
70
|
+
|
57
71
|
# Find recent updated deals.
|
58
72
|
# {http://developers.hubspot.com/docs/methods/deals/get_deals_modified}
|
59
73
|
# @param count [Integer] the amount of deals to return.
|
@@ -62,18 +76,39 @@ module Hubspot
|
|
62
76
|
response = Hubspot::Connection.get_json(RECENT_UPDATED_PATH, opts)
|
63
77
|
response['results'].map { |d| new(d) }
|
64
78
|
end
|
65
|
-
|
79
|
+
|
66
80
|
# Find all deals associated to a company
|
67
81
|
# {http://developers.hubspot.com/docs/methods/deals/get-associated-deals}
|
68
82
|
# @param company [Hubspot::Company] the company
|
69
83
|
# @return [Array] Array of Hubspot::Deal records
|
70
84
|
def find_by_company(company)
|
85
|
+
find_by_association company
|
86
|
+
end
|
87
|
+
|
88
|
+
# Find all deals associated to a contact
|
89
|
+
# {http://developers.hubspot.com/docs/methods/deals/get-associated-deals}
|
90
|
+
# @param contact [Hubspot::Contact] the contact
|
91
|
+
# @return [Array] Array of Hubspot::Deal records
|
92
|
+
def find_by_contact(contact)
|
93
|
+
find_by_association contact
|
94
|
+
end
|
95
|
+
|
96
|
+
# Find all deals associated to a contact or company
|
97
|
+
# {http://developers.hubspot.com/docs/methods/deals/get-associated-deals}
|
98
|
+
# @param object [Hubspot::Contact || Hubspot::Company] a contact or company
|
99
|
+
# @return [Array] Array of Hubspot::Deal records
|
100
|
+
def find_by_association(object)
|
71
101
|
path = ASSOCIATED_DEAL_PATH
|
72
|
-
|
102
|
+
objectType = case object
|
103
|
+
when Hubspot::Company then :company
|
104
|
+
when Hubspot::Contact then :contact
|
105
|
+
else raise(Hubspot::InvalidParams, "Instance type not supported")
|
106
|
+
end
|
107
|
+
|
108
|
+
params = { objectType: objectType, objectId: object.vid }
|
73
109
|
response = Hubspot::Connection.get_json(path, params)
|
74
110
|
response["results"].map { |deal_id| find(deal_id) }
|
75
111
|
end
|
76
|
-
|
77
112
|
end
|
78
113
|
|
79
114
|
# Archives the contact in hubspot
|
data/lib/hubspot/engagement.rb
CHANGED
@@ -9,17 +9,20 @@ module Hubspot
|
|
9
9
|
class Engagement
|
10
10
|
CREATE_ENGAGMEMENT_PATH = '/engagements/v1/engagements'
|
11
11
|
ENGAGEMENT_PATH = '/engagements/v1/engagements/:engagement_id'
|
12
|
+
ASSOCIATE_ENGAGEMENT_PATH = '/engagements/v1/engagements/:engagement_id/associations/:object_type/:object_vid'
|
12
13
|
GET_ASSOCIATED_ENGAGEMENTS = '/engagements/v1/engagements/associated/:objectType/:objectId/paged'
|
13
14
|
|
14
15
|
attr_reader :id
|
15
16
|
attr_reader :engagement
|
16
17
|
attr_reader :associations
|
18
|
+
attr_reader :attachments
|
17
19
|
attr_reader :metadata
|
18
20
|
|
19
21
|
def initialize(response_hash)
|
20
22
|
|
21
23
|
@engagement = response_hash["engagement"]
|
22
24
|
@associations = response_hash["associations"]
|
25
|
+
@attachments = response_hash["attachments"]
|
23
26
|
@metadata = response_hash["metadata"]
|
24
27
|
@id = engagement["id"]
|
25
28
|
end
|
@@ -66,6 +69,20 @@ module Hubspot
|
|
66
69
|
end
|
67
70
|
engagements
|
68
71
|
end
|
72
|
+
|
73
|
+
# Associates an engagement with an object
|
74
|
+
# {https://developers.hubspot.com/docs/methods/engagements/associate_engagement}
|
75
|
+
# @param engagement_id [int] id of the engagement to associate
|
76
|
+
# @param object_type [string] one of contact, company, or deal
|
77
|
+
# @param object_vid [int] id of the contact, company, or deal to associate
|
78
|
+
def associate!(engagement_id, object_type, object_vid)
|
79
|
+
Hubspot::Connection.put_json(ASSOCIATE_ENGAGEMENT_PATH,
|
80
|
+
params: {
|
81
|
+
engagement_id: engagement_id,
|
82
|
+
object_type: object_type,
|
83
|
+
object_vid: object_vid
|
84
|
+
})
|
85
|
+
end
|
69
86
|
end
|
70
87
|
|
71
88
|
# Archives the engagement in hubspot
|
@@ -90,9 +107,10 @@ module Hubspot
|
|
90
107
|
# @return [Hubspot::Engagement] self
|
91
108
|
def update!(params)
|
92
109
|
data = {
|
93
|
-
engagement: engagement,
|
94
|
-
associations: associations,
|
95
|
-
|
110
|
+
engagement: params[:engagement] || engagement,
|
111
|
+
associations: params[:associations] || associations,
|
112
|
+
attachments: params[:attachments] || attachments,
|
113
|
+
metadata: params[:metadata] || metadata
|
96
114
|
}
|
97
115
|
|
98
116
|
Hubspot::Connection.put_json(ENGAGEMENT_PATH, params: { engagement_id: id }, body: data)
|
@@ -110,7 +128,7 @@ module Hubspot
|
|
110
128
|
end
|
111
129
|
|
112
130
|
class << self
|
113
|
-
def create!(contact_id, note_body, owner_id = nil)
|
131
|
+
def create!(contact_id, note_body, owner_id = nil, deal_id = nil)
|
114
132
|
data = {
|
115
133
|
engagement: {
|
116
134
|
type: 'NOTE'
|
@@ -125,6 +143,8 @@ module Hubspot
|
|
125
143
|
|
126
144
|
# if the owner id has been provided, append it to the engagement
|
127
145
|
data[:engagement][:owner_id] = owner_id if owner_id
|
146
|
+
# if the deal id has been provided, associate the note with the deal
|
147
|
+
data[:associations][:dealIds] = [deal_id] if deal_id
|
128
148
|
|
129
149
|
super(data)
|
130
150
|
end
|
data/lib/hubspot/form.rb
CHANGED
@@ -88,7 +88,7 @@ module Hubspot
|
|
88
88
|
|
89
89
|
def assign_properties(hash)
|
90
90
|
@guid = hash['guid']
|
91
|
-
@fields = hash['formFieldGroups'].inject([]){ |result, fg| result | fg['fields'] }
|
91
|
+
@fields = (hash['formFieldGroups'] || []).inject([]) { |result, fg| result | fg['fields'] }
|
92
92
|
@properties = hash
|
93
93
|
end
|
94
94
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Hubspot
|
4
|
+
class OAuth < Connection
|
5
|
+
include HTTParty
|
6
|
+
|
7
|
+
DEFAULT_OAUTH_HEADERS = {"Content-Type" => "application/x-www-form-urlencoded;charset=utf-8"}
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def refresh(token, params={}, options={})
|
11
|
+
oauth_post(token_url, { grant_type: "refresh_token", refresh_token: token }.merge(params),
|
12
|
+
options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create(code, params={}, options={})
|
16
|
+
oauth_post(token_url, { grant_type: "authorization_code", code: code }.merge(params),
|
17
|
+
options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def authorize_url(scopes, params={})
|
21
|
+
client_id = params[:client_id] || Hubspot::Config.client_id
|
22
|
+
redirect_uri = params[:redirect_uri] || Hubspot::Config.redirect_uri
|
23
|
+
scopes = Array.wrap(scopes)
|
24
|
+
|
25
|
+
"https://app.hubspot.com/oauth/authorize?client_id=#{client_id}&scope=#{scopes.join("%20")}&redirect_uri=#{redirect_uri}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def token_url
|
29
|
+
token_url = Hubspot::Config.base_url + "/oauth/v1/token"
|
30
|
+
end
|
31
|
+
|
32
|
+
def oauth_post(url, params, options={})
|
33
|
+
no_parse = options[:no_parse] || false
|
34
|
+
|
35
|
+
body = {
|
36
|
+
client_id: Hubspot::Config.client_id,
|
37
|
+
client_secret: Hubspot::Config.client_secret,
|
38
|
+
redirect_uri: Hubspot::Config.redirect_uri,
|
39
|
+
}.merge(params)
|
40
|
+
|
41
|
+
response = post(url, body: body, headers: DEFAULT_OAUTH_HEADERS)
|
42
|
+
log_request_and_response url, response, body
|
43
|
+
|
44
|
+
raise(Hubspot::RequestError.new(response)) unless response.success?
|
45
|
+
|
46
|
+
no_parse ? response : response.parsed_response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/hubspot/subscription.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
1
|
module Hubspot
|
2
2
|
class Subscription
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
SUBSCRIPTIONS_PATH = '/email/public/v1/subscriptions'
|
4
|
+
TIMELINE_PATH = '/email/public/v1/subscriptions/timeline'
|
5
|
+
SUBSCRIPTION_PATH = '/email/public/v1/subscriptions/:email_address'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
attr_reader :subscribed
|
8
|
+
attr_reader :marked_as_spam
|
9
|
+
attr_reader :bounced
|
10
|
+
attr_reader :status
|
11
|
+
attr_reader :subscription_statuses
|
12
12
|
|
13
|
-
|
14
|
-
@subscribed
|
15
|
-
@marked_as_spam
|
16
|
-
@bounced
|
17
|
-
@status
|
18
|
-
@subscription_statuses
|
13
|
+
def initialize(response_hash)
|
14
|
+
@subscribed = response_hash['subscribed']
|
15
|
+
@marked_as_spam = response_hash['markedAsSpam']
|
16
|
+
@bounced = response_hash['bounced']
|
17
|
+
@status = response_hash['status']
|
18
|
+
@subscription_statuses = response_hash['SubscriptionStatuses']
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
21
|
+
class << self
|
22
|
+
def status(email)
|
23
|
+
response = Hubspot::Connection.get_json(SUBSCRIPTION_PATH, {email_address: email})
|
24
|
+
new(response)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -39,7 +39,17 @@ describe Hubspot::Contact do
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
describe ".add_contact!" do
|
43
|
+
cassette "add_contact_to_company_class"
|
44
|
+
let(:company){ Hubspot::Company.create!("company_#{Time.now.to_i}@example.com") }
|
45
|
+
let(:contact){ Hubspot::Contact.create!("contact_#{Time.now.to_i}@example.com") }
|
46
|
+
subject { Hubspot::Company.find_by_id(company.vid) }
|
47
|
+
|
48
|
+
before { Hubspot::Company.add_contact! company.vid, contact.vid }
|
49
|
+
its(['num_associated_contacts']) { should eql '1' }
|
50
|
+
end
|
51
|
+
|
52
|
+
describe ".find_by_id" do
|
43
53
|
context 'given an uniq id' do
|
44
54
|
cassette "company_find_by_id"
|
45
55
|
subject{ Hubspot::Company.find_by_id(vid) }
|
@@ -163,6 +173,39 @@ describe Hubspot::Contact do
|
|
163
173
|
end
|
164
174
|
end
|
165
175
|
|
176
|
+
describe "#batch_update!" do
|
177
|
+
cassette "company_batch_update"
|
178
|
+
let(:company){ Hubspot::Company.create!("company_#{Time.now.to_i}@example.com") }
|
179
|
+
|
180
|
+
context 'update via vid' do
|
181
|
+
let(:updated_companies) { [{ vid: company.vid, name: "Carol H" }] }
|
182
|
+
|
183
|
+
it 'should update companies' do
|
184
|
+
Hubspot::Company.batch_update!(updated_companies)
|
185
|
+
checked_company = Hubspot::Company.find_by_id(company.vid)
|
186
|
+
expect(checked_company.properties["name"]).to eq("Carol H")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'update via objectId' do
|
191
|
+
let(:updated_companies) { [{ objectId: company.vid, name: "Carol H" }] }
|
192
|
+
|
193
|
+
it 'should update companies' do
|
194
|
+
Hubspot::Company.batch_update!(updated_companies)
|
195
|
+
checked_company = Hubspot::Company.find_by_id(company.vid)
|
196
|
+
expect(checked_company.properties["name"]).to eq("Carol H")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'missing vid or objectId' do
|
201
|
+
let(:updated_companies) { [{ name: "Carol H" }] }
|
202
|
+
|
203
|
+
it 'should raise error with expected message' do
|
204
|
+
expect { Hubspot::Company.batch_update!(updated_companies) }.to raise_error(Hubspot::InvalidParams, 'expecting vid or objectId for company')
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
166
209
|
describe "#destroy!" do
|
167
210
|
cassette "company_destroy"
|
168
211
|
let(:company){ Hubspot::Company.create!("newcompany_y_#{Time.now.to_i}@hsgem.com") }
|
@@ -181,11 +224,22 @@ describe Hubspot::Contact do
|
|
181
224
|
end
|
182
225
|
end
|
183
226
|
|
227
|
+
describe "#get_contact_vids" do
|
228
|
+
cassette "company_get_contact_vids"
|
229
|
+
let(:company) { Hubspot::Company.create!("company_#{Time.now.to_i}@example.com") }
|
230
|
+
let(:contact) { Hubspot::Contact.create!("contact_#{Time.now.to_i}@example.com") }
|
231
|
+
before { company.add_contact(contact) }
|
232
|
+
subject { company.get_contact_vids }
|
233
|
+
|
234
|
+
it { is_expected.to eq [contact.vid] }
|
235
|
+
end
|
236
|
+
|
184
237
|
describe "#add_contact" do
|
185
|
-
cassette "
|
238
|
+
cassette "add_contact_to_company_instance"
|
186
239
|
let(:company){ Hubspot::Company.create!("company_#{Time.now.to_i}@example.com") }
|
187
240
|
let(:contact){ Hubspot::Contact.create!("contact_#{Time.now.to_i}@example.com") }
|
188
|
-
subject { Hubspot::Company.
|
241
|
+
subject { Hubspot::Company.find_by_id(company.vid) }
|
242
|
+
|
189
243
|
context "with Hubspot::Contact instance" do
|
190
244
|
before { company.add_contact contact }
|
191
245
|
its(['num_associated_contacts']) { should eql '1' }
|
@@ -105,7 +105,7 @@ describe Hubspot::ContactList do
|
|
105
105
|
list = lists.first
|
106
106
|
expect(list).to be_a(Hubspot::ContactList)
|
107
107
|
expect(list.id).to be_an(Integer)
|
108
|
-
|
108
|
+
end
|
109
109
|
|
110
110
|
expect_count_and_offset { |params| Hubspot::ContactList.all(params) }
|
111
111
|
end
|
@@ -114,7 +114,7 @@ describe Hubspot::ContactList do
|
|
114
114
|
cassette 'find_all_stastic_lists'
|
115
115
|
|
116
116
|
it 'returns by defaut all the static contact lists' do
|
117
|
-
|
117
|
+
lists = Hubspot::ContactList.all(static: true)
|
118
118
|
expect(lists.count).to be > 20
|
119
119
|
|
120
120
|
list = lists.first
|
@@ -127,7 +127,7 @@ describe Hubspot::ContactList do
|
|
127
127
|
cassette 'find_all_dynamic_lists'
|
128
128
|
|
129
129
|
it 'returns by defaut all the static contact lists' do
|
130
|
-
|
130
|
+
lists = Hubspot::ContactList.all(dynamic: true)
|
131
131
|
expect(lists.count).to be > 20
|
132
132
|
|
133
133
|
list = lists.first
|
@@ -336,6 +336,32 @@ describe Hubspot::Contact do
|
|
336
336
|
end
|
337
337
|
end
|
338
338
|
|
339
|
+
describe '.merge!' do
|
340
|
+
cassette 'contact_merge'
|
341
|
+
let(:primary_params) { { firstname: 'Hugh', lastname: 'Jackman' } }
|
342
|
+
let(:primary_contact) { Hubspot::Contact.create!("primary_#{Time.now.to_i}@hsgem.com", primary_params) }
|
343
|
+
let(:secondary_params) { { firstname: 'Wolverine' } }
|
344
|
+
let(:secondary_contact) { Hubspot::Contact.create!("secondary_#{Time.now.to_i}@hsgem.com", secondary_params) }
|
345
|
+
|
346
|
+
subject { Hubspot::Contact.merge!(primary_contact.vid, secondary_contact.vid) }
|
347
|
+
|
348
|
+
it 'merges the contacts' do
|
349
|
+
subject
|
350
|
+
|
351
|
+
primary_find_by_id = Hubspot::Contact.find_by_id primary_contact.vid
|
352
|
+
primary_find_by_email = Hubspot::Contact.find_by_email primary_contact.email
|
353
|
+
secondary_find_by_id = Hubspot::Contact.find_by_id secondary_contact.vid
|
354
|
+
secondary_find_by_email = Hubspot::Contact.find_by_email secondary_contact.email
|
355
|
+
|
356
|
+
primary_find_by_id.email.should == primary_contact.email
|
357
|
+
primary_find_by_id.email.should == primary_find_by_email.email
|
358
|
+
primary_find_by_id.email.should == secondary_find_by_id.email
|
359
|
+
primary_find_by_id.email.should == secondary_find_by_email.email
|
360
|
+
primary_find_by_id['firstname'].should == 'Wolverine'
|
361
|
+
primary_find_by_id['lastname'].should == 'Jackman'
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
339
365
|
describe '#update!' do
|
340
366
|
cassette 'contact_update'
|
341
367
|
let(:contact){ Hubspot::Contact.new(example_contact_hash) }
|
@@ -71,6 +71,20 @@ describe Hubspot::Engagement do
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
describe ".associate!" do
|
75
|
+
cassette "engagement_associate"
|
76
|
+
|
77
|
+
let(:engagement) { Hubspot::EngagementNote.create!(nil, 'note') }
|
78
|
+
let(:contact) { Hubspot::Contact.create!("newcontact#{Time.now.to_i}@hsgem.com") }
|
79
|
+
subject { Hubspot::Engagement.associate!(engagement.id, 'contact', contact.vid) }
|
80
|
+
|
81
|
+
it 'associate an engagement to a resource' do
|
82
|
+
subject
|
83
|
+
found_by_contact = Hubspot::Engagement.find_by_contact(contact.vid)
|
84
|
+
expect(found_by_contact.first.id).to eql engagement.id
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
74
88
|
describe '#destroy!' do
|
75
89
|
cassette 'engagement_destroy'
|
76
90
|
|
@@ -142,6 +142,18 @@ describe Hubspot::Form do
|
|
142
142
|
result.should be false
|
143
143
|
end
|
144
144
|
end
|
145
|
+
|
146
|
+
context 'when initializing Hubspot::Form directly' do
|
147
|
+
let(:form) { Hubspot::Form.new('guid' => '561d9ce9-bb4c-45b4-8e32-21cdeaa3a7f0') }
|
148
|
+
|
149
|
+
before { Hubspot.configure(hapikey: 'demo', portal_id: '62515') }
|
150
|
+
|
151
|
+
it 'returns true if the form submission is successful' do
|
152
|
+
params = {}
|
153
|
+
result = form.submit(params)
|
154
|
+
result.should be true
|
155
|
+
end
|
156
|
+
end
|
145
157
|
end
|
146
158
|
|
147
159
|
describe '#update!' do
|
@@ -5,7 +5,7 @@ describe "Companies API Live test", live: true do
|
|
5
5
|
Hubspot.configure hapikey: "demo"
|
6
6
|
end
|
7
7
|
|
8
|
-
it 'find, update and destroy a company' do
|
8
|
+
it 'find, update, batch_update and destroy a company' do
|
9
9
|
companies = Hubspot::Company.find_by_domain("create-delete-test.com")
|
10
10
|
companies.first.destroy! if companies.any?
|
11
11
|
|
@@ -15,9 +15,17 @@ describe "Companies API Live test", live: true do
|
|
15
15
|
company.update! name: "Create Delete Test 2"
|
16
16
|
company = Hubspot::Company.find_by_id(company.vid)
|
17
17
|
|
18
|
+
|
18
19
|
expect(company["name"]).to eql "Create Delete Test 2"
|
19
20
|
|
21
|
+
Hubspot::Company.batch_update!([{objectId: company.vid, name: 'Batch Update'}])
|
22
|
+
sleep 0.5 # prevent bulk update hasn't finished propagation
|
23
|
+
company = Hubspot::Company.find_by_id(company.vid)
|
24
|
+
|
25
|
+
expect(company["name"]).to eql "Batch Update"
|
26
|
+
|
20
27
|
expect(company.destroy!).to be_true
|
21
28
|
expect(Hubspot::Company.find_by_domain("create-delete-test.com")).to eq []
|
29
|
+
|
22
30
|
end
|
23
31
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hubspot-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew DiMichele
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2018-11-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -85,16 +85,16 @@ dependencies:
|
|
85
85
|
name: webmock
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
|
-
- - "
|
88
|
+
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
90
|
+
version: '0'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
|
-
- - "
|
95
|
+
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
97
|
+
version: '0'
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
99
|
name: vcr
|
100
100
|
requirement: !ruby/object:Gem::Requirement
|
@@ -222,6 +222,7 @@ files:
|
|
222
222
|
- lib/hubspot/engagement.rb
|
223
223
|
- lib/hubspot/exceptions.rb
|
224
224
|
- lib/hubspot/form.rb
|
225
|
+
- lib/hubspot/oauth.rb
|
225
226
|
- lib/hubspot/owner.rb
|
226
227
|
- lib/hubspot/properties.rb
|
227
228
|
- lib/hubspot/railtie.rb
|