adapi 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +3 -0
  2. data/GUIDELINES.markdown +61 -0
  3. data/README.markdown +79 -45
  4. data/Rakefile +31 -3
  5. data/adapi.gemspec +10 -3
  6. data/examples/add_ad_group.rb +1 -1
  7. data/examples/add_bare_ad_group.rb +1 -4
  8. data/examples/add_campaign.rb +1 -0
  9. data/examples/add_campaign_criteria.rb +27 -0
  10. data/examples/add_invalid_ad_group.rb +3 -1
  11. data/examples/add_invalid_text_ad.rb +1 -1
  12. data/examples/add_keywords.rb +1 -1
  13. data/examples/add_negative_campaign_criteria.rb +23 -0
  14. data/examples/add_text_ad.rb +1 -1
  15. data/examples/customize_configuration.rb +1 -2
  16. data/examples/delete_keyword.rb +1 -1
  17. data/examples/find_campaign.rb +5 -5
  18. data/examples/find_campaign_ad_groups.rb +1 -1
  19. data/examples/find_campaign_criteria.rb +103 -0
  20. data/examples/find_locations.rb +21 -0
  21. data/examples/rollback_campaign.rb +7 -6
  22. data/examples/update_campaign.rb +1 -1
  23. data/examples/update_campaign_status.rb +1 -1
  24. data/lib/adapi.rb +11 -9
  25. data/lib/adapi/ad/text_ad.rb +2 -1
  26. data/lib/adapi/ad_group.rb +13 -9
  27. data/lib/adapi/ad_param.rb +89 -0
  28. data/lib/adapi/api.rb +8 -0
  29. data/lib/adapi/campaign.rb +27 -18
  30. data/lib/adapi/campaign_criterion.rb +278 -0
  31. data/lib/adapi/campaign_target.rb +5 -123
  32. data/lib/adapi/config.rb +36 -31
  33. data/lib/adapi/constant_data.rb +13 -0
  34. data/lib/adapi/constant_data/language.rb +45 -0
  35. data/lib/adapi/keyword.rb +15 -5
  36. data/lib/adapi/location.rb +91 -0
  37. data/lib/adapi/version.rb +8 -1
  38. data/lib/httpi_request_monkeypatch.rb +4 -0
  39. data/test/config/adapi.yml.template +21 -0
  40. data/test/config/adwords_api.yml.template +10 -0
  41. data/test/integration/create_campaign_test.rb +54 -0
  42. data/test/test_helper.rb +2 -3
  43. data/test/unit/ad_group_test.rb +3 -4
  44. data/test/unit/ad_test.rb +1 -1
  45. data/test/unit/campaign_criterion_test.rb +23 -0
  46. data/test/unit/config_test.rb +52 -0
  47. metadata +48 -35
  48. data/examples/add_campaign_targets.rb +0 -26
  49. data/test/unit/campaign_target_test.rb +0 -51
@@ -2,7 +2,7 @@
2
2
  require 'adapi'
3
3
 
4
4
  # create ad group
5
- require File.join(File.dirname(__FILE__), 'add_bare_ad_group')
5
+ require_relative 'add_bare_ad_group'
6
6
 
7
7
  ad = Adapi::Ad::TextAd.create(
8
8
  :ad_group_id => $ad_group[:id],
@@ -5,7 +5,7 @@ require 'adapi'
5
5
  # this example shows how to load custom settings
6
6
 
7
7
  Adapi::Config.load_settings(
8
- :path => File.expand_path(File.dirname(__FILE__)),
8
+ :dir => File.expand_path(File.dirname(__FILE__)),
9
9
  :filename => 'custom_settings.yml'
10
10
  )
11
11
 
@@ -16,4 +16,3 @@ p Adapi::Config.read[:authentication][:email]
16
16
  Adapi::Config.set(:sandbox)
17
17
  p "Set :sandbox account:"
18
18
  p Adapi::Config.read[:authentication][:email]
19
-
@@ -3,7 +3,7 @@
3
3
  require 'adapi'
4
4
 
5
5
  # create ad group
6
- require File.join(File.dirname(__FILE__), 'add_bare_ad_group')
6
+ require_relative 'add_bare_ad_group'
7
7
 
8
8
  $keywords = Adapi::Keyword.new(
9
9
  :ad_group_id => $ad_group[:id],
@@ -3,7 +3,7 @@
3
3
  require 'adapi'
4
4
 
5
5
  # create campaign
6
- require File.join(File.dirname(__FILE__), 'add_campaign')
6
+ require_relative 'add_campaign'
7
7
 
8
8
  $campaign = Adapi::Campaign.find_complete($campaign.id).to_hash
9
9
 
@@ -18,10 +18,10 @@ puts "Budget period: %s" % $campaign[:budget][:period]
18
18
  puts "\nBidding strategy type: %s" % $campaign[:bidding_strategy][:xsi_type]
19
19
  # TODO bidding_strategy.bid_ceiling
20
20
 
21
- puts "\nTargets:"
22
- $campaign[:targets].each do |target|
23
- p target[:xsi_type]
24
- p target
21
+ puts "\nCriteria:"
22
+ $campaign[:criteria].each do |criterion|
23
+ p criterion[:xsi_type]
24
+ p criterion
25
25
  end
26
26
 
27
27
  puts "\nAd groups (#{$campaign[:ad_groups].size} in total):"
@@ -3,7 +3,7 @@
3
3
  require 'adapi'
4
4
 
5
5
  # create campaign
6
- require File.join(File.dirname(__FILE__), 'add_bare_ad_group')
6
+ require_relative 'add_bare_ad_group'
7
7
 
8
8
  # find all ad_groups for campaign
9
9
 
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: utf-8
3
+ #
4
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
5
+ #
6
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
7
+ #
8
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17
+ # implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ # This example illustrates how to retrieve all the campaign targets. To set
22
+ # campaign targets, run add_campaign_targeting_criteria.rb.
23
+ #
24
+ # Tags: CampaignCriterionService.get
25
+
26
+ require 'adwords_api'
27
+
28
+ API_VERSION = :v201109
29
+ PAGE_SIZE = 500
30
+
31
+ def get_campaign_targeting_criteria()
32
+ # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
33
+ # when called without parameters.
34
+ adwords = AdwordsApi::Api.new
35
+
36
+ # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
37
+ # the configuration file or provide your own logger:
38
+ # adwords.logger = Logger.new('adwords_xml.log')
39
+
40
+ campaign_criterion_srv =
41
+ adwords.service(:CampaignCriterionService, API_VERSION)
42
+
43
+ # Specify campaign ID to get targeting for.
44
+ campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i
45
+
46
+ # Selector to get all the targeting for this campaign.
47
+ selector = {
48
+ :fields => ['Id', 'CriteriaType', 'KeywordText'],
49
+ :predicates => [
50
+ {:field => 'CampaignId', :operator => 'EQUALS', :values => [campaign_id]}
51
+ ],
52
+ :paging => {
53
+ :start_index => 0,
54
+ :number_results => PAGE_SIZE
55
+ }
56
+ }
57
+
58
+ # Set initial values.
59
+ offset, page = 0, {}
60
+
61
+ begin
62
+ page = campaign_criterion_srv.get(selector)
63
+ if page[:entries]
64
+ page[:entries].each do |typed_criterion|
65
+ negative = typed_criterion[:xsi_type] == 'NegativeCampaignCriterion' ?
66
+ ' (negative)' : ''
67
+ criterion = typed_criterion[:criterion]
68
+ puts ("Campaign criterion%s with ID %d, type '%s' and text '%s'" +
69
+ " was found.") %
70
+ [negative, criterion[:id], criterion[:type], criterion[:text]]
71
+ end
72
+ # Increment values to request the next page.
73
+ offset += PAGE_SIZE
74
+ selector[:paging][:start_index] = offset
75
+ end
76
+ end while page[:total_num_entries] > offset
77
+
78
+ if page.include?(:total_num_entries)
79
+ puts "\tCampaign ID %d has %d criteria." %
80
+ [campaign_id, page[:total_num_entries]]
81
+ end
82
+ end
83
+
84
+ if __FILE__ == $0
85
+ begin
86
+ get_campaign_targeting_criteria()
87
+
88
+ # HTTP errors.
89
+ rescue AdsCommon::Errors::HttpError => e
90
+ puts "HTTP Error: %s" % e
91
+
92
+ # API errors.
93
+ rescue AdwordsApi::Errors::ApiException => e
94
+ puts "Message: %s" % e.message
95
+ puts 'Errors:'
96
+ e.errors.each_with_index do |error, index|
97
+ puts "\tError [%d]:" % (index + 1)
98
+ error.each do |field, value|
99
+ puts "\t\t%s: %s" % [field, value]
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'adapi'
4
+
5
+ # find location by LocationService
6
+
7
+ $search_params = [
8
+ { :country => 'CZ' },
9
+ { :country => 'CZ', :province => 'Prague' },
10
+ { :country => 'CZ', :province => 'Prague', :city => 'Prague' },
11
+ { :province => 'Prague' },
12
+ { :city => 'Prague' }
13
+ ]
14
+
15
+ $search_params.each do |params|
16
+ $location = Adapi::Location.find(params)
17
+
18
+ puts "\nSearched for: " + params.inspect
19
+ # puts "Found Location ID: #{Adapi::Location.location_tree($location)}"
20
+ puts "Found Location ID: #{$location[:id]}"
21
+ end
@@ -2,8 +2,11 @@
2
2
 
3
3
  require 'adapi'
4
4
 
5
- # create campaign by single command, with campaing targets, with ad_groups
6
- # including keywords and ads
5
+ # This test tries to create a complete campaign and fails because ad is
6
+ # intentionally left without url. The point is to test how create_campaign
7
+ # fails: campaign status should be set to DELETED and name changed (so the
8
+ # name isn't blocked and another campaign can be created with the same name
9
+ # eventually)
7
10
 
8
11
  campaign_data = {
9
12
  :name => "Campaign #%d" % (Time.new.to_f * 1000).to_i,
@@ -19,9 +22,8 @@ campaign_data = {
19
22
  :target_content_contextual => false
20
23
  },
21
24
 
22
- :targets => {
25
+ :criteria => {
23
26
  :language => [ 'en', 'cs' ],
24
- # TODO test together with city target
25
27
  :geo => { :proximity => { :geo_point => '38.89859,-77.035971', :radius => '10 km' } }
26
28
  },
27
29
 
@@ -37,7 +39,7 @@ campaign_data = {
37
39
  :headline => "Code like Neo",
38
40
  :description1 => 'Need mad coding skills?',
39
41
  :description2 => 'Check out my new blog!',
40
- :url => '', # THIS FAILS
42
+ :url => '', # THIS SHOULD FAIL
41
43
  :display_url => 'http://www.demcodez.com'
42
44
  }
43
45
  ]
@@ -54,4 +56,3 @@ pp $campaign.attributes
54
56
 
55
57
  p "with errors:"
56
58
  pp $campaign.errors.to_a
57
-
@@ -1,7 +1,7 @@
1
1
  require 'adapi'
2
2
 
3
3
  # create campaign
4
- require File.join(File.dirname(__FILE__), 'add_bare_campaign')
4
+ require_relative 'add_bare_campaign'
5
5
 
6
6
  p "ORIGINAL CAMPAIGN:"
7
7
  pp $campaign.attributes
@@ -2,7 +2,7 @@
2
2
  require 'adapi'
3
3
 
4
4
  # create campaign
5
- require File.join(File.dirname(__FILE__), 'add_bare_campaign')
5
+ require_relative 'add_bare_campaign'
6
6
 
7
7
  p "ORIGINAL STATUS: %s" % $campaign.status
8
8
 
data/lib/adapi.rb CHANGED
@@ -2,38 +2,40 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'adwords_api'
5
+ # require 'logger'
5
6
  require 'yaml'
6
7
  require 'pp'
7
8
 
8
9
  require 'active_model'
9
- # require only ActiveSupport parts that we actually use
10
+ # TODO require only ActiveSupport parts that we actually use
10
11
  require 'active_support/all'
11
12
 
12
13
  require 'adapi/version'
13
14
  require 'adapi/config'
14
15
  require 'adapi/api'
15
16
  require 'adapi/campaign'
17
+ require 'adapi/campaign_criterion'
16
18
  require 'adapi/campaign_target'
17
19
  require 'adapi/ad_group'
18
20
  require 'adapi/ad_group_criterion'
19
21
  require 'adapi/keyword'
20
22
  require 'adapi/ad'
21
23
  require 'adapi/ad/text_ad'
24
+ require 'adapi/ad_param'
25
+ require 'adapi/constant_data'
26
+ require 'adapi/constant_data/language'
27
+ require 'adapi/location'
22
28
 
23
- # monkeypatch HTTPI
29
+ # monkeypatch HTTPI - important, check the file!
24
30
  require 'httpi_request_monkeypatch'
25
31
 
26
32
  HTTPI.adapter = :curb
27
- # supress HTTPI output
28
- # HTTPI.log = false
33
+ HTTPI.log = false # supress HTTPI output
29
34
 
30
35
  module Adapi
31
- API_VERSION = :v201101
36
+ API_VERSION = :v201109
32
37
  end
33
38
 
34
- # check ruby version, should be 1.9
35
- # FIXME there's gotta be more elegant way to do this
36
- `ruby -v`.to_s =~ /^ruby (\d\.\d)\./
37
- if $1.to_f < 1.9
39
+ if RUBY_VERSION < '1.9'
38
40
  puts "WARNING: please use ruby 1.9, adapi gem won't work properly in 1.8 and earlier versions"
39
41
  end
@@ -101,7 +101,8 @@ module Adapi
101
101
  # supported condition parameters: ad_group_id and id
102
102
  predicates = [ :ad_group_id, :id ].map do |param_name|
103
103
  if params[param_name]
104
- {:field => param_name.to_s.camelcase, :operator => 'EQUALS', :values => params[param_name] }
104
+ value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
105
+ {:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
105
106
  end
106
107
  end.compact
107
108
 
@@ -24,19 +24,22 @@ module Adapi
24
24
  # convert bids to GoogleApi format
25
25
  #
26
26
  # can be either string (just xsi_type) or hash (xsi_type with params)
27
- # althogh I'm not sure if just string makes sense in this case
27
+ # although I'm not sure if just string makes sense in this case
28
28
  #
29
29
  if @bids
30
30
  unless @bids.is_a?(Hash)
31
31
  @bids = { :xsi_type => @bids }
32
32
  end
33
-
34
- if @bids[:keyword_max_cpc] and not @bids[:keyword_max_cpc].is_a?(Hash)
35
- @bids[:keyword_max_cpc] = {
36
- :amount => {
37
- :micro_amount => Api.to_micro_units(@bids[:keyword_max_cpc])
33
+
34
+ # convert bid amounts to micro_amounts
35
+ [ :proxy_keyword_max_cpc, :proxy_site_max_cpc ].each do |k|
36
+ if @bids[k] and not @bids[k].is_a?(Hash)
37
+ @bids[k] = {
38
+ :amount => {
39
+ :micro_amount => Api.to_micro_units(@bids[k])
40
+ }
38
41
  }
39
- }
42
+ end
40
43
  end
41
44
  end
42
45
 
@@ -93,10 +96,11 @@ module Adapi
93
96
  first_only = (amount.to_sym == :first)
94
97
 
95
98
  raise "Campaign ID is required" unless params[:campaign_id]
96
-
99
+
97
100
  predicates = [ :campaign_id, :id ].map do |param_name|
98
101
  if params[param_name]
99
- {:field => param_name.to_s.camelcase, :operator => 'EQUALS', :values => params[param_name] }
102
+ value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
103
+ {:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
100
104
  end
101
105
  end.compact
102
106
 
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+
3
+ module Adapi
4
+ class AdParam < Api
5
+ attr_accessor :ad_group_id, :criterion_id, :insertion_text, :param_index
6
+
7
+ validates_presence_of :ad_group_id, :criterion_id
8
+
9
+ def attributes
10
+ super.merge(
11
+ 'ad_group_id' => ad_group_id, 'criterion_id' => criterion_id,
12
+ 'insertion_text' => insertion_text, 'param_index' => param_index
13
+ )
14
+ end
15
+
16
+ def initialize(params = {})
17
+ params[:service_name] = :AdParamService
18
+
19
+ %w{ ad_group_id criterion_id insertion_text param_index }.each do |param_name|
20
+ self.send "#{param_name}=", params[param_name.to_sym]
21
+ end
22
+
23
+ super(params)
24
+ end
25
+
26
+ def create
27
+ operation = {
28
+ :operator => 'SET',
29
+ :operand => serializable_hash
30
+ }
31
+
32
+ begin
33
+ response = @service.mutate([operation])
34
+
35
+ #rescue AdsCommon::Errors::HttpError => e
36
+ #self.errors.add(:base, e.message)
37
+
38
+ ## traps any exceptions raised by AdWords API
39
+ #rescue AdwordsApi::Errors::ApiException => e
40
+ # # return PolicyViolations so they can be sent again
41
+ # e.errors.each do |error|
42
+ # if (error[:api_error_type] == 'PolicyViolationError') && error[:is_exemptable]
43
+ # self.errors.add(error[:api_error_type], error[:key])
44
+ # else
45
+ # # otherwise, just report the errors
46
+ # self.errors.add( "[#{self.xsi_type.underscore}]", "#{error[:error_string]} @ #{error[:field_path]}")
47
+ # end
48
+ # end
49
+ end
50
+
51
+ response
52
+ end
53
+
54
+ def self.find(params = {})
55
+ params.symbolize_keys!
56
+
57
+ predicates = [ :ad_group_id, :criterion_id ].map do |param_name|
58
+ if params[param_name]
59
+ value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
60
+ {:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
61
+ end
62
+ end.compact
63
+
64
+ selector = {
65
+ :fields => ['AdGroupId', 'CriterionId', 'InsertionText', 'ParamIndex'],
66
+ :predicates => predicates
67
+ }
68
+
69
+ response = AdParam.new.service.get(selector)
70
+
71
+ response = (response and response[:entries]) ? response[:entries] : []
72
+
73
+ response.map! do |ad_params_data|
74
+ AdParam.new(ad_params_data)
75
+ end
76
+
77
+ response
78
+ end
79
+
80
+ def serializable_hash
81
+ {
82
+ :ad_group_id => ad_group_id,
83
+ :criterion_id => criterion_id,
84
+ :param_index => param_index,
85
+ :insertion_text => insertion_text
86
+ }
87
+ end
88
+ end
89
+ end