soapy_bing 0.0.1

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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/.env +6 -0
  3. data/.gitignore +6 -0
  4. data/.rspec +2 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +6 -0
  7. data/README.md +63 -0
  8. data/Rakefile +7 -0
  9. data/circle.yml +6 -0
  10. data/lib/soapy_bing/account.rb +12 -0
  11. data/lib/soapy_bing/ads/reports/base.rb +83 -0
  12. data/lib/soapy_bing/ads/reports/campaign_performance_report.rb +23 -0
  13. data/lib/soapy_bing/ads/reports/parsers/csv_parser.rb +45 -0
  14. data/lib/soapy_bing/ads/reports/parsers.rb +1 -0
  15. data/lib/soapy_bing/ads/reports.rb +3 -0
  16. data/lib/soapy_bing/ads.rb +22 -0
  17. data/lib/soapy_bing/helpers/class_name.rb +9 -0
  18. data/lib/soapy_bing/helpers.rb +1 -0
  19. data/lib/soapy_bing/oauth_credentials.rb +43 -0
  20. data/lib/soapy_bing/param_guard.rb +26 -0
  21. data/lib/soapy_bing/soap/request/base.rb +37 -0
  22. data/lib/soapy_bing/soap/request/poll_generate_report_request.rb +33 -0
  23. data/lib/soapy_bing/soap/request/submit_generate_report_request.rb +16 -0
  24. data/lib/soapy_bing/soap/request.rb +3 -0
  25. data/lib/soapy_bing/soap/response/base.rb +16 -0
  26. data/lib/soapy_bing/soap/response/payload.rb +15 -0
  27. data/lib/soapy_bing/soap/response/poll_generate_report_response.rb +13 -0
  28. data/lib/soapy_bing/soap/response/report_status.rb +33 -0
  29. data/lib/soapy_bing/soap/response/submit_generate_report_response.rb +11 -0
  30. data/lib/soapy_bing/soap/response.rb +5 -0
  31. data/lib/soapy_bing/soap/template_renderer.rb +21 -0
  32. data/lib/soapy_bing/soap/templates/poll_generate_report.erb.xml +18 -0
  33. data/lib/soapy_bing/soap/templates/submit_generate_report.erb.xml +46 -0
  34. data/lib/soapy_bing/soap.rb +3 -0
  35. data/lib/soapy_bing/version.rb +3 -0
  36. data/lib/soapy_bing.rb +11 -0
  37. data/lib/tasks/console.rake +5 -0
  38. data/lib/tasks/coverage.rake +6 -0
  39. data/lib/tasks/spec.rake +4 -0
  40. data/soapy_bing.gemspec +34 -0
  41. data/spec/fixtures/reports/campaign_performance_report.csv +37 -0
  42. data/spec/fixtures/reports/campaign_performance_report.json +146 -0
  43. data/spec/fixtures/soap_templates/simple.erb.xml +2 -0
  44. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_pending_status.yml +168 -0
  45. data/spec/fixtures/vcr_cassettes/campaign_performance_report/with_successful_status.yml +284 -0
  46. data/spec/fixtures/vcr_cassettes/oauth_credentials/access_token/with_successful_status.yml +42 -0
  47. data/spec/integration/soapy_bing/ads/reports/campaign_performance_report_spec.rb +39 -0
  48. data/spec/integration/soapy_bing/oauth_credentials_spec.rb +10 -0
  49. data/spec/simplecov_setup.rb +9 -0
  50. data/spec/soapy_bing/account_spec.rb +80 -0
  51. data/spec/soapy_bing/ads/reports/campaign_performance_report_spec.rb +41 -0
  52. data/spec/soapy_bing/ads/reports/parsers/csv_parser_spec.rb +31 -0
  53. data/spec/soapy_bing/ads_spec.rb +32 -0
  54. data/spec/soapy_bing/helpers/class_name_spec.rb +14 -0
  55. data/spec/soapy_bing/oauth_credentials_spec.rb +108 -0
  56. data/spec/soapy_bing/param_guard_spec.rb +43 -0
  57. data/spec/soapy_bing/soap/request/base_spec.rb +54 -0
  58. data/spec/soapy_bing/soap/request/poll_generate_report_request_spec.rb +59 -0
  59. data/spec/soapy_bing/soap/response/base_spec.rb +12 -0
  60. data/spec/soapy_bing/soap/response/payload_spec.rb +25 -0
  61. data/spec/soapy_bing/soap/response/poll_generate_report_response_spec.rb +25 -0
  62. data/spec/soapy_bing/soap/response/report_status_spec.rb +91 -0
  63. data/spec/soapy_bing/soap/response/submit_generate_report_response_spec.rb +19 -0
  64. data/spec/soapy_bing/soap/template_renderer_spec.rb +24 -0
  65. data/spec/spec_helper.rb +32 -0
  66. data/spec/support/dotenv.rb +3 -0
  67. data/spec/support/vcr.rb +101 -0
  68. metadata +305 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 07b8f667808f19487bb63afecce80900cbdb0116
4
+ data.tar.gz: cef996017af61aa78230fba06560e3c89304c9a8
5
+ SHA512:
6
+ metadata.gz: ff3885794184b39eae3827dba80d1b512342b80fa9ef34965c9e8c9477808fcc9196ab2a35262c150e3e4444cceba8d2c6fc3db268bfd1fd70c98e9b5fc60c9a
7
+ data.tar.gz: 2a81559fa1fe6f2d9db5201489d87b3749cf52542cdbeeee689c760b21c67d10de72fd70ac5622b4fb9022866f2b299b041d49499d5df26985a6aba3121cf445
data/.env ADDED
@@ -0,0 +1,6 @@
1
+ export BING_ADS_OAUTH_CLIENT_ID='bing-ads-oauth-client-id'
2
+ export BING_ADS_OAUTH_CLIENT_SECRET='bing-ads-oauth-client-secret'
3
+ export BING_ADS_OAUTH_REFRESH_TOKEN='bing-ads-oauth-refresh-token'
4
+ export BING_ADS_DEVELOPER_TOKEN='bing-ads-developer-token'
5
+ export BING_ADS_ACCOUNT_ID='bing-ads-account-id'
6
+ export BING_ADS_CUSTOMER_ID='bing-ads-customer-id'
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ Gemfile.lock
2
+
3
+ /coverage
4
+
5
+ .env.local
6
+ .env.development
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rubocop-ci', github: 'ad2games/rubocop-ci'
6
+ gem 'simplecov', require: false
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # SoapyBing
2
+ [![Circle CI](https://circleci.com/gh/ad2games/soapy_bing.png?style=shield)](https://circleci.com/gh/ad2games/soapy_bing)
3
+
4
+ Client library for [Bing Ads API](https://msdn.microsoft.com/en-us/library/bing-ads-overview)
5
+
6
+ ## Installation
7
+
8
+ In your Gemfile
9
+
10
+ ```ruby
11
+ gem 'soapy-bing', git: 'https://github.com/ad2games/soapy-bing'
12
+ ```
13
+
14
+ ## Usage Examples
15
+
16
+ ```ruby
17
+ bing_ads = SoapyBing::Ads.new
18
+ report = bing_ads.campaign_performance_report(
19
+ date_start: '2015-10-14',
20
+ date_end: '2015-10-14',
21
+
22
+ settings: { # optional
23
+ columns: [''] # see SoapyBing::Ads::Report::Base::DEFAULT_REPORT_SETTINGS[:columns]
24
+ }
25
+ )
26
+ report.rows # =>
27
+ # [{
28
+ # "HourOfDay" => "0",
29
+ # "CampaignName" => "My Campaign",
30
+ # "Impressions" => "100",
31
+ # "Clicks" => "47",
32
+ # "Spend" => "12.65"
33
+ # }, {
34
+ # ...
35
+ # }]
36
+ ```
37
+
38
+ ## Authentication
39
+
40
+ Authentication attributes could be passed explicitly when new instance of SoapyBing::Ads is created.
41
+ Or they could be configured as Envronment variables.
42
+
43
+ ```ruby
44
+ SoapyBing::Ads.new(
45
+ oauth: { # optional, could be configured via environment variables as
46
+ client_id: '', # ENV['BING_ADS_OAUTH_CLIENT_ID']
47
+ client_secret: '', # ENV['BING_ADS_OAUTH_CLIENT_SECRET']
48
+ refresh_token: '' # ENV['BING_ADS_OAUTH_REFRESH_TOKEN']
49
+ },
50
+ account: { # optional, could be configured via environment variables as
51
+ developer_token: '', # ENV['BING_ADS_DEVELOPER_TOKEN']
52
+ account_id: '', # ENV['BING_ADS_ACCOUNT_ID']
53
+ customer_id: '' # ENV['BING_ADS_CUSTOMER_ID']
54
+ }
55
+ )
56
+ ```
57
+
58
+ ## Links
59
+ * MS Live Applications [https://account.live.com/developers/applications](https://account.live.com/developers/applications)
60
+ * Bing Ads User Authentication with OAuth [https://msdn.microsoft.com/en-us/library/bing-ads-user-authentication-oauth-guide.aspx](https://msdn.microsoft.com/en-us/library/bing-ads-user-authentication-oauth-guide.aspx)
61
+ * Getting Started With the Bing Ads API [https://msdn.microsoft.com/en-us/library/bing-ads-getting-started.aspx](https://msdn.microsoft.com/en-us/library/bing-ads-getting-started.aspx)
62
+ * Bing Ads API Reference [https://msdn.microsoft.com/en-US/library/bing-ads-api-reference.aspx](https://msdn.microsoft.com/en-US/library/bing-ads-api-reference.aspx)
63
+ * Reporting Service Reference [https://msdn.microsoft.com/en-US/library/bing-ads-reporting-service-reference](https://msdn.microsoft.com/en-US/library/bing-ads-reporting-service-reference)
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'bundler/gem_tasks'
5
+ require 'rubocop-ci'
6
+
7
+ Rake.add_rakelib 'lib/tasks'
data/circle.yml ADDED
@@ -0,0 +1,6 @@
1
+ dependencies:
2
+ post:
3
+ - bundle update
4
+ test:
5
+ pre:
6
+ - bundle exec rake rubocop
@@ -0,0 +1,12 @@
1
+ module SoapyBing
2
+ class Account
3
+ attr_reader :developer_token, :account_id, :customer_id
4
+
5
+ def initialize(account_options)
6
+ param_guard = ParamGuard.new(account_options, env_namespace: 'BING_ADS')
7
+ @developer_token = param_guard.require!(:developer_token)
8
+ @account_id = param_guard.require!(:account_id)
9
+ @customer_id = param_guard.require!(:customer_id)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,83 @@
1
+ require 'ostruct'
2
+ require 'open-uri'
3
+ require 'zip'
4
+
5
+ module SoapyBing
6
+ class Ads
7
+ module Reports
8
+ class Base
9
+ class UnknownParserError < StandardError; end
10
+ include Helpers::ClassName
11
+
12
+ DEFAULT_REPORT_SETTINGS = {
13
+ format: 'Csv',
14
+ language: 'English',
15
+ name: 'MyReport',
16
+ aggregation: 'HourOfDay',
17
+ columns: %w(TimePeriod CampaignName Impressions Clicks Spend)
18
+ }.freeze
19
+
20
+ attr_reader :oauth_credentials, :account, :settings
21
+
22
+ def initialize(options)
23
+ @oauth_credentials = options.fetch(:oauth_credentials)
24
+ @account = options.fetch(:account)
25
+ @settings = OpenStruct.new(DEFAULT_REPORT_SETTINGS.merge(options.fetch(:settings, {})))
26
+ end
27
+
28
+ def rows
29
+ @rows ||= parser_class.new(report_body).rows
30
+ end
31
+
32
+ private
33
+
34
+ def parser_class
35
+ class_name = "#{settings.format.upcase}Parser".to_sym
36
+ fail UnknownParserError, class_name unless Parsers.constants.include?(class_name)
37
+ Parsers.const_get class_name
38
+ end
39
+
40
+ def download_url
41
+ @url ||=
42
+ Soap::Request::PollGenerateReportRequest
43
+ .new(context: poll_generate_report_context)
44
+ .perform
45
+ .payload
46
+ end
47
+
48
+ def poll_generate_report_context
49
+ {
50
+ account: account,
51
+ authentication_token: oauth_credentials.access_token,
52
+ request_id: request_id
53
+ }
54
+ end
55
+
56
+ def report_body
57
+ @report_body ||=
58
+ Zip::InputStream.open(OpenURI.open_uri(download_url)) do |archive_io|
59
+ file_io = archive_io.get_next_entry.get_input_stream
60
+ file_io.read
61
+ end
62
+ end
63
+
64
+ def request_id
65
+ @request_id ||=
66
+ Soap::Request::SubmitGenerateReportRequest
67
+ .new(context: submit_generate_report_context)
68
+ .perform
69
+ .payload
70
+ end
71
+
72
+ def submit_generate_report_context
73
+ {
74
+ account: account,
75
+ authentication_token: oauth_credentials.access_token,
76
+ settings: settings,
77
+ report_class: class_name
78
+ }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,23 @@
1
+ require 'date'
2
+
3
+ module SoapyBing
4
+ class Ads
5
+ module Reports
6
+ class CampaignPerformanceReport < Base
7
+ attr_reader :date_range
8
+
9
+ def initialize(options)
10
+ super(options)
11
+ @date_range = Range.new(
12
+ Date.parse(options.fetch(:date_start)),
13
+ Date.parse(options.fetch(:date_end))
14
+ )
15
+ end
16
+
17
+ def submit_generate_report_context
18
+ super.merge(date_range: date_range)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ require 'csv'
2
+
3
+ module SoapyBing
4
+ class Ads
5
+ module Reports
6
+ module Parsers
7
+ class CSVParser
8
+ class FormatError < StandardError; end
9
+
10
+ CSV_PAYLOAD_OFFSET_FRONT = 10 # First 10 csv lines are report metadata
11
+ CSV_PAYLOAD_OFFSET_BACK = 2 # Last 2 csv lines are Microsoft copyright
12
+
13
+ def initialize(raw)
14
+ @raw = raw
15
+ end
16
+
17
+ def rows
18
+ @rows ||= begin
19
+ header, *body = extract_csv_payload
20
+ fail FormatError if body.size != payload_rows_number
21
+ body.map { |row| header.zip(row).to_h }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :raw
28
+
29
+ def extract_csv_payload
30
+ text = raw.dup
31
+ text.force_encoding(Encoding::UTF_8).encode! unless text.encoding == Encoding::UTF_8
32
+ text.sub!(/^\xEF\xBB\xBF/, '') # cleanup BOM
33
+
34
+ csv_rows = CSV.parse(text)
35
+ csv_rows[CSV_PAYLOAD_OFFSET_FRONT...-CSV_PAYLOAD_OFFSET_BACK]
36
+ end
37
+
38
+ def payload_rows_number
39
+ raw.match(/"Rows: (\d+)"/)[1].to_i
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1 @@
1
+ require 'soapy_bing/ads/reports/parsers/csv_parser'
@@ -0,0 +1,3 @@
1
+ require 'soapy_bing/ads/reports/parsers'
2
+ require 'soapy_bing/ads/reports/base'
3
+ require 'soapy_bing/ads/reports/campaign_performance_report'
@@ -0,0 +1,22 @@
1
+ require 'soapy_bing/ads/reports'
2
+
3
+ module SoapyBing
4
+ class Ads
5
+ attr_reader :oauth_credentials, :account
6
+
7
+ def initialize(oauth: {}, account: {})
8
+ @oauth_credentials = OauthCredentials.new(oauth)
9
+ @account = Account.new(account)
10
+ end
11
+
12
+ def campaign_performance_report(date_start:, date_end:, settings: {})
13
+ Reports::CampaignPerformanceReport.new(
14
+ oauth_credentials: oauth_credentials,
15
+ account: account,
16
+ date_start: date_start,
17
+ date_end: date_end,
18
+ settings: settings
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ module SoapyBing
2
+ module Helpers
3
+ module ClassName
4
+ def class_name
5
+ @class_name ||= self.class.name.demodulize
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1 @@
1
+ require 'soapy_bing/helpers/class_name'
@@ -0,0 +1,43 @@
1
+ require 'httparty'
2
+
3
+ module SoapyBing
4
+ class OauthCredentials
5
+ class TokenRefreshError < StandardError; end
6
+
7
+ TOKEN_URL = 'https://login.live.com/oauth20_token.srf'.freeze
8
+
9
+ attr_reader :client_id, :client_secret, :refresh_token, :token_url
10
+
11
+ def initialize(oauth_options = {})
12
+ param_guard = ParamGuard.new(oauth_options, env_namespace: 'BING_ADS_OAUTH')
13
+ @client_id = param_guard.require!(:client_id)
14
+ @client_secret = param_guard.require!(:client_secret)
15
+ @refresh_token = param_guard.require!(:refresh_token)
16
+ @token_url = oauth_options.fetch(:token_url, ENV['BING_ADS_OAUTH_TOKEN_URL'] || TOKEN_URL)
17
+ end
18
+
19
+ def access_token
20
+ @access_token ||= request_access_token
21
+ end
22
+
23
+ private
24
+
25
+ def request_access_token
26
+ resp = HTTParty.post(token_url, body: access_token_params)
27
+ if resp.code == 200
28
+ resp['access_token']
29
+ else
30
+ fail TokenRefreshError
31
+ end
32
+ end
33
+
34
+ def access_token_params
35
+ {
36
+ client_id: client_id,
37
+ client_secret: client_secret,
38
+ grant_type: 'refresh_token',
39
+ refresh_token: refresh_token
40
+ }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ module SoapyBing
2
+ class ParamGuard
3
+ class ParamRequiredError < StandardError; end
4
+
5
+ def initialize(local_options, env_namespace: '')
6
+ @local_options = local_options
7
+ @env_namespace = env_namespace
8
+ end
9
+
10
+ def require!(name)
11
+ local_options.fetch(name, ENV[env_var_name(name)]) || fail(ParamRequiredError, err_msg(name))
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :local_options, :env_namespace
17
+
18
+ def err_msg(name)
19
+ "#{name} have to be passed explicitly or via ENV['#{env_var_name(name)}']"
20
+ end
21
+
22
+ def env_var_name(name)
23
+ (env_namespace.empty? ? name : "#{env_namespace}_#{name}").upcase.tr('-', '_')
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ require 'httparty'
2
+
3
+ module SoapyBing
4
+ module Soap
5
+ module Request
6
+ class Base
7
+ include Helpers::ClassName
8
+
9
+ DEFAULT_HTTP_HEADERS = {
10
+ 'Content-Type' => 'text/xml;charset=UTF-8'
11
+ }.freeze
12
+
13
+ attr_reader :context
14
+
15
+ def initialize(context:)
16
+ @context = context
17
+ end
18
+
19
+ def post(url, body: default_body, headers: default_headers)
20
+ HTTParty.post(url, body: body, headers: headers)
21
+ end
22
+
23
+ def default_body
24
+ TemplateRenderer.new(context).render(action_name.underscore)
25
+ end
26
+
27
+ def default_headers
28
+ DEFAULT_HTTP_HEADERS.merge('SOAPAction' => action_name)
29
+ end
30
+
31
+ def action_name
32
+ class_name.sub(/Request$/, '')
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ module SoapyBing
2
+ module Soap
3
+ module Request
4
+ class PollGenerateReportRequest < Base
5
+ class FailedStatusError < StandardError; end
6
+ class PendingStatusError < StandardError; end
7
+ class PollingTimeoutError < StandardError; end
8
+
9
+ API_BASE_URL = 'https://reporting.api.bingads.microsoft.com'.freeze
10
+ API_VERSION = 9
11
+ API_ENDPOINT =
12
+ "#{API_BASE_URL}/Api/Advertiser/Reporting/V#{API_VERSION}/ReportingService.svc".freeze
13
+
14
+ POLLING_TRIES = 40
15
+
16
+ def perform
17
+ Retryable.retryable(tries: POLLING_TRIES, on: PendingStatusError) { poll! }
18
+ rescue PendingStatusError
19
+ raise PollingTimeoutError
20
+ end
21
+
22
+ private
23
+
24
+ def poll!
25
+ response = Response::PollGenerateReportResponse.new(post(API_ENDPOINT))
26
+ fail PendingStatusError if response.pending?
27
+ fail FailedStatusError if response.error?
28
+ response
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ module SoapyBing
2
+ module Soap
3
+ module Request
4
+ class SubmitGenerateReportRequest < Base
5
+ API_BASE_URL = 'https://reporting.api.bingads.microsoft.com'.freeze
6
+ API_VERSION = 9
7
+ API_ENDPOINT =
8
+ "#{API_BASE_URL}/Api/Advertiser/Reporting/V#{API_VERSION}/ReportingService.svc".freeze
9
+
10
+ def perform
11
+ Response::SubmitGenerateReportResponse.new(post(API_ENDPOINT))
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ require 'soapy_bing/soap/request/base'
2
+ require 'soapy_bing/soap/request/submit_generate_report_request'
3
+ require 'soapy_bing/soap/request/poll_generate_report_request'
@@ -0,0 +1,16 @@
1
+ module SoapyBing
2
+ module Soap
3
+ module Response
4
+ class Base
5
+ include Helpers::ClassName
6
+ include Payload
7
+
8
+ attr_reader :body
9
+
10
+ def initialize(body)
11
+ @body = body
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module SoapyBing
2
+ module Soap
3
+ module Response
4
+ module Payload
5
+ def payload
6
+ @payload ||= extract_payload
7
+ end
8
+
9
+ def extract_payload
10
+ fail NotImplementedError
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module SoapyBing
2
+ module Soap
3
+ module Response
4
+ class PollGenerateReportResponse < Base
5
+ include ReportStatus
6
+
7
+ def extract_payload
8
+ report_status['ReportDownloadUrl']
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ module SoapyBing
2
+ module Soap
3
+ module Response
4
+ module ReportStatus
5
+ include Helpers::ClassName
6
+
7
+ def status
8
+ @status ||= extract_status
9
+ end
10
+
11
+ def extract_status
12
+ report_status['Status']
13
+ end
14
+
15
+ def report_status
16
+ body['Envelope']['Body'][class_name]['ReportRequestStatus']
17
+ end
18
+
19
+ def error?
20
+ status == 'Error'
21
+ end
22
+
23
+ def success?
24
+ status == 'Success'
25
+ end
26
+
27
+ def pending?
28
+ status == 'Pending'
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ module SoapyBing
2
+ module Soap
3
+ module Response
4
+ class SubmitGenerateReportResponse < Base
5
+ def extract_payload
6
+ body['Envelope']['Body'][class_name]['ReportRequestId']
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ require 'soapy_bing/soap/response/payload'
2
+ require 'soapy_bing/soap/response/report_status'
3
+ require 'soapy_bing/soap/response/base'
4
+ require 'soapy_bing/soap/response/submit_generate_report_response'
5
+ require 'soapy_bing/soap/response/poll_generate_report_response'
@@ -0,0 +1,21 @@
1
+ require 'ostruct'
2
+ require 'erubis'
3
+
4
+ module SoapyBing
5
+ module Soap
6
+ class TemplateRenderer < OpenStruct
7
+ TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
8
+
9
+ def render(template_name)
10
+ template_body = read(template_name)
11
+ Erubis::XmlEruby.new(template_body).result(binding)
12
+ end
13
+
14
+ private
15
+
16
+ def read(name)
17
+ File.read(File.join(TEMPLATE_PATH, "#{name}.erb.xml"))
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xmlns:tns="https://bingads.microsoft.com/Reporting/v9"
5
+ xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
6
+ xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
7
+ <env:Header>
8
+ <tns:CustomerAccountId><%= account.account_id %></tns:CustomerAccountId>
9
+ <tns:CustomerId><%= account.customer_id %></tns:CustomerId>
10
+ <tns:DeveloperToken><%= account.developer_token %></tns:DeveloperToken>
11
+ <tns:AuthenticationToken><%= authentication_token %></tns:AuthenticationToken>
12
+ </env:Header>
13
+ <env:Body>
14
+ <tns:PollGenerateReportRequest>
15
+ <tns:ReportRequestId xsi:nil="false"><%= request_id %></tns:ReportRequestId>
16
+ </tns:PollGenerateReportRequest>
17
+ </env:Body>
18
+ </env:Envelope>