hubspot-api-ruby 0.8.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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/Gemfile +3 -0
  4. data/Guardfile +9 -0
  5. data/LICENSE.txt +18 -0
  6. data/README.md +295 -0
  7. data/RELEASING.md +6 -0
  8. data/Rakefile +32 -0
  9. data/hubspot-api-ruby.gemspec +42 -0
  10. data/lib/hubspot-api-ruby.rb +39 -0
  11. data/lib/hubspot/blog.rb +98 -0
  12. data/lib/hubspot/collection.rb +41 -0
  13. data/lib/hubspot/company.rb +160 -0
  14. data/lib/hubspot/company_properties.rb +59 -0
  15. data/lib/hubspot/config.rb +63 -0
  16. data/lib/hubspot/connection.rb +152 -0
  17. data/lib/hubspot/contact.rb +110 -0
  18. data/lib/hubspot/contact_list.rb +129 -0
  19. data/lib/hubspot/contact_properties.rb +59 -0
  20. data/lib/hubspot/deal.rb +173 -0
  21. data/lib/hubspot/deal_pipeline.rb +58 -0
  22. data/lib/hubspot/deal_properties.rb +59 -0
  23. data/lib/hubspot/deprecator.rb +7 -0
  24. data/lib/hubspot/engagement.rb +222 -0
  25. data/lib/hubspot/event.rb +21 -0
  26. data/lib/hubspot/exceptions.rb +18 -0
  27. data/lib/hubspot/file.rb +38 -0
  28. data/lib/hubspot/form.rb +95 -0
  29. data/lib/hubspot/oauth.rb +50 -0
  30. data/lib/hubspot/owner.rb +57 -0
  31. data/lib/hubspot/paged_collection.rb +35 -0
  32. data/lib/hubspot/properties.rb +123 -0
  33. data/lib/hubspot/railtie.rb +10 -0
  34. data/lib/hubspot/resource.rb +270 -0
  35. data/lib/hubspot/subscription.rb +37 -0
  36. data/lib/hubspot/topic.rb +37 -0
  37. data/lib/hubspot/utils.rb +127 -0
  38. data/lib/tasks/hubspot.rake +53 -0
  39. data/spec/factories/companies.rb +9 -0
  40. data/spec/factories/contacts.rb +10 -0
  41. data/spec/lib/hubspot-ruby_spec.rb +12 -0
  42. data/spec/lib/hubspot/blog_spec.rb +150 -0
  43. data/spec/lib/hubspot/company_properties_spec.rb +410 -0
  44. data/spec/lib/hubspot/company_spec.rb +340 -0
  45. data/spec/lib/hubspot/config_spec.rb +87 -0
  46. data/spec/lib/hubspot/connection_spec.rb +214 -0
  47. data/spec/lib/hubspot/contact_list_spec.rb +301 -0
  48. data/spec/lib/hubspot/contact_properties_spec.rb +245 -0
  49. data/spec/lib/hubspot/contact_spec.rb +223 -0
  50. data/spec/lib/hubspot/deal_pipeline_spec.rb +85 -0
  51. data/spec/lib/hubspot/deal_properties_spec.rb +262 -0
  52. data/spec/lib/hubspot/deal_spec.rb +185 -0
  53. data/spec/lib/hubspot/deprecator_spec.rb +15 -0
  54. data/spec/lib/hubspot/engagement_spec.rb +177 -0
  55. data/spec/lib/hubspot/event_spec.rb +33 -0
  56. data/spec/lib/hubspot/file_spec.rb +38 -0
  57. data/spec/lib/hubspot/form_spec.rb +189 -0
  58. data/spec/lib/hubspot/owner_spec.rb +56 -0
  59. data/spec/lib/hubspot/properties_spec.rb +45 -0
  60. data/spec/lib/hubspot/resource_spec.rb +54 -0
  61. data/spec/lib/hubspot/topic_spec.rb +23 -0
  62. data/spec/lib/hubspot/utils_spec.rb +164 -0
  63. data/spec/lib/tasks/hubspot_spec.rb +119 -0
  64. data/spec/shared_examples/saveable_resource.rb +45 -0
  65. data/spec/shared_examples/updateable_resource.rb +87 -0
  66. data/spec/spec_helper.rb +44 -0
  67. data/spec/support/capture_output.rb +21 -0
  68. data/spec/support/cassette_helper.rb +19 -0
  69. data/spec/support/hubspot_api_helpers.rb +13 -0
  70. data/spec/support/rake.rb +46 -0
  71. data/spec/support/tests_helper.rb +17 -0
  72. data/spec/support/vcr.rb +16 -0
  73. metadata +369 -0
@@ -0,0 +1,98 @@
1
+ module Hubspot
2
+ #
3
+ # HubSpot Contacts API
4
+ #
5
+ class Blog
6
+ BLOG_LIST_PATH = "/content/api/v2/blogs"
7
+ BLOG_POSTS_PATH = "/content/api/v2/blog-posts"
8
+ GET_BLOG_BY_ID_PATH = "/content/api/v2/blogs/:blog_id"
9
+
10
+ class << self
11
+ # Lists the blogs
12
+ # {https://developers.hubspot.com/docs/methods/blogv2/get_blogs}
13
+ # No param filtering is currently implemented
14
+ # @return [Hubspot::Blog] the first 20 blogs or empty_array
15
+ def list
16
+ response = Hubspot::Connection.get_json(BLOG_LIST_PATH, {})
17
+ response['objects'].map { |b| new(b) }
18
+ end
19
+
20
+ # Finds a specific blog by its ID
21
+ # {https://developers.hubspot.com/docs/methods/blogv2/get_blogs_blog_id}
22
+ # @return Hubspot::Blog
23
+ def find_by_id(id)
24
+ response = Hubspot::Connection.get_json(GET_BLOG_BY_ID_PATH, { blog_id: id })
25
+ new(response)
26
+ end
27
+ end
28
+
29
+ attr_reader :properties
30
+
31
+ def initialize(response_hash)
32
+ @properties = response_hash #no need to parse anything, we have properties
33
+ end
34
+
35
+ def [](property)
36
+ @properties[property]
37
+ end
38
+
39
+
40
+ # Returns the posts for this blog instance.
41
+ # defaults to returning the last 2 months worth of published blog posts
42
+ # in date descending order (i.e. most recent first)
43
+ # {https://developers.hubspot.com/docs/methods/blogv2/get_blog_posts}
44
+ # @return [Hubspot::BlogPost]
45
+ def posts(params = {})
46
+ default_params = {
47
+ content_group_id: self["id"],
48
+ order_by: '-created',
49
+ created__gt: Time.now - 2.month,
50
+ state: 'PUBLISHED'
51
+ }
52
+ raise Hubspot::InvalidParams.new('params must be passed as a hash') unless params.is_a?(Hash)
53
+ params = default_params.merge(params)
54
+
55
+ raise Hubspot::InvalidParams.new('State parameter was invalid') unless [false, 'PUBLISHED', 'DRAFT'].include?(params[:state])
56
+ params.each { |k, v| params.delete(k) if v == false }
57
+
58
+ response = Hubspot::Connection.get_json(BLOG_POSTS_PATH, params)
59
+ response['objects'].map { |p| BlogPost.new(p) }
60
+ end
61
+ end
62
+
63
+ class BlogPost
64
+ GET_BLOG_POST_BY_ID_PATH = "/content/api/v2/blog-posts/:blog_post_id"
65
+
66
+ # Returns a specific blog post by ID
67
+ # {https://developers.hubspot.com/docs/methods/blogv2/get_blog_posts_blog_post_id}
68
+ # @return Hubspot::BlogPost
69
+ def self.find_by_blog_post_id(id)
70
+ response = Hubspot::Connection.get_json(GET_BLOG_POST_BY_ID_PATH, { blog_post_id: id })
71
+ new(response)
72
+ end
73
+
74
+ def initialize(response_hash)
75
+ @properties = response_hash #no need to parse anything, we have properties
76
+ end
77
+
78
+ def [](property)
79
+ @properties[property]
80
+ end
81
+
82
+ def created_at
83
+ Time.at(@properties['created'] / 1000)
84
+ end
85
+
86
+ def topics
87
+ @topics ||= begin
88
+ if @properties['topic_ids'].empty?
89
+ []
90
+ else
91
+ @properties['topic_ids'].map do |topic_id|
92
+ Hubspot::Topic.find_by_topic_id(topic_id)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,41 @@
1
+ class Hubspot::Collection
2
+ def initialize(opts = {}, &block)
3
+ @options = opts
4
+ @fetch_proc = block
5
+ fetch
6
+ end
7
+
8
+ def refresh
9
+ fetch
10
+ self
11
+ end
12
+
13
+ def resources
14
+ @resources
15
+ end
16
+
17
+ def update_all(opts = {})
18
+ return true if empty?
19
+
20
+ # This assumes that all resources are the same type
21
+ resource_class = resources.first.class
22
+ unless resource_class.respond_to?(:batch_update)
23
+ raise "#{resource_class} does not support bulk update"
24
+ end
25
+
26
+ resource_class.batch_update(resources, opts)
27
+ end
28
+
29
+ protected
30
+ def fetch
31
+ @resources = @fetch_proc.call(@options)
32
+ end
33
+
34
+ def respond_to_missing?(name, include_private = false)
35
+ @resources.respond_to?(name, include_private)
36
+ end
37
+
38
+ def method_missing(method, *args, &block)
39
+ @resources.public_send(method, *args, &block)
40
+ end
41
+ end
@@ -0,0 +1,160 @@
1
+ class Hubspot::Company < Hubspot::Resource
2
+ self.id_field = "companyId"
3
+ self.property_name_field = "name"
4
+
5
+ ADD_CONTACT_PATH = '/companies/v2/companies/:id/contacts/:contact_id'
6
+ ALL_PATH = '/companies/v2/companies/paged'
7
+ BATCH_UPDATE_PATH = '/companies/v1/batch-async/update'
8
+ CONTACTS_PATH = '/companies/v2/companies/:id/contacts'
9
+ CONTACT_IDS_PATH = '/companies/v2/companies/:id/vids'
10
+ CREATE_PATH = '/companies/v2/companies/'
11
+ DELETE_PATH = '/companies/v2/companies/:id'
12
+ FIND_PATH = '/companies/v2/companies/:id'
13
+ RECENTLY_CREATED_PATH = '/companies/v2/companies/recent/created'
14
+ RECENTLY_MODIFIED_PATH = '/companies/v2/companies/recent/modified'
15
+ REMOVE_CONTACT_PATH = '/companies/v2/companies/:id/contacts/:contact_id'
16
+ SEARCH_DOMAIN_PATH = '/companies/v2/domains/:domain/companies'
17
+ UPDATE_PATH = '/companies/v2/companies/:id'
18
+
19
+ class << self
20
+ def all(opts = {})
21
+ Hubspot::PagedCollection.new(opts) do |options, offset, limit|
22
+ response = Hubspot::Connection.get_json(
23
+ ALL_PATH,
24
+ options.merge(offset: offset, limit: limit)
25
+ )
26
+
27
+ companies = response["companies"].map { |result| from_result(result) }
28
+
29
+ [companies, response["offset"], response["has-more"]]
30
+ end
31
+ end
32
+
33
+ def search_domain(domain, opts = {})
34
+ Hubspot::PagedCollection.new(opts) do |options, offset, limit|
35
+ request = {
36
+ "limit" => limit,
37
+ "requestOptions" => options,
38
+ "offset" => {
39
+ "isPrimary" => true,
40
+ "companyId" => offset
41
+ }
42
+ }
43
+
44
+ response = Hubspot::Connection.post_json(
45
+ SEARCH_DOMAIN_PATH,
46
+ params: { domain: domain },
47
+ body: request
48
+ )
49
+
50
+ companies = response["results"].map { |result| from_result(result) }
51
+
52
+ [companies, response["offset"]["companyId"], response["hasMore"]]
53
+ end
54
+ end
55
+
56
+ def recently_created(opts = {})
57
+ Hubspot::PagedCollection.new(opts) do |options, offset, limit|
58
+ response = Hubspot::Connection.get_json(
59
+ RECENTLY_CREATED_PATH,
60
+ {offset: offset, count: limit}
61
+ )
62
+
63
+ companies = response["results"].map { |result| from_result(result) }
64
+
65
+ [companies, response["offset"], response["hasMore"]]
66
+ end
67
+ end
68
+
69
+ def recently_modified(opts = {})
70
+ Hubspot::PagedCollection.new(opts) do |options, offset, limit|
71
+ response = Hubspot::Connection.get_json(
72
+ RECENTLY_MODIFIED_PATH,
73
+ {offset: offset, count: limit}
74
+ )
75
+
76
+ companies = response["results"].map { |result| from_result(result) }
77
+
78
+ [companies, response["offset"], response["hasMore"]]
79
+ end
80
+ end
81
+
82
+ def add_contact(id, contact_id)
83
+ Hubspot::Connection.put_json(
84
+ ADD_CONTACT_PATH,
85
+ params: { id: id, contact_id: contact_id }
86
+ )
87
+ true
88
+ end
89
+
90
+ def remove_contact(id, contact_id)
91
+ Hubspot::Connection.delete_json(
92
+ REMOVE_CONTACT_PATH,
93
+ { id: id, contact_id: contact_id }
94
+ )
95
+
96
+ true
97
+ end
98
+
99
+ def batch_update(companies, opts = {})
100
+ request = companies.map do |company|
101
+ # Use the specified options or update with the changes
102
+ changes = opts.empty? ? company.changes : opts
103
+
104
+ unless changes.empty?
105
+ {
106
+ "objectId" => company.id,
107
+ "properties" => changes.map { |k, v| { "name" => k, "value" => v } }
108
+ }
109
+ end
110
+ end
111
+
112
+ # Remove any objects without changes and return if there is nothing to update
113
+ request.compact!
114
+ return true if request.empty?
115
+
116
+ Hubspot::Connection.post_json(
117
+ BATCH_UPDATE_PATH,
118
+ params: {},
119
+ body: request
120
+ )
121
+
122
+ true
123
+ end
124
+ end
125
+
126
+ def contacts(opts = {})
127
+ Hubspot::PagedCollection.new(opts) do |options, offset, limit|
128
+ response = Hubspot::Connection.get_json(
129
+ CONTACTS_PATH,
130
+ {"id" => @id, "vidOffset" => offset, "count" => limit}
131
+ )
132
+
133
+ contacts = response["contacts"].map do |result|
134
+ result["properties"] = Hubspot::Utils.properties_array_to_hash(result["properties"])
135
+ Hubspot::Contact.from_result(result)
136
+ end
137
+
138
+ [contacts, response["vidOffset"], response["hasMore"]]
139
+ end
140
+ end
141
+
142
+ def contact_ids(opts = {})
143
+ Hubspot::PagedCollection.new(opts) do |options, offset, limit|
144
+ response = Hubspot::Connection.get_json(
145
+ CONTACT_IDS_PATH,
146
+ {"id" => @id, "vidOffset" => offset, "count" => limit}
147
+ )
148
+
149
+ [response["vids"], response["vidOffset"], response["hasMore"]]
150
+ end
151
+ end
152
+
153
+ def add_contact(contact)
154
+ self.class.add_contact(@id, contact.to_i)
155
+ end
156
+
157
+ def remove_contact(contact)
158
+ self.class.remove_contact(@id, contact.to_i)
159
+ end
160
+ end
@@ -0,0 +1,59 @@
1
+ module Hubspot
2
+ class CompanyProperties < Properties
3
+
4
+ ALL_PROPERTIES_PATH = "/properties/v1/companies/properties"
5
+ ALL_GROUPS_PATH = "/properties/v1/companies/groups"
6
+ CREATE_PROPERTY_PATH = "/properties/v1/companies/properties"
7
+ UPDATE_PROPERTY_PATH = "/properties/v1/companies/properties/named/:property_name"
8
+ DELETE_PROPERTY_PATH = "/properties/v1/companies/properties/named/:property_name"
9
+ CREATE_GROUP_PATH = "/properties/v1/companies/groups"
10
+ UPDATE_GROUP_PATH = "/properties/v1/companies/groups/named/:group_name"
11
+ DELETE_GROUP_PATH = "/properties/v1/companies/groups/named/:group_name"
12
+
13
+ class << self
14
+ def add_default_parameters(opts={})
15
+ superclass.add_default_parameters(opts)
16
+ end
17
+
18
+ def all(opts={}, filter={})
19
+ superclass.all(ALL_PROPERTIES_PATH, opts, filter)
20
+ end
21
+
22
+ def groups(opts={}, filter={})
23
+ superclass.groups(ALL_GROUPS_PATH, opts, filter)
24
+ end
25
+
26
+ def create!(params={})
27
+ superclass.create!(CREATE_PROPERTY_PATH, params)
28
+ end
29
+
30
+ def update!(property_name, params={})
31
+ superclass.update!(UPDATE_PROPERTY_PATH, property_name, params)
32
+ end
33
+
34
+ def delete!(property_name)
35
+ superclass.delete!(DELETE_PROPERTY_PATH, property_name)
36
+ end
37
+
38
+ def create_group!(params={})
39
+ superclass.create_group!(CREATE_GROUP_PATH, params)
40
+ end
41
+
42
+ def update_group!(group_name, params={})
43
+ superclass.update_group!(UPDATE_GROUP_PATH, group_name, params)
44
+ end
45
+
46
+ def delete_group!(group_name)
47
+ superclass.delete_group!(DELETE_GROUP_PATH, group_name)
48
+ end
49
+
50
+ def same?(src, dst)
51
+ superclass.same?(src, dst)
52
+ end
53
+
54
+ def valid_params(params)
55
+ superclass.valid_params(params)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ require 'logger'
2
+ require 'hubspot/connection'
3
+
4
+ module Hubspot
5
+ class Config
6
+ CONFIG_KEYS = [
7
+ :hapikey, :base_url, :portal_id, :logger, :access_token, :client_id,
8
+ :client_secret, :redirect_uri, :read_timeout, :open_timeout
9
+ ]
10
+ DEFAULT_LOGGER = Logger.new(nil)
11
+ DEFAULT_BASE_URL = "https://api.hubapi.com".freeze
12
+
13
+ class << self
14
+ attr_accessor *CONFIG_KEYS
15
+
16
+ def configure(config)
17
+ config.stringify_keys!
18
+ @hapikey = config["hapikey"]
19
+ @base_url = config["base_url"] || DEFAULT_BASE_URL
20
+ @portal_id = config["portal_id"]
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
+ @read_timeout = config['read_timeout'] || config['timeout']
27
+ @open_timeout = config['open_timeout'] || config['timeout']
28
+
29
+ unless authentication_uncertain?
30
+ raise Hubspot::ConfigurationError.new("You must provide either an access_token or an hapikey")
31
+ end
32
+
33
+ if access_token.present?
34
+ Hubspot::Connection.headers("Authorization" => "Bearer #{access_token}")
35
+ end
36
+ self
37
+ end
38
+
39
+ def reset!
40
+ @hapikey = nil
41
+ @base_url = DEFAULT_BASE_URL
42
+ @portal_id = nil
43
+ @logger = DEFAULT_LOGGER
44
+ @access_token = nil
45
+ Hubspot::Connection.headers({})
46
+ end
47
+
48
+ def ensure!(*params)
49
+ params.each do |p|
50
+ raise Hubspot::ConfigurationError.new("'#{p}' not configured") unless instance_variable_get "@#{p}"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def authentication_uncertain?
57
+ access_token.present? ^ hapikey.present?
58
+ end
59
+ end
60
+
61
+ reset!
62
+ end
63
+ end
@@ -0,0 +1,152 @@
1
+ module Hubspot
2
+ class Connection
3
+ include HTTParty
4
+
5
+ class << self
6
+ def get_json(path, opts)
7
+ url = generate_url(path, opts)
8
+ response = get(url, format: :json, read_timeout: read_timeout(opts), open_timeout: open_timeout(opts))
9
+ log_request_and_response url, response
10
+ handle_response(response)
11
+ end
12
+
13
+ def post_json(path, opts)
14
+ no_parse = opts[:params].delete(:no_parse) { false }
15
+
16
+ url = generate_url(path, opts[:params])
17
+ response = post(
18
+ url,
19
+ body: opts[:body].to_json,
20
+ headers: { 'Content-Type' => 'application/json' },
21
+ format: :json,
22
+ read_timeout: read_timeout(opts),
23
+ open_timeout: open_timeout(opts)
24
+ )
25
+
26
+ log_request_and_response url, response, opts[:body]
27
+ raise(Hubspot::RequestError.new(response)) unless response.success?
28
+
29
+ no_parse ? response : response.parsed_response
30
+ end
31
+
32
+ def put_json(path, options)
33
+ no_parse = options[:params].delete(:no_parse) { false }
34
+ url = generate_url(path, options[:params])
35
+
36
+ response = put(
37
+ url,
38
+ body: options[:body].to_json,
39
+ headers: { "Content-Type" => "application/json" },
40
+ format: :json,
41
+ read_timeout: read_timeout(options),
42
+ open_timeout: open_timeout(options),
43
+ )
44
+
45
+ log_request_and_response(url, response, options[:body])
46
+ raise(Hubspot::RequestError.new(response)) unless response.success?
47
+
48
+ no_parse ? response : response.parsed_response
49
+ end
50
+
51
+ def delete_json(path, opts)
52
+ url = generate_url(path, opts)
53
+ response = delete(url, format: :json, read_timeout: read_timeout(opts), open_timeout: open_timeout(opts))
54
+ log_request_and_response url, response, opts[:body]
55
+ raise(Hubspot::RequestError.new(response)) unless response.success?
56
+ response
57
+ end
58
+
59
+ protected
60
+
61
+ def read_timeout(opts = {})
62
+ opts.delete(:read_timeout) || Hubspot::Config.read_timeout
63
+ end
64
+
65
+ def open_timeout(opts = {})
66
+ opts.delete(:open_timeout) || Hubspot::Config.open_timeout
67
+ end
68
+
69
+ def handle_response(response)
70
+ if response.success?
71
+ response.parsed_response
72
+ else
73
+ raise(Hubspot::RequestError.new(response))
74
+ end
75
+ end
76
+
77
+ def log_request_and_response(uri, response, body=nil)
78
+ Hubspot::Config.logger.info(<<~MSG)
79
+ Hubspot: #{uri}.
80
+ Body: #{body}.
81
+ Response: #{response.code} #{response.body}
82
+ MSG
83
+ end
84
+
85
+ def generate_url(path, params={}, options={})
86
+ if Hubspot::Config.access_token.present?
87
+ options[:hapikey] = false
88
+ else
89
+ Hubspot::Config.ensure! :hapikey
90
+ end
91
+ path = path.clone
92
+ params = params.clone
93
+ base_url = options[:base_url] || Hubspot::Config.base_url
94
+ params["hapikey"] = Hubspot::Config.hapikey unless options[:hapikey] == false
95
+
96
+ if path =~ /:portal_id/
97
+ Hubspot::Config.ensure! :portal_id
98
+ params["portal_id"] = Hubspot::Config.portal_id if path =~ /:portal_id/
99
+ end
100
+
101
+ params.each do |k,v|
102
+ if path.match(":#{k}")
103
+ path.gsub!(":#{k}", CGI.escape(v.to_s))
104
+ params.delete(k)
105
+ end
106
+ end
107
+ raise(Hubspot::MissingInterpolation.new("Interpolation not resolved")) if path =~ /:/
108
+
109
+ query = params.map do |k,v|
110
+ v.is_a?(Array) ? v.map { |value| param_string(k,value) } : param_string(k,v)
111
+ end.join("&")
112
+
113
+ path += path.include?('?') ? '&' : "?" if query.present?
114
+ base_url + path + query
115
+ end
116
+
117
+ # convert into milliseconds since epoch
118
+ def converted_value(value)
119
+ value.is_a?(Time) ? (value.to_i * 1000) : CGI.escape(value.to_s)
120
+ end
121
+
122
+ def param_string(key,value)
123
+ case key
124
+ when /range/
125
+ raise "Value must be a range" unless value.is_a?(Range)
126
+ "#{key}=#{converted_value(value.begin)}&#{key}=#{converted_value(value.end)}"
127
+ when /^batch_(.*)$/
128
+ key = $1.gsub(/(_.)/) { |w| w.last.upcase }
129
+ "#{key}=#{converted_value(value)}"
130
+ else
131
+ "#{key}=#{converted_value(value)}"
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ class FormsConnection < Connection
138
+ follow_redirects true
139
+
140
+ def self.submit(path, opts)
141
+ url = generate_url(path, opts[:params], { base_url: 'https://forms.hubspot.com', hapikey: false })
142
+ post(url, body: opts[:body], headers: { 'Content-Type' => 'application/x-www-form-urlencoded' })
143
+ end
144
+ end
145
+
146
+ class EventConnection < Connection
147
+ def self.trigger(path, opts)
148
+ url = generate_url(path, opts[:params], { base_url: 'https://track.hubspot.com', hapikey: false })
149
+ get(url, body: opts[:body], headers: opts[:headers])
150
+ end
151
+ end
152
+ end