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