adapi 0.0.2 → 0.0.3
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.
- data/README.rdoc +74 -39
- data/adapi.gemspec +4 -0
- data/examples/add_ad_group.rb +6 -22
- data/examples/add_bare_ad_group.rb +11 -6
- data/examples/add_bare_campaign.rb +5 -7
- data/examples/add_campaign.rb +12 -29
- data/examples/add_campaign_targets.rb +16 -7
- data/examples/add_invalid_ad_group.rb +35 -0
- data/examples/add_invalid_text_ad.rb +30 -0
- data/examples/add_keywords.rb +14 -0
- data/examples/add_text_ad.rb +23 -0
- data/examples/log_to_specific_account.rb +26 -13
- data/examples/rollback_campaign.rb +56 -0
- data/examples/update_campaign.rb +14 -15
- data/examples/update_campaign_status.rb +6 -6
- data/lib/adapi.rb +19 -1
- data/lib/adapi/ad.rb +55 -42
- data/lib/adapi/ad/text_ad.rb +126 -0
- data/lib/adapi/ad_group.rb +80 -42
- data/lib/adapi/ad_group_criterion.rb +13 -55
- data/lib/adapi/api.rb +107 -1
- data/lib/adapi/campaign.rb +144 -94
- data/lib/adapi/campaign_target.rb +93 -45
- data/lib/adapi/config.rb +16 -2
- data/lib/adapi/keyword.rb +109 -0
- data/lib/adapi/version.rb +12 -1
- data/lib/httpi_request_monkeypatch.rb +35 -0
- data/test/factories/ad_group_factory.rb +20 -0
- data/test/factories/ad_text_factory.rb +9 -0
- data/test/test_helper.rb +3 -3
- data/test/unit/ad/ad_text_test.rb +30 -0
- data/test/unit/ad_group_test.rb +34 -0
- data/test/unit/ad_test.rb +12 -0
- data/test/unit/campaign_target_test.rb +18 -3
- metadata +122 -109
- data/examples/add_ad.rb +0 -17
- data/examples/add_ad_group_criteria.rb +0 -20
- data/examples/update_campaign_name.rb +0 -14
data/README.rdoc
CHANGED
@@ -13,7 +13,7 @@ single method call: Adapi::Campaign.create(campaign_data). Various convenience
|
|
13
13
|
methods are added to the models, for example: campaign.pause! (pauses
|
14
14
|
campaign). Adapi enables multiple AdWords accounts to be used at the same time.
|
15
15
|
|
16
|
-
Adapi is STILL IN DEVELOPMENT
|
16
|
+
Adapi is STILL IN DEVELOPMENT and not nearly done yet! Version 1.0.0 should have
|
17
17
|
all planned functionality.
|
18
18
|
|
19
19
|
== Installation
|
@@ -25,12 +25,50 @@ all planned functionality.
|
|
25
25
|
bundle install
|
26
26
|
rake install
|
27
27
|
|
28
|
-
== Configuration
|
29
|
-
|
30
|
-
This section explains how to connect to specific account and client.
|
31
|
-
|
32
|
-
|
33
|
-
can
|
28
|
+
== Configuration
|
29
|
+
|
30
|
+
This section explains how to connect to specific AdWords account and client.
|
31
|
+
|
32
|
+
You can set many AdWords accounts to connect to and switch between while running
|
33
|
+
the application. You can even update single values of the settings on-the-fly.
|
34
|
+
|
35
|
+
# load the settings
|
36
|
+
Adapi::Config.load_settings(:in_hash => {
|
37
|
+
:coca_cola => {
|
38
|
+
:authentication => {
|
39
|
+
:method => 'ClientLogin',
|
40
|
+
:email => 'coca_cola_email@gmail.com',
|
41
|
+
:password => 'coca_cola_password',
|
42
|
+
:developer_token => 'coca_cola_developer_token',
|
43
|
+
:user_agent => 'Coca-Cola Adwords API Test'
|
44
|
+
},
|
45
|
+
:service => {
|
46
|
+
:environment => 'SANDBOX'
|
47
|
+
}
|
48
|
+
},
|
49
|
+
:pepsi => {
|
50
|
+
:authentication => {
|
51
|
+
:method => 'ClientLogin',
|
52
|
+
:email => 'pepsi_email@gmail.com',
|
53
|
+
:password => 'pepsi_password',
|
54
|
+
:developer_token => 'pepsi_developer_token',
|
55
|
+
:user_agent => 'Pepsi Adwords API Test'
|
56
|
+
},
|
57
|
+
:service => {
|
58
|
+
:environment => 'SANDBOX'
|
59
|
+
}
|
60
|
+
}
|
61
|
+
})
|
62
|
+
|
63
|
+
# set to pepsi and specific client
|
64
|
+
Adapi::Config.set(:pepsi, :client_customer_id => '555-666-7777')
|
65
|
+
|
66
|
+
# do some stuff here...
|
67
|
+
|
68
|
+
# set to coca-cola and another client
|
69
|
+
Adapi::Config.set(:coca_cola, :client_customer_id => '777-666-5555')
|
70
|
+
|
71
|
+
# do some stuff here...
|
34
72
|
|
35
73
|
=== Authentication workflow
|
36
74
|
|
@@ -83,38 +121,35 @@ the same configuration will also work for adapi: ~/adwords_api.yml
|
|
83
121
|
Before logging into the Adwords API, you can set global settings through
|
84
122
|
Adapi::Config:
|
85
123
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
:
|
91
|
-
:
|
92
|
-
:
|
93
|
-
:
|
94
|
-
|
95
|
-
:
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
campaign = account.campaigns.create(campaign_data)
|
116
|
-
|
117
|
-
account.campaigns[ campaign[:id] ].ad_groups << ad_group_data
|
124
|
+
# load the settings
|
125
|
+
Adapi::Config.load_settings(:in_hash => {
|
126
|
+
:sandbox => {
|
127
|
+
:authentication => {
|
128
|
+
:method => "ClientLogin"
|
129
|
+
:email => "sandbox_email@gmail.com",
|
130
|
+
:password => "sandbox_password",
|
131
|
+
:developer_token => "sandbox_token",
|
132
|
+
:client_email => "sandbox_client_email@gmail.com",
|
133
|
+
:user_agent => "Adwords API Test"
|
134
|
+
},
|
135
|
+
:service => {
|
136
|
+
:environment => "SANDBOX"
|
137
|
+
}
|
138
|
+
}
|
139
|
+
})
|
140
|
+
|
141
|
+
Adapi::Config.set(:sandbox)
|
142
|
+
|
143
|
+
== TODO API
|
144
|
+
|
145
|
+
AdWords Services are implemented as models, similar to ActiveRecord models of
|
146
|
+
database tables. They are built on ActiveModel.
|
147
|
+
|
148
|
+
== API Version Support
|
149
|
+
|
150
|
+
Adapi supports only the latest version of Google AdWords API: v201101. Older
|
151
|
+
versions will not be supported. v201101 and newer versions will still be
|
152
|
+
supported when new versions are released.
|
118
153
|
|
119
154
|
== Examples
|
120
155
|
|
data/adapi.gemspec
CHANGED
@@ -20,6 +20,10 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
s.add_dependency "google-ads-common", "~> 0.5.0"
|
22
22
|
s.add_dependency "google-adwords-api", "~> 0.4.0"
|
23
|
+
s.add_dependency "activemodel", "~> 3.1.0" # , :require => "active_model"
|
24
|
+
s.add_dependency "activesupport", "~> 3.1.0" #, :require => "active_support"
|
25
|
+
s.add_dependency "rake", "~> 0.9.2"
|
26
|
+
s.add_dependency "curb", "~> 0.7.15"
|
23
27
|
|
24
28
|
s.add_development_dependency "turn"
|
25
29
|
s.add_development_dependency "shoulda"
|
data/examples/add_ad_group.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
require 'adapi'
|
3
3
|
|
4
4
|
# create campaign
|
5
|
-
require 'add_bare_campaign'
|
5
|
+
require File.join(File.dirname(__FILE__), 'add_bare_campaign')
|
6
6
|
|
7
7
|
# create ad group
|
8
8
|
|
@@ -10,29 +10,11 @@ ad_group_data = {
|
|
10
10
|
:name => "AdGroup #%d" % (Time.new.to_f * 1000).to_i,
|
11
11
|
:status => 'ENABLED',
|
12
12
|
:campaign_id => $campaign[:id],
|
13
|
-
:bids => {
|
14
|
-
:xsi_type => 'ManualCPCAdGroupBids',
|
15
|
-
:keyword_max_cpc => {
|
16
|
-
:amount => {
|
17
|
-
:micro_amount => 10000000
|
18
|
-
}
|
19
|
-
}
|
20
|
-
},
|
21
13
|
|
22
|
-
:
|
23
|
-
{ # keyword_criterion
|
24
|
-
:xsi_type => 'BiddableAdGroupCriterion',
|
25
|
-
:criterion => { :xsi_type => 'Keyword', :text => 'codez', :match_type => 'BROAD' }
|
26
|
-
},
|
27
|
-
{ # placement_criterion
|
28
|
-
:xsi_type => 'BiddableAdGroupCriterion',
|
29
|
-
:criterion => { :xsi_type => 'Placement', :url => 'http://www.blogger.com' }
|
30
|
-
}
|
31
|
-
],
|
14
|
+
:keywords => [ 'dem codez', '"top coder"', '[-code]' ],
|
32
15
|
|
33
16
|
:ads => [
|
34
17
|
{
|
35
|
-
:xsi_type => 'TextAd',
|
36
18
|
:headline => "Code like Neo",
|
37
19
|
:description1 => 'Need mad coding skills?',
|
38
20
|
:description2 => 'Check out my new blog!',
|
@@ -40,7 +22,9 @@ ad_group_data = {
|
|
40
22
|
:display_url => 'http://www.demcodez.com'
|
41
23
|
}
|
42
24
|
]
|
43
|
-
|
44
25
|
}
|
45
26
|
|
46
|
-
|
27
|
+
$ad_group = Adapi::AdGroup.create(ad_group_data)
|
28
|
+
|
29
|
+
p "Created ad_group ID #{$ad_group.id} for campaign ID #{$ad_group.campaign_id}"
|
30
|
+
p $ad_group.attributes
|
@@ -2,18 +2,21 @@
|
|
2
2
|
require 'adapi'
|
3
3
|
|
4
4
|
# create campaign
|
5
|
-
require 'add_bare_campaign'
|
5
|
+
require File.join(File.dirname(__FILE__), 'add_bare_campaign')
|
6
6
|
|
7
7
|
# create ad group with basic data only
|
8
8
|
# this script is used as an include in other scripts
|
9
9
|
|
10
10
|
$ad_group_data = {
|
11
|
+
:campaign_id => $campaign[:id],
|
11
12
|
:name => "AdGroup #%d" % (Time.new.to_f * 1000).to_i,
|
12
13
|
:status => 'ENABLED',
|
13
|
-
|
14
|
+
# TODO refactor AdGroup.bids DSL
|
14
15
|
:bids => {
|
15
|
-
|
16
|
-
:
|
16
|
+
# this should be set automatically, it's dependent on Campaign.bids
|
17
|
+
:xsi_type => 'BudgetOptimizerAdGroupBids',
|
18
|
+
# :keyword_max_cpc => 10
|
19
|
+
:proxy_keyword_max_cpc => {
|
17
20
|
:amount => {
|
18
21
|
:micro_amount => 10000000
|
19
22
|
}
|
@@ -21,6 +24,8 @@ $ad_group_data = {
|
|
21
24
|
}
|
22
25
|
}
|
23
26
|
|
24
|
-
$ad_group = Adapi::AdGroup.create(
|
27
|
+
$ad_group = Adapi::AdGroup.create($ad_group_data)
|
28
|
+
|
25
29
|
|
26
|
-
p $ad_group
|
30
|
+
p "Created ad_group ID #{$ad_group.id} for campaign ID #{$ad_group.campaign_id}"
|
31
|
+
p $ad_group.attributes
|
@@ -7,12 +7,9 @@ require 'adapi'
|
|
7
7
|
$campaign_data = {
|
8
8
|
:name => "Campaign #%d" % (Time.new.to_f * 1000).to_i,
|
9
9
|
:status => 'PAUSED',
|
10
|
-
:bidding_strategy =>
|
11
|
-
:
|
12
|
-
|
13
|
-
:amount => { :micro_amount => 50000000 },
|
14
|
-
:delivery_method => 'STANDARD'
|
15
|
-
},
|
10
|
+
# :bidding_strategy => 'ManualCPC',
|
11
|
+
:bidding_strategy => { :xsi_type => 'BudgetOptimizer', :bid_ceiling => 55 },
|
12
|
+
:budget => 50,
|
16
13
|
|
17
14
|
:network_setting => {
|
18
15
|
:target_google_search => true,
|
@@ -24,4 +21,5 @@ $campaign_data = {
|
|
24
21
|
|
25
22
|
$campaign = Adapi::Campaign.create($campaign_data)
|
26
23
|
|
27
|
-
p $campaign
|
24
|
+
p "Created campaign ID #{$campaign.id}"
|
25
|
+
p $campaign.attributes
|
data/examples/add_campaign.rb
CHANGED
@@ -7,12 +7,9 @@ require 'adapi'
|
|
7
7
|
campaign_data = {
|
8
8
|
:name => "Campaign #%d" % (Time.new.to_f * 1000).to_i,
|
9
9
|
:status => 'PAUSED',
|
10
|
-
|
11
|
-
:
|
12
|
-
|
13
|
-
:amount => { :micro_amount => 50000000 },
|
14
|
-
:delivery_method => 'STANDARD'
|
15
|
-
},
|
10
|
+
# Automatic CPC: BudgetOptimizer or ManualCPC
|
11
|
+
:bidding_strategy => { :xsi_type => 'BudgetOptimizer', :bid_ceiling => 100 },
|
12
|
+
:budget => { :amount => 50, :delivery_method => 'STANDARD' },
|
16
13
|
|
17
14
|
:network_setting => {
|
18
15
|
:target_google_search => true,
|
@@ -23,36 +20,19 @@ campaign_data = {
|
|
23
20
|
|
24
21
|
:targets => {
|
25
22
|
:language => [ 'en', 'cs' ],
|
26
|
-
|
23
|
+
# TODO test together with city target
|
24
|
+
:geo => { :proximity => { :geo_point => '38.89859,-77.035971', :radius => '10 km' } }
|
27
25
|
},
|
28
26
|
|
29
27
|
:ad_groups => [
|
30
28
|
{
|
31
29
|
:name => "AdGroup #%d" % (Time.new.to_f * 1000).to_i,
|
32
30
|
:status => 'ENABLED',
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
:amount => {
|
37
|
-
:micro_amount => 10000000
|
38
|
-
}
|
39
|
-
}
|
40
|
-
},
|
41
|
-
|
42
|
-
:criteria => [
|
43
|
-
{ # keyword_criterion
|
44
|
-
:xsi_type => 'BiddableAdGroupCriterion',
|
45
|
-
:criterion => { :xsi_type => 'Keyword', :text => 'codez', :match_type => 'BROAD' }
|
46
|
-
},
|
47
|
-
{ # placement_criterion
|
48
|
-
:xsi_type => 'BiddableAdGroupCriterion',
|
49
|
-
:criterion => { :xsi_type => 'Placement', :url => 'http://www.blogger.com' }
|
50
|
-
}
|
51
|
-
],
|
52
|
-
|
31
|
+
|
32
|
+
:keywords => [ 'dem codez', '"top coder"', "[-code]" ],
|
33
|
+
|
53
34
|
:ads => [
|
54
35
|
{
|
55
|
-
:xsi_type => 'TextAd',
|
56
36
|
:headline => "Code like Neo",
|
57
37
|
:description1 => 'Need mad coding skills?',
|
58
38
|
:description2 => 'Check out my new blog!',
|
@@ -65,4 +45,7 @@ campaign_data = {
|
|
65
45
|
|
66
46
|
}
|
67
47
|
|
68
|
-
|
48
|
+
$campaign = Adapi::Campaign.create(campaign_data)
|
49
|
+
|
50
|
+
p "Created campaign ID #{$campaign.id}"
|
51
|
+
p $campaign.attributes
|
@@ -2,15 +2,24 @@
|
|
2
2
|
require 'adapi'
|
3
3
|
|
4
4
|
# create campaign
|
5
|
-
require 'add_bare_campaign'
|
5
|
+
require File.join(File.dirname(__FILE__), 'add_bare_campaign')
|
6
6
|
|
7
|
-
#
|
8
|
-
|
7
|
+
# TODO we should be able call it from campaign instance, for example:
|
8
|
+
# $campaign.set_targets(:language => [ 'en' ], ...)
|
9
|
+
|
10
|
+
$campaign_target = Adapi::CampaignTarget.new(
|
9
11
|
:campaign_id => $campaign[:id],
|
10
12
|
:targets => {
|
11
|
-
:language => [ 'en'
|
12
|
-
:geo => {
|
13
|
+
:language => [ 'en' ],
|
14
|
+
:geo => {
|
15
|
+
# :country => 'CZ'
|
16
|
+
# :province => 'CZ-PR'
|
17
|
+
# :city => { :city_name => 'Prague', :province_code => 'CZ-PR', :country_code => 'CZ' }
|
18
|
+
:proximity => { :geo_point => '50.083333,14.366667', :radius => '50 km' }
|
19
|
+
}
|
13
20
|
}
|
14
|
-
|
21
|
+
)
|
22
|
+
|
23
|
+
$campaign_target.create
|
15
24
|
|
16
|
-
p
|
25
|
+
p $campaign_target.attributes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
require 'adapi'
|
3
|
+
|
4
|
+
# create campaign
|
5
|
+
require File.join(File.dirname(__FILE__), 'add_bare_campaign')
|
6
|
+
|
7
|
+
# create ad group
|
8
|
+
|
9
|
+
ad_group_data = {
|
10
|
+
:name => "AdGroup #%d" % (Time.new.to_f * 1000).to_i,
|
11
|
+
:status => 'ENABLED',
|
12
|
+
:campaign_id => $campaign[:id],
|
13
|
+
|
14
|
+
:keywords => [ 'dem codez', '"top coder"', '[-code]' ],
|
15
|
+
|
16
|
+
:ads => [
|
17
|
+
{
|
18
|
+
:headline => "Code like Neo",
|
19
|
+
:description1 => 'Need mad coding skills?',
|
20
|
+
:description2 => 'Check out my new blog!',
|
21
|
+
# this should throw an error
|
22
|
+
:url => 'http://www.demcodez.com THIS IS INVALID',
|
23
|
+
:display_url => 'http://www.demcodez.com'
|
24
|
+
}
|
25
|
+
]
|
26
|
+
}
|
27
|
+
|
28
|
+
$ad_group = Adapi::AdGroup.create(ad_group_data)
|
29
|
+
|
30
|
+
if $ad_group.errors.empty?
|
31
|
+
p $ad_group.data
|
32
|
+
else
|
33
|
+
p "ERRORS:"
|
34
|
+
p $ad_group.errors.to_a
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
require 'adapi'
|
3
|
+
|
4
|
+
# PolicyViolations will appread only in production, not in sandbox
|
5
|
+
#
|
6
|
+
#Adapi::Config.load_settings
|
7
|
+
#Adapi::Config.set(:production_settings)
|
8
|
+
#
|
9
|
+
#pp "Running in #{Adapi::Config.read[:service][:environment]}"
|
10
|
+
|
11
|
+
# create ad group
|
12
|
+
require File.join(File.dirname(__FILE__), 'add_bare_ad_group')
|
13
|
+
|
14
|
+
$ad = Adapi::Ad::TextAd.create(
|
15
|
+
:ad_group_id => $ad_group[:id],
|
16
|
+
:headline => "Code a Blog",
|
17
|
+
:description1 => 'Need mad coding skill?',
|
18
|
+
:description2 => 'Check out my new blog!!!', # !!! - this is invalid
|
19
|
+
:url => 'http://www.demcodez.com',
|
20
|
+
:display_url => 'http://www.demcodez.com',
|
21
|
+
:status => 'PAUSED'
|
22
|
+
)
|
23
|
+
|
24
|
+
if $ad.errors.empty?
|
25
|
+
p "OK"
|
26
|
+
p $ad.data
|
27
|
+
else
|
28
|
+
p "ERROR"
|
29
|
+
p $ad.errors.messages
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
require 'adapi'
|
3
|
+
|
4
|
+
# create ad group
|
5
|
+
require File.join(File.dirname(__FILE__), 'add_bare_ad_group')
|
6
|
+
|
7
|
+
$keywords = Adapi::Keyword.new(
|
8
|
+
:ad_group_id => $ad_group[:id],
|
9
|
+
:keywords => [ 'dem codez', '"top coder"', '[-code]' ]
|
10
|
+
)
|
11
|
+
|
12
|
+
$r = $keywords.create
|
13
|
+
|
14
|
+
p $keywords
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
require 'adapi'
|
3
|
+
|
4
|
+
# create ad group
|
5
|
+
require File.join(File.dirname(__FILE__), 'add_bare_ad_group')
|
6
|
+
|
7
|
+
ad = Adapi::Ad::TextAd.create(
|
8
|
+
:ad_group_id => $ad_group[:id],
|
9
|
+
:headline => "Code like Neo",
|
10
|
+
:description1 => 'Need mad coding skills?',
|
11
|
+
:description2 => 'Check out my new blog!',
|
12
|
+
:url => 'http://www.demcodez.com',
|
13
|
+
:display_url => 'http://www.demcodez.com',
|
14
|
+
:status => 'PAUSED'
|
15
|
+
)
|
16
|
+
|
17
|
+
if ad
|
18
|
+
p "OK"
|
19
|
+
p ad.data
|
20
|
+
else
|
21
|
+
p "ERROR"
|
22
|
+
p ad.errors.messages
|
23
|
+
end
|