adapi 0.0.7 → 0.0.8
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.markdown +223 -7
- data/adapi.gemspec +4 -4
- data/lib/adapi.rb +9 -6
- data/lib/adapi/api.rb +3 -10
- data/lib/adapi/campaign_criterion.rb +1 -1
- data/lib/adapi/config.rb +23 -0
- data/lib/adapi/constant_data/province.rb +1128 -0
- data/lib/adapi/location.rb +24 -9
- data/lib/adapi/version.rb +10 -1
- data/lib/httpi_monkeypatch.rb +17 -0
- data/lib/savon_monkeypatch.rb +43 -0
- data/test/factories/ad_group_factory.rb +10 -6
- data/test/factories/ad_text_factory.rb +11 -7
- data/test/factories/campaign_factory.rb +15 -0
- data/test/integration/create_campaign_test.rb +19 -4
- data/test/integration/find_location_test.rb +54 -0
- data/test/unit/ad/ad_text_test.rb +1 -1
- data/test/unit/ad_group_test.rb +1 -1
- data/test/unit/campaign_test.rb +50 -0
- metadata +24 -14
- data/lib/ads_common_monkeypatch.rb +0 -9
- data/lib/httpi_request_monkeypatch.rb +0 -40
data/lib/adapi/location.rb
CHANGED
@@ -23,6 +23,7 @@ module Adapi
|
|
23
23
|
# Location.find(:country => 'CZ', :region => 'Prague' :city => 'Prague')
|
24
24
|
#
|
25
25
|
# TODO add legacy aliases: :city_name, :province_code, :country_code
|
26
|
+
# TODO move search by id into separate method
|
26
27
|
#
|
27
28
|
def self.find(amount = :all, params = {})
|
28
29
|
# set amount = :first by default
|
@@ -37,17 +38,23 @@ module Adapi
|
|
37
38
|
# in which language to retrieve locations
|
38
39
|
params[:locale] ||= 'en'
|
39
40
|
|
40
|
-
# support for legacy parameter
|
41
|
+
# support for legacy :province parameter
|
41
42
|
if params[:province] and not params[:region]
|
42
43
|
params[:region] = params[:province]
|
43
44
|
end
|
44
45
|
|
46
|
+
# if :region parameter is in old format, replace it with province name
|
47
|
+
if params[:region] && (params[:region] =~ /^[A-Z]{2}\-\w{1,3}$/)
|
48
|
+
province_name = ConstantData::Location::Province.find_name_by_province_code(params[:region])
|
49
|
+
params[:region] = province_name if province_name
|
50
|
+
end
|
51
|
+
|
45
52
|
# if :country parameter is valid country code, replace it with country name
|
46
53
|
if params[:country] && (params[:country].size == 2)
|
47
54
|
country_name = ConstantData::Location::Country.find_name_by_country_code(params[:country])
|
48
55
|
params[:country] = country_name if country_name
|
49
56
|
end
|
50
|
-
|
57
|
+
|
51
58
|
# determine by what criteria to search
|
52
59
|
location_type, location_name = nil, nil
|
53
60
|
LOCATIONS_HIERARCHY.each do |param_name|
|
@@ -58,7 +65,7 @@ module Adapi
|
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
61
|
-
raise "Invalid params"
|
68
|
+
raise "Invalid params" if location_name.nil? and not params[:id]
|
62
69
|
|
63
70
|
selector = {
|
64
71
|
:fields => ['Id', 'LocationName', 'CanonicalName', 'DisplayType', 'ParentLocations', 'Reach'],
|
@@ -70,23 +77,31 @@ module Adapi
|
|
70
77
|
]
|
71
78
|
}
|
72
79
|
|
80
|
+
if params[:id]
|
81
|
+
selector[:predicates] = [
|
82
|
+
{ :field => 'Id', :operator => 'EQUALS', :values => [ params[:id].to_i ] }
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
73
86
|
# returns array of locations. and now the fun begins
|
74
87
|
locations = Location.new.service.get(selector)
|
88
|
+
|
89
|
+
if params[:id]
|
90
|
+
return locations.first[:location] rescue nil
|
91
|
+
end
|
75
92
|
|
76
93
|
# now we have to find location with correct display_type and TODO hierarchy
|
77
94
|
# problematic example: Prague is both city and province (region)
|
78
95
|
|
79
|
-
location = nil
|
80
96
|
locations.each do |entry|
|
97
|
+
next unless entry.is_a?(Hash)
|
98
|
+
|
81
99
|
if entry[:location][:display_type] == location_type
|
82
|
-
|
83
|
-
break
|
100
|
+
return entry[:location]
|
84
101
|
end
|
85
102
|
end
|
86
103
|
|
87
|
-
|
88
|
-
|
89
|
-
location
|
104
|
+
nil
|
90
105
|
end
|
91
106
|
|
92
107
|
# Displays location tree - location with its parents
|
data/lib/adapi/version.rb
CHANGED
@@ -1,10 +1,19 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Adapi
|
4
|
-
VERSION = "0.0.
|
4
|
+
VERSION = "0.0.8"
|
5
5
|
|
6
6
|
# CHANGELOG:
|
7
7
|
#
|
8
|
+
# 0.0.8
|
9
|
+
# updated to AdWords API version v201109
|
10
|
+
# updated gem dependencies
|
11
|
+
# removed obsolete monkeypatches
|
12
|
+
# improved SOAP logging - enable pretty logging and configurable log path
|
13
|
+
# added conversion of legacy province_code to province_name in location search
|
14
|
+
# added tests for Campaign, CampaignCriterion and Location service
|
15
|
+
# added Getting Started section to README
|
16
|
+
#
|
8
17
|
# 0.0.7
|
9
18
|
# fix Location search by country code
|
10
19
|
# hotfix OAuth
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# manually hardcode timeouts for HTTPI to 5 minutes (300 seconds)
|
4
|
+
# TODO check if there's still no way to do it properly through HTTPI
|
5
|
+
# TODO enable user to set timeout in adapi configuration
|
6
|
+
|
7
|
+
module HTTPI
|
8
|
+
class Request
|
9
|
+
def open_timeout
|
10
|
+
300
|
11
|
+
end
|
12
|
+
|
13
|
+
def read_timeout
|
14
|
+
300
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
# This monkeypatch adds option to prettify Savon SOAP log
|
3
|
+
#
|
4
|
+
# Not necessary, but very convenient feature - unless you're fond of deciphering
|
5
|
+
# endless SOAP request/response one-liners.
|
6
|
+
#
|
7
|
+
# Can be safely removed. Should be only temporary, as the idea is taken straight
|
8
|
+
# out of Savon pull requests and should be included in its future version.
|
9
|
+
|
10
|
+
module Savon
|
11
|
+
module Config
|
12
|
+
|
13
|
+
# Logs a given +message+. Optionally filtered if +xml+ is truthy.
|
14
|
+
def log(message, xml = false)
|
15
|
+
return unless log?
|
16
|
+
|
17
|
+
# ORIG
|
18
|
+
# message = filter_xml(message) if xml && !log_filter.empty?
|
19
|
+
|
20
|
+
# NEW: always run filter_xml method
|
21
|
+
message = filter_xml(message) # if xml && !log_filter.empty?
|
22
|
+
|
23
|
+
logger.send log_level, message
|
24
|
+
end
|
25
|
+
|
26
|
+
# Filters the given +xml+ based on log filter.
|
27
|
+
def filter_xml(xml)
|
28
|
+
doc = Nokogiri::XML(xml)
|
29
|
+
return xml unless doc.errors.empty?
|
30
|
+
|
31
|
+
log_filter.each do |filter|
|
32
|
+
doc.xpath("//*[local-name()='#{filter}']").map { |node| node.content = "***FILTERED***" }
|
33
|
+
end
|
34
|
+
|
35
|
+
# ORIG
|
36
|
+
# doc.root.to_s
|
37
|
+
|
38
|
+
# NEW: return formatted SOAP
|
39
|
+
doc.to_xml(:indent => 2)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -2,10 +2,14 @@
|
|
2
2
|
|
3
3
|
the_bids = { :xsi_type => 'ManualCPCAdGroupBids', :keyword_max_cpc => 10 }
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
FactoryGirl.define do
|
6
|
+
|
7
|
+
factory :ad_group, :class => Adapi::AdGroup do
|
8
|
+
sequence(:campaign_id) { |n| n }
|
9
|
+
name "AdGroup %d" % (Time.new.to_f * 1000).to_i
|
10
|
+
status 'ENABLED'
|
11
|
+
bids the_bids
|
12
|
+
keywords [ 'dem codez', '"top coder"', '[-code]' ]
|
13
|
+
end
|
14
|
+
|
11
15
|
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
FactoryGirl.define do
|
4
|
+
|
5
|
+
factory :text_ad, :class => Adapi::Ad::TextAd do
|
6
|
+
sequence(:ad_group_id) { |n| n }
|
7
|
+
headline 'Code like Neo'
|
8
|
+
description1 'Need mad coding skills?'
|
9
|
+
description2 'Check out my new blog!'
|
10
|
+
url 'http://www.demcodez.com'
|
11
|
+
display_url 'http://www.demcodez.com'
|
12
|
+
end
|
13
|
+
|
10
14
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
|
5
|
+
a_bidding_strategy = { :xsi_type => 'BudgetOptimizer', :bid_ceiling => 55 }
|
6
|
+
|
7
|
+
factory :valid_campaign, :class => Adapi::Campaign do
|
8
|
+
sequence(:id) { |n| n }
|
9
|
+
sequence(:name) { |n| 'Campaign #%s' % n }
|
10
|
+
status 'PAUSED'
|
11
|
+
bidding_strategy a_bidding_strategy
|
12
|
+
budget 50
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -3,15 +3,15 @@
|
|
3
3
|
require 'test_helper'
|
4
4
|
|
5
5
|
module Adapi
|
6
|
-
class
|
7
|
-
context "non-existent
|
6
|
+
class CampaignCreateTest < Test::Unit::TestCase
|
7
|
+
context "non-existent campaign" do
|
8
8
|
should "not be found" do
|
9
9
|
# FIXME randomly generated id, but it might actually exist
|
10
10
|
assert_nil Adapi::Campaign.find(Time.new.to_i)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
context "existing
|
14
|
+
context "existing campaign" do
|
15
15
|
setup do
|
16
16
|
@campaign_data = {
|
17
17
|
:name => "Campaign #%d" % (Time.new.to_f * 1000).to_i,
|
@@ -23,6 +23,13 @@ module Adapi
|
|
23
23
|
:target_search_network => true,
|
24
24
|
:target_content_network => false,
|
25
25
|
:target_content_contextual => false
|
26
|
+
},
|
27
|
+
|
28
|
+
:criteria => {
|
29
|
+
:language => %w{ en cs },
|
30
|
+
:location => {
|
31
|
+
:name => { :city => 'Brno', :region => 'CZ-JM', :country => 'CZ' }
|
32
|
+
}
|
26
33
|
}
|
27
34
|
}
|
28
35
|
|
@@ -37,6 +44,14 @@ module Adapi
|
|
37
44
|
|
38
45
|
assert_equal @campaign_data[:status], @campaign.status
|
39
46
|
assert_equal @campaign_data[:name], @campaign.name
|
47
|
+
|
48
|
+
# TODO move campaign criteria to separate test
|
49
|
+
criteria = Adapi::CampaignCriterion.find(:campaign_id => @campaign.id)
|
50
|
+
assert_equal %w{ cs en }, criteria.map { |c| c[:code] if c[:type] == "LANGUAGE" }.compact.sort
|
51
|
+
|
52
|
+
location = criteria.find { |c| c[:type] == "LOCATION" }
|
53
|
+
assert_equal "Brno", location[:location_name]
|
54
|
+
assert_equal "City", location[:display_type]
|
40
55
|
end
|
41
56
|
|
42
57
|
should "still be found after deletion" do
|
@@ -46,7 +61,7 @@ module Adapi
|
|
46
61
|
|
47
62
|
assert_equal "DELETED", deleted_campaign.status
|
48
63
|
assert_equal @campaign_data[:name], deleted_campaign.name
|
49
|
-
end
|
64
|
+
end
|
50
65
|
|
51
66
|
end
|
52
67
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
module Adapi
|
6
|
+
class FindLocationTest < Test::Unit::TestCase
|
7
|
+
context "non-existent Location" do
|
8
|
+
setup do
|
9
|
+
@location = Adapi::Location.find( :city => "Shamballa" )
|
10
|
+
end
|
11
|
+
|
12
|
+
should "not be found" do
|
13
|
+
assert_nil @location
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "existing Location" do
|
18
|
+
|
19
|
+
should "be found by name" do
|
20
|
+
@location = Adapi::Location.find( :country=>"CZ", :province=>"Prague", :city=>"Prague" )
|
21
|
+
assert_not_nil @location
|
22
|
+
assert_equal 1003803, @location[:id]
|
23
|
+
assert_equal "Prague", @location[:location_name]
|
24
|
+
assert_equal "City", @location[:display_type]
|
25
|
+
end
|
26
|
+
|
27
|
+
should "be found by id" do
|
28
|
+
@location = Adapi::Location.find( :id => 1003803 )
|
29
|
+
assert_not_nil @location
|
30
|
+
assert_equal 1003803, @location[:id]
|
31
|
+
assert_equal "Prague", @location[:location_name]
|
32
|
+
assert_equal "City", @location[:display_type]
|
33
|
+
end
|
34
|
+
|
35
|
+
should "be found by country_code" do
|
36
|
+
@location = Adapi::Location.find( :country => "CZ" )
|
37
|
+
assert_not_nil @location
|
38
|
+
assert_equal 2203, @location[:id]
|
39
|
+
assert_equal "Czech Republic", @location[:location_name]
|
40
|
+
assert_equal "Country", @location[:display_type]
|
41
|
+
end
|
42
|
+
|
43
|
+
should "be found by province_code" do
|
44
|
+
@location = Adapi::Location.find( :country => "CZ", :province => "CZ-JM" )
|
45
|
+
assert_not_nil @location
|
46
|
+
assert_equal 20219, @location[:id]
|
47
|
+
assert_equal "South Moravia", @location[:location_name]
|
48
|
+
assert_equal "Region", @location[:display_type]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/test/unit/ad_group_test.rb
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
module Adapi
|
6
|
+
class CampaignTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
context "invalid Campaign" do
|
9
|
+
setup do
|
10
|
+
@campaign = Campaign.new
|
11
|
+
end
|
12
|
+
|
13
|
+
should "not be valid?" do
|
14
|
+
assert ! @campaign.valid?
|
15
|
+
end
|
16
|
+
|
17
|
+
should "return error messages" do
|
18
|
+
@campaign.valid? # creates errors messages
|
19
|
+
assert ! @campaign.errors.full_messages.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "Campaign status" do
|
24
|
+
|
25
|
+
should "be required" do
|
26
|
+
campaign = FactoryGirl.build(:valid_campaign, :status => nil)
|
27
|
+
assert ! campaign.valid?
|
28
|
+
assert campaign.errors.has_key? :status
|
29
|
+
end
|
30
|
+
|
31
|
+
should "should be only ACTIVE, DELETED or PAUSED" do
|
32
|
+
campaign = FactoryGirl.build(:valid_campaign, :status => "TEST")
|
33
|
+
assert ! campaign.valid?
|
34
|
+
assert campaign.errors.has_key? :status
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
context "Campaign name" do
|
40
|
+
|
41
|
+
should "be required" do
|
42
|
+
campaign = FactoryGirl.build(:valid_campaign, :name => nil)
|
43
|
+
assert ! campaign.valid?
|
44
|
+
assert campaign.errors.has_key? :name
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-05-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: google-ads-common
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - '='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.
|
21
|
+
version: 0.7.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - '='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.
|
29
|
+
version: 0.7.0
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: google-adwords-api
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - '='
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 0.
|
37
|
+
version: 0.6.0
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - '='
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 0.
|
45
|
+
version: 0.6.0
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: activemodel
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,7 +98,7 @@ dependencies:
|
|
98
98
|
requirements:
|
99
99
|
- - ~>
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version:
|
101
|
+
version: 0.8.0
|
102
102
|
type: :runtime
|
103
103
|
prerelease: false
|
104
104
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -106,7 +106,7 @@ dependencies:
|
|
106
106
|
requirements:
|
107
107
|
- - ~>
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
109
|
+
version: 0.8.0
|
110
110
|
- !ruby/object:Gem::Dependency
|
111
111
|
name: yard
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -192,17 +192,17 @@ dependencies:
|
|
192
192
|
requirement: !ruby/object:Gem::Requirement
|
193
193
|
none: false
|
194
194
|
requirements:
|
195
|
-
- -
|
195
|
+
- - ~>
|
196
196
|
- !ruby/object:Gem::Version
|
197
|
-
version:
|
197
|
+
version: 3.3.0
|
198
198
|
type: :development
|
199
199
|
prerelease: false
|
200
200
|
version_requirements: !ruby/object:Gem::Requirement
|
201
201
|
none: false
|
202
202
|
requirements:
|
203
|
-
- -
|
203
|
+
- - ~>
|
204
204
|
- !ruby/object:Gem::Version
|
205
|
-
version:
|
205
|
+
version: 3.3.0
|
206
206
|
- !ruby/object:Gem::Dependency
|
207
207
|
name: minitest
|
208
208
|
requirement: !ruby/object:Gem::Requirement
|
@@ -270,23 +270,27 @@ files:
|
|
270
270
|
- lib/adapi/constant_data.rb
|
271
271
|
- lib/adapi/constant_data/country.rb
|
272
272
|
- lib/adapi/constant_data/language.rb
|
273
|
+
- lib/adapi/constant_data/province.rb
|
273
274
|
- lib/adapi/keyword.rb
|
274
275
|
- lib/adapi/location.rb
|
275
276
|
- lib/adapi/version.rb
|
276
|
-
- lib/
|
277
|
-
- lib/
|
277
|
+
- lib/httpi_monkeypatch.rb
|
278
|
+
- lib/savon_monkeypatch.rb
|
278
279
|
- test/config/adapi.yml.template
|
279
280
|
- test/config/adwords_api.yml.template
|
280
281
|
- test/factories/.gitignore
|
281
282
|
- test/factories/ad_group_factory.rb
|
282
283
|
- test/factories/ad_text_factory.rb
|
284
|
+
- test/factories/campaign_factory.rb
|
283
285
|
- test/integration/create_campaign_test.rb
|
286
|
+
- test/integration/find_location_test.rb
|
284
287
|
- test/test_helper.rb
|
285
288
|
- test/unit/.gitignore
|
286
289
|
- test/unit/ad/ad_text_test.rb
|
287
290
|
- test/unit/ad_group_test.rb
|
288
291
|
- test/unit/ad_test.rb
|
289
292
|
- test/unit/campaign_criterion_test.rb
|
293
|
+
- test/unit/campaign_test.rb
|
290
294
|
- test/unit/config_test.rb
|
291
295
|
homepage: https://github.com/lstejskal/adapi
|
292
296
|
licenses: []
|
@@ -300,12 +304,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
300
304
|
- - ! '>='
|
301
305
|
- !ruby/object:Gem::Version
|
302
306
|
version: '0'
|
307
|
+
segments:
|
308
|
+
- 0
|
309
|
+
hash: -478548313315719997
|
303
310
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
304
311
|
none: false
|
305
312
|
requirements:
|
306
313
|
- - ! '>='
|
307
314
|
- !ruby/object:Gem::Version
|
308
315
|
version: '0'
|
316
|
+
segments:
|
317
|
+
- 0
|
318
|
+
hash: -478548313315719997
|
309
319
|
requirements: []
|
310
320
|
rubyforge_project: adapi
|
311
321
|
rubygems_version: 1.8.21
|