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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f3a1711f64e0786c4ea7fea3ae863130e3f8a3d5
4
- data.tar.gz: 7c350471fe6a943453353c4a54021a104abb6b1b
3
+ metadata.gz: 1ed5f2aa1fbcdc87f14f1197d9b78dad4380f057
4
+ data.tar.gz: 2184008548937dbbbb103f30d3eb96c11f3c0d8c
5
5
  SHA512:
6
- metadata.gz: dbf007dcda9eed037c323f4f9209b9cfed55926458b923fc9a7bbf95c88cf5ef0bd7e01857affd32c8d8d4649613ebfa1a614d84897ec8e9ba7ca4398550c615
7
- data.tar.gz: 96a5651da299bdd400c02da77be240d76aecdf7acc72bc1c6e93b9dd6b24fbb3653dcbbb8da09413b83a90c6bc4c0557ef4a5dfa0c06c3fcb0e6b536a895c9b6
6
+ metadata.gz: 20f5233aa72b00ce366785e64b0ac385c8a48d86e2c79df9196958b668c573e32018cb2924e86c0216d489049535ce443dad9e7227a9b35941160e938f22023c
7
+ data.tar.gz: bea00d2e9baf596c71a2fcbaeaaa89b081dbbec0b900f03226bdfbbef8fc7ebab1bb0618739a660e1ccf624199341607f79412a97877ed87d1fde0732dec2feb
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
  # Add dependencies to hubspot-ruby.gemspec
3
- gemspec
3
+ gemspec
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
- Before using the library, you must initialize it with your HubSpot API key. If you're using Rails, put this code in an
18
- initializer:
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're have a HubSpot account, you can get your api key by logging in and visiting this url: https://app.hubspot.com/keys/get
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
- ### Note about authentication
70
+ ```ruby
71
+ Hubspot::OAuth.refresh(refresh_token)
72
+ ```
27
73
 
28
- For now, this library only supports authentication with a HubSpot API key (aka "hapikey"). OAuth is not yet supported.
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.
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "hubspot-ruby"
3
- s.version = "0.4.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", "< 1.10")
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")
@@ -19,6 +19,7 @@ require 'hubspot/deal_properties'
19
19
  require 'hubspot/owner'
20
20
  require 'hubspot/engagement'
21
21
  require 'hubspot/subscription'
22
+ require 'hubspot/oauth'
22
23
 
23
24
  module Hubspot
24
25
  def self.configure(config={})
@@ -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 = "/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
- ADD_CONTACT_TO_COMPANY_PATH = "/companies/v2/companies/:company_id/contacts/:vid"
15
- DESTROY_COMPANY_PATH = "/companies/v2/companies/:company_id"
16
- GET_COMPANY_CONTACTS_PATH = "/companies/v2/companies/:company_id/contacts"
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
- Hubspot::Connection.put_json(ADD_CONTACT_TO_COMPANY_PATH,
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
 
@@ -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 = [:hapikey, :base_url, :portal_id, :logger]
7
- DEFAULT_LOGGER = Logger.new('/dev/null')
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['logger'] || DEFAULT_LOGGER
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)
@@ -45,7 +45,11 @@ module Hubspot
45
45
  end
46
46
 
47
47
  def generate_url(path, params={}, options={})
48
- Hubspot::Config.ensure! :hapikey
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
@@ -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.merge!(vid: contact_hash[:vid])
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.merge!(email: contact_hash[:email])
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
 
@@ -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
- params = { objectType: :company, objectId: company.vid }
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
@@ -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
- metadata: metadata
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
@@ -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
@@ -1,29 +1,28 @@
1
1
  module Hubspot
2
2
  class Subscription
3
- SUBSCRIPTIONS_PATH = '/email/public/v1/subscriptions'
4
- TIMELINE_PATH = '/email/public/v1/subscriptions/timeline'
5
- SUBSCRIPTION_PATH = '/email/public/v1/subscriptions/:email_address'
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
- attr_reader :subscribed
8
- attr_reader :marked_as_spam
9
- attr_reader :bounced
10
- attr_reader :status
11
- attr_reader :subscription_statuses
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
- 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']
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
- 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
-
28
- end
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
- describe ".find_by_id" do
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 "add_contact_to_company"
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.all(recent: true).last }
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
- end
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
- lists = Hubspot::ContactList.all(static: true)
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
- lists = Hubspot::ContactList.all(dynamic: true)
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.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: 2017-12-11 00:00:00.000000000 Z
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: '1.10'
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: '1.10'
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