hubspot-ruby 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|