adapi 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/GUIDELINES.markdown +61 -0
- data/README.markdown +79 -45
- data/Rakefile +31 -3
- data/adapi.gemspec +10 -3
- data/examples/add_ad_group.rb +1 -1
- data/examples/add_bare_ad_group.rb +1 -4
- data/examples/add_campaign.rb +1 -0
- data/examples/add_campaign_criteria.rb +27 -0
- data/examples/add_invalid_ad_group.rb +3 -1
- data/examples/add_invalid_text_ad.rb +1 -1
- data/examples/add_keywords.rb +1 -1
- data/examples/add_negative_campaign_criteria.rb +23 -0
- data/examples/add_text_ad.rb +1 -1
- data/examples/customize_configuration.rb +1 -2
- data/examples/delete_keyword.rb +1 -1
- data/examples/find_campaign.rb +5 -5
- data/examples/find_campaign_ad_groups.rb +1 -1
- data/examples/find_campaign_criteria.rb +103 -0
- data/examples/find_locations.rb +21 -0
- data/examples/rollback_campaign.rb +7 -6
- data/examples/update_campaign.rb +1 -1
- data/examples/update_campaign_status.rb +1 -1
- data/lib/adapi.rb +11 -9
- data/lib/adapi/ad/text_ad.rb +2 -1
- data/lib/adapi/ad_group.rb +13 -9
- data/lib/adapi/ad_param.rb +89 -0
- data/lib/adapi/api.rb +8 -0
- data/lib/adapi/campaign.rb +27 -18
- data/lib/adapi/campaign_criterion.rb +278 -0
- data/lib/adapi/campaign_target.rb +5 -123
- data/lib/adapi/config.rb +36 -31
- data/lib/adapi/constant_data.rb +13 -0
- data/lib/adapi/constant_data/language.rb +45 -0
- data/lib/adapi/keyword.rb +15 -5
- data/lib/adapi/location.rb +91 -0
- data/lib/adapi/version.rb +8 -1
- data/lib/httpi_request_monkeypatch.rb +4 -0
- data/test/config/adapi.yml.template +21 -0
- data/test/config/adwords_api.yml.template +10 -0
- data/test/integration/create_campaign_test.rb +54 -0
- data/test/test_helper.rb +2 -3
- data/test/unit/ad_group_test.rb +3 -4
- data/test/unit/ad_test.rb +1 -1
- data/test/unit/campaign_criterion_test.rb +23 -0
- data/test/unit/config_test.rb +52 -0
- metadata +48 -35
- data/examples/add_campaign_targets.rb +0 -26
- data/test/unit/campaign_target_test.rb +0 -51
data/lib/adapi/api.rb
CHANGED
@@ -28,6 +28,14 @@ module Adapi
|
|
28
28
|
@version = API_VERSION
|
29
29
|
@service = @adwords.service(params[:service_name].to_sym, @version)
|
30
30
|
@params = params
|
31
|
+
|
32
|
+
# TODO add switched for logging (true/false) and log location
|
33
|
+
log_level = Adapi::Config.read[:library][:log_level] rescue nil
|
34
|
+
if log_level
|
35
|
+
logger = Logger.new( File.join(ENV['HOME'], (params[:logfile] || 'adapi.log')) )
|
36
|
+
logger.level = eval("Logger::%s" % Adapi::Config.read[:library][:log_level].to_s.upcase)
|
37
|
+
@adwords.logger = logger
|
38
|
+
end
|
31
39
|
end
|
32
40
|
|
33
41
|
def to_param
|
data/lib/adapi/campaign.rb
CHANGED
@@ -6,12 +6,12 @@ module Adapi
|
|
6
6
|
# http://code.google.com/apis/adwords/docs/reference/latest/CampaignService.Campaign.html
|
7
7
|
#
|
8
8
|
attr_accessor :name, :serving_status, :start_date, :end_date, :budget,
|
9
|
-
:bidding_strategy, :network_setting, :
|
9
|
+
:bidding_strategy, :network_setting, :criteria, :ad_groups
|
10
10
|
|
11
11
|
def attributes
|
12
12
|
super.merge('name' => name, 'start_date' => start_date, 'end_date' => end_date,
|
13
13
|
'budget' => budget, 'bidding_strategy' => bidding_strategy,
|
14
|
-
'network_setting' => network_setting, '
|
14
|
+
'network_setting' => network_setting, 'criteria' => criteria,
|
15
15
|
'ad_groups' => ad_groups)
|
16
16
|
end
|
17
17
|
|
@@ -24,7 +24,7 @@ module Adapi
|
|
24
24
|
@xsi_type = 'Campaign'
|
25
25
|
|
26
26
|
%w{ name status start_date end_date budget bidding_strategy
|
27
|
-
network_setting
|
27
|
+
network_setting criteria ad_groups}.each do |param_name|
|
28
28
|
self.send "#{param_name}=", params[param_name.to_sym]
|
29
29
|
end
|
30
30
|
|
@@ -54,7 +54,7 @@ module Adapi
|
|
54
54
|
# PS: not sure if this should be a default. maybe we don't even need it
|
55
55
|
@budget[:delivery_method] ||= 'STANDARD'
|
56
56
|
|
57
|
-
@
|
57
|
+
@criteria ||= []
|
58
58
|
@ad_groups ||= []
|
59
59
|
|
60
60
|
super(params)
|
@@ -81,15 +81,15 @@ module Adapi
|
|
81
81
|
|
82
82
|
self.id = response[:value].first[:id] rescue nil
|
83
83
|
|
84
|
-
# create targets if they are available
|
85
|
-
if
|
86
|
-
|
84
|
+
# create criteria (former targets) if they are available
|
85
|
+
if criteria.size > 0
|
86
|
+
criterion = Adapi::CampaignCriterion.create(
|
87
87
|
:campaign_id => @id,
|
88
|
-
:
|
88
|
+
:criteria => criteria
|
89
89
|
)
|
90
90
|
|
91
|
-
if (
|
92
|
-
self.errors.add("[campaign
|
91
|
+
if (criterion.errors.size > 0)
|
92
|
+
self.errors.add("[campaign criterion]", criterion.errors.to_a)
|
93
93
|
self.rollback
|
94
94
|
return false
|
95
95
|
end
|
@@ -116,9 +116,8 @@ module Adapi
|
|
116
116
|
# TODO implement class method
|
117
117
|
#
|
118
118
|
def update(params = {})
|
119
|
-
#
|
120
|
-
|
121
|
-
response = self.mutate(
|
119
|
+
# HOTFIX can't use current instance, gotta create new one
|
120
|
+
response = Adapi::Campaign.new().mutate(
|
122
121
|
:operator => 'SET',
|
123
122
|
:operand => params.merge(:id => @id)
|
124
123
|
)
|
@@ -128,16 +127,24 @@ module Adapi
|
|
128
127
|
# faster than self.find
|
129
128
|
params.each_pair { |k,v| self.send("#{k}=", v) }
|
130
129
|
|
131
|
-
true
|
130
|
+
true
|
132
131
|
end
|
133
132
|
|
134
133
|
def activate; update(:status => 'ACTIVE'); end
|
135
134
|
def pause; update(:status => 'PAUSED'); end
|
135
|
+
|
136
|
+
# Deletes campaign - which means, sets its status to deleted (because
|
137
|
+
# AdWords campaigns are never really deleted.)
|
138
|
+
#
|
136
139
|
def delete; update(:status => 'DELETED'); end
|
137
140
|
|
138
141
|
def rename(new_name); update(:name => new_name); end
|
139
142
|
|
140
143
|
# when Campaign#create fails, "delete" campaign
|
144
|
+
|
145
|
+
# Deletes campaign if it's not already deleted. For more information about
|
146
|
+
# "deleted" campaigns, see `delete` method
|
147
|
+
#
|
141
148
|
def rollback
|
142
149
|
if (@status == 'DELETED')
|
143
150
|
self.errors.add(:base, 'Campaign is already deleted.')
|
@@ -170,7 +177,9 @@ module Adapi
|
|
170
177
|
|
171
178
|
predicates = [ :id ].map do |param_name|
|
172
179
|
if params[param_name]
|
173
|
-
|
180
|
+
# convert to array
|
181
|
+
value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
|
182
|
+
{:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
|
174
183
|
end
|
175
184
|
end.compact
|
176
185
|
|
@@ -200,13 +209,13 @@ module Adapi
|
|
200
209
|
AdGroup.find( (first_only ? :first : :all), :campaign_id => self.id )
|
201
210
|
end
|
202
211
|
|
203
|
-
# Returns complete campaign data:
|
212
|
+
# Returns complete campaign data: criteria, ad groups, keywords and ads.
|
204
213
|
# Basically everything what you can set when creating a campaign.
|
205
214
|
#
|
206
215
|
def self.find_complete(campaign_id)
|
207
216
|
campaign = self.find(campaign_id)
|
208
217
|
|
209
|
-
campaign[:
|
218
|
+
campaign[:criteria] = CampaignCriterion.find(:campaign_id => campaign.to_param)
|
210
219
|
|
211
220
|
campaign[:ad_groups] = AdGroup.find(:all, :campaign_id => campaign.to_param)
|
212
221
|
|
@@ -226,7 +235,7 @@ module Adapi
|
|
226
235
|
:status => self[:status],
|
227
236
|
:budget => self[:budget],
|
228
237
|
:bidding_strategy => self[:bidding_strategy],
|
229
|
-
:
|
238
|
+
:criteria => self[:criteria],
|
230
239
|
:ad_groups => self[:ad_groups]
|
231
240
|
}
|
232
241
|
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Adapi
|
4
|
+
|
5
|
+
# http://code.google.com/apis/adwords/docs/reference/latest/CampaignTargetService.html
|
6
|
+
#
|
7
|
+
class CampaignCriterion < Api
|
8
|
+
|
9
|
+
attr_accessor :campaign_id, :criteria
|
10
|
+
|
11
|
+
validates_presence_of :campaign_id
|
12
|
+
|
13
|
+
CRITERION_TYPES = [ :age_range, :carrier, :content_label, :gender, :keyword,
|
14
|
+
:language, :location, :operating_system_version, :placement, :platform,
|
15
|
+
:polygon, :product, :proximity, :criterion_user_interest,
|
16
|
+
:criterion_user_list, :vertical ]
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
super.merge( 'campaign_id' => campaign_id, 'criteria' => criteria )
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(params = {})
|
23
|
+
params[:service_name] = :CampaignCriterionService
|
24
|
+
params[:negative] ||= false
|
25
|
+
|
26
|
+
@xsi_type = if (params[:negative] == true)
|
27
|
+
'NegativeCampaignCriterion'
|
28
|
+
else
|
29
|
+
'CampaignCriterion'
|
30
|
+
end
|
31
|
+
|
32
|
+
%w{ campaign_id criteria }.each do |param_name|
|
33
|
+
self.send "#{param_name}=", params[param_name.to_sym]
|
34
|
+
end
|
35
|
+
|
36
|
+
super(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def create
|
40
|
+
# step 1 - convert input hash to new array of criteria
|
41
|
+
# example: :language => [ :en, :cz ] -> [:language, :en]
|
42
|
+
criteria_array = []
|
43
|
+
|
44
|
+
@criteria.each_pair do |criterion_type, criterion_settings|
|
45
|
+
case criterion_type
|
46
|
+
when :language
|
47
|
+
criterion_settings.each do |value|
|
48
|
+
criteria_array << [criterion_type, value]
|
49
|
+
end
|
50
|
+
|
51
|
+
# location - besides standard, expected interface, this criterion is
|
52
|
+
# heavily customized to comply with legacy interfaces (pre-v201109).
|
53
|
+
#
|
54
|
+
# Standard v201109 location interface:
|
55
|
+
# :location => location_id
|
56
|
+
# :location => { :id => location_id }
|
57
|
+
# :location => { :id => [ location_id ] }
|
58
|
+
#
|
59
|
+
# Accepted subtypes:
|
60
|
+
# id
|
61
|
+
# proximity (just actually redirects to proximity criterion)
|
62
|
+
# city
|
63
|
+
# province
|
64
|
+
# country
|
65
|
+
#
|
66
|
+
when :location, :geo # PS: geo is legacy synonym for location
|
67
|
+
# handles ":location => location_id" shortcut
|
68
|
+
unless criterion_settings.is_a?(Hash)
|
69
|
+
criterion_settings = { :id => criterion_settings.to_i }
|
70
|
+
end
|
71
|
+
|
72
|
+
criterion_settings.each_pair do |subtype, subtype_settings|
|
73
|
+
# any location subtypes can be in array
|
74
|
+
subtype_settings = [ subtype_settings ] unless subtype_settings.is_a?(Array)
|
75
|
+
|
76
|
+
case subtype
|
77
|
+
when :id
|
78
|
+
subtype_settings.each do |value|
|
79
|
+
criteria_array << [:location, value]
|
80
|
+
end
|
81
|
+
|
82
|
+
# find id for location(s) by LocationCriterion service
|
83
|
+
when :name
|
84
|
+
subtype_settings = [subtype_settings] unless subtype_settings.is_a?(Array)
|
85
|
+
|
86
|
+
subtype_settings.each do |location_criteria|
|
87
|
+
location = Adapi::Location.find(location_criteria)
|
88
|
+
|
89
|
+
raise "Location not found" if location.nil?
|
90
|
+
|
91
|
+
criteria_array << [ :location, location[:id] ]
|
92
|
+
end
|
93
|
+
|
94
|
+
when :proximity
|
95
|
+
subtype_settings.each do |value|
|
96
|
+
criteria_array << [subtype, value]
|
97
|
+
end
|
98
|
+
|
99
|
+
else
|
100
|
+
raise "Unknown location subtype: %s" % subtype
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# not-supported criterions (they work, but have to be entered in
|
105
|
+
# google format, no shortcuts are set up for them)
|
106
|
+
else
|
107
|
+
unless CRITERION_TYPES.include?(criterion_type)
|
108
|
+
raise "Unknown criterion type; #{criterion_type}"
|
109
|
+
end
|
110
|
+
|
111
|
+
if criterion_settings.is_a?(Array)
|
112
|
+
criterion_settings.each do |value|
|
113
|
+
criteria_array << [criterion_type, value]
|
114
|
+
end
|
115
|
+
else
|
116
|
+
criteria_array << [criterion_type, criterion_settings]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# step 2 - convert individual criteria to low-level google params
|
122
|
+
operations = criteria_array.map do |criterion_type, criterion_settings|
|
123
|
+
{
|
124
|
+
:operator => 'ADD',
|
125
|
+
:operand => {
|
126
|
+
:campaign_id => @campaign_id,
|
127
|
+
:criterion => CampaignCriterion::create_criterion(criterion_type, criterion_settings)
|
128
|
+
}
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
response = self.mutate(operations)
|
133
|
+
|
134
|
+
(response and response[:value]) ? true : false
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.find(params = {})
|
138
|
+
params.symbolize_keys!
|
139
|
+
|
140
|
+
# by default, skip criteria types that have no criterion data
|
141
|
+
params[:skip_empty_criterion_types] ||= true
|
142
|
+
|
143
|
+
if params[:conditions]
|
144
|
+
params[:campaign_id] = params[:campaign_id] || params[:conditions][:campaign_id]
|
145
|
+
end
|
146
|
+
|
147
|
+
raise ArgumentError, "Campaign ID is required" unless params[:campaign_id]
|
148
|
+
|
149
|
+
predicates = [ :campaign_id ].map do |param_name|
|
150
|
+
if params[param_name]
|
151
|
+
# convert to array
|
152
|
+
value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
|
153
|
+
{:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
|
154
|
+
end
|
155
|
+
end.compact
|
156
|
+
|
157
|
+
# TODO: get more fields
|
158
|
+
selector = {
|
159
|
+
:fields => ['Id'],
|
160
|
+
:ordering => [{:field => 'Id', :sort_order => 'ASCENDING'}],
|
161
|
+
:predicates => predicates
|
162
|
+
}
|
163
|
+
|
164
|
+
response = CampaignCriterion.new.service.get(selector)
|
165
|
+
|
166
|
+
response = (response and response[:entries]) ? response[:entries] : []
|
167
|
+
|
168
|
+
# return everything or only
|
169
|
+
if params[:skip_empty_criterion_types]
|
170
|
+
response.select! { |criterion_type| criterion_type.has_key?(:criteria) }
|
171
|
+
end
|
172
|
+
|
173
|
+
# TODO optionally return just certain type(s)
|
174
|
+
# easy, just add condition (single type or array), filter and set
|
175
|
+
# :skip_empty_target_types option to false
|
176
|
+
|
177
|
+
response
|
178
|
+
end
|
179
|
+
|
180
|
+
# Transforms our custom high-level criteria parameters to AdWords API parameters
|
181
|
+
#
|
182
|
+
# Every criterion can be entered as high-level alias or as id
|
183
|
+
#
|
184
|
+
# Language:
|
185
|
+
# :language => [ :en, :cs ]
|
186
|
+
# :language => [ 1000, 1021 ] # integers!
|
187
|
+
#
|
188
|
+
#
|
189
|
+
def self.create_criterion(criterion_type, criterion_data)
|
190
|
+
case criterion_type
|
191
|
+
#
|
192
|
+
# example: [:language, 'en'] -> {:xsi_type => 'Language', :id => 1000}
|
193
|
+
when :language
|
194
|
+
{
|
195
|
+
:xsi_type => 'Language',
|
196
|
+
:id => ConstantData::Language.find(criterion_data).id
|
197
|
+
}
|
198
|
+
|
199
|
+
when :location
|
200
|
+
{
|
201
|
+
:xsi_type => 'Location',
|
202
|
+
:id => criterion_data.to_i
|
203
|
+
}
|
204
|
+
|
205
|
+
when :proximity
|
206
|
+
radius_in_units, radius_units = parse_radius(criterion_data[:radius])
|
207
|
+
long, lat = parse_geodata(criterion_data[:geo_point])
|
208
|
+
|
209
|
+
{
|
210
|
+
:xsi_type => 'Proximity',
|
211
|
+
:radius_in_units => radius_in_units,
|
212
|
+
:radius_distance_units => radius_units,
|
213
|
+
:geo_point => {
|
214
|
+
:longitude_in_micro_degrees => long,
|
215
|
+
:latitude_in_micro_degrees => lat
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
=begin
|
220
|
+
when :city
|
221
|
+
geo_values.merge(
|
222
|
+
:xsi_type => "#{geo_type.to_s.capitalize}Target",
|
223
|
+
:excluded => false
|
224
|
+
)
|
225
|
+
|
226
|
+
else # :country, :province
|
227
|
+
{
|
228
|
+
:xsi_type => "#{geo_type.to_s.capitalize}Target",
|
229
|
+
:excluded => false,
|
230
|
+
"#{geo_type}_code".to_sym => to_uppercase(geo_values)
|
231
|
+
}
|
232
|
+
end
|
233
|
+
=end
|
234
|
+
|
235
|
+
# unsupported criterion types
|
236
|
+
else
|
237
|
+
{ :xsi_type => criterion_type.to_s.camelize }.merge(criterion_data)
|
238
|
+
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.parse_radius(radius)
|
243
|
+
radius_in_units, radius_units = radius.split(' ', 2)
|
244
|
+
[
|
245
|
+
radius_in_units.to_i,
|
246
|
+
(radius_units == 'm') ? 'MILES' : 'KILOMETERS'
|
247
|
+
]
|
248
|
+
end
|
249
|
+
|
250
|
+
# parse longitude and lattitude from string in this format:
|
251
|
+
# "longitude,lattitude" to [int,int] in Google microdegrees
|
252
|
+
# for example: "38.89859,-77.035971" -> [38898590, -77035971]
|
253
|
+
#
|
254
|
+
def self.parse_geodata(long_lat)
|
255
|
+
long_lat.split(',', 2).map { |x| to_microdegrees(x) }
|
256
|
+
end
|
257
|
+
|
258
|
+
# convert latitude or longitude data to microdegrees,
|
259
|
+
# a format with AdWords API accepts
|
260
|
+
#
|
261
|
+
# TODO alias :to_microdegrees :to_micro_units
|
262
|
+
#
|
263
|
+
def self.to_microdegrees(x)
|
264
|
+
Api.to_micro_units(x)
|
265
|
+
end
|
266
|
+
|
267
|
+
# convert either single value or array of value to uppercase
|
268
|
+
#
|
269
|
+
def self.to_uppercase(values)
|
270
|
+
if values.is_a?(Array)
|
271
|
+
values.map { |value| value.to_s.upcase }
|
272
|
+
else
|
273
|
+
values.to_s.upcase
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
# This class is obsolete in v201109, CampaignCriterion is used instead. Only
|
4
|
+
# AdScheduleTarget is still being used, but it's not implemented yet.
|
5
|
+
|
3
6
|
module Adapi
|
4
7
|
|
5
8
|
# http://code.google.com/apis/adwords/docs/reference/latest/CampaignTargetService.html
|
@@ -10,8 +13,6 @@ module Adapi
|
|
10
13
|
|
11
14
|
validates_presence_of :campaign_id
|
12
15
|
|
13
|
-
# TODO validate if targets are in correct format
|
14
|
-
|
15
16
|
def attributes
|
16
17
|
super.merge( 'campaign_id' => campaign_id, 'targets' => targets )
|
17
18
|
end
|
@@ -52,9 +53,6 @@ module Adapi
|
|
52
53
|
|
53
54
|
def self.find(params = {})
|
54
55
|
params.symbolize_keys!
|
55
|
-
|
56
|
-
# by default, return skip target types that have no target data
|
57
|
-
params[:skip_empty_target_types] ||= true
|
58
56
|
|
59
57
|
if params[:conditions]
|
60
58
|
params[:campaign_id] = params[:campaign_id] || params[:conditions][:campaign_id]
|
@@ -68,129 +66,13 @@ module Adapi
|
|
68
66
|
|
69
67
|
response = (response and response[:entries]) ? response[:entries] : []
|
70
68
|
|
71
|
-
# return everything or only
|
72
|
-
if params[:skip_empty_target_types]
|
73
|
-
response.select! { |target_type| target_type.has_key?(:targets) }
|
74
|
-
end
|
75
|
-
|
76
|
-
# TODO optionally return just certain target type(s)
|
77
|
-
# easy, just add condition (single type or array), filter and set
|
78
|
-
# :skip_empty_target_types option to false
|
79
|
-
|
80
|
-
# optionally convert to original input shortcuts specified by Adapi DSL
|
81
|
-
# TODO on second thought, no need to do that now. it's simpler to make
|
82
|
-
# CampaignTarget.new accept original AdWords data
|
83
|
-
# if params[:format] == :adapi_dsl
|
84
|
-
# response = Hash[ response.map { |t| CampaignTarget.parse_dsl(t) } ]
|
85
|
-
# end
|
86
|
-
|
87
69
|
response
|
88
70
|
end
|
89
71
|
|
90
|
-
|
91
|
-
def self.parse_dsl(target)
|
92
|
-
case target[:target_list_type]
|
93
|
-
when "LanguageTargetList"
|
94
|
-
[ :language, target[:targets].map { |t| t[:language_code] } ]
|
95
|
-
when "GeoTargetList"
|
96
|
-
targets = Hash[ target[:targets].map do |t|
|
97
|
-
target_type = t[:target_type].gsub(/Target$/, '')
|
98
|
-
|
99
|
-
case target_type
|
100
|
-
when :proximity
|
101
|
-
[ target_type, { :geo_point => '', :radius => '' } ]
|
102
|
-
when :city
|
103
|
-
else
|
104
|
-
[ target_type, t ]
|
105
|
-
end
|
106
|
-
end ]
|
107
|
-
|
108
|
-
[ :geo, targets ]
|
109
|
-
else
|
110
|
-
warn "Unsupported target type! %s" % target.inspect
|
111
|
-
[ target[:target_list_type], target[:targets] ]
|
112
|
-
end
|
113
|
-
end
|
114
|
-
=end
|
115
|
-
|
116
|
-
# transform our own high-level target parameters to google low-level
|
117
|
-
#
|
118
|
-
# TODO allow to enter AdWords parameters in original format
|
72
|
+
# Obsolete. Transforms our own high-level target parameters to google low-level
|
119
73
|
#
|
120
74
|
def self.create_targets(target_type, target_data)
|
121
|
-
|
122
|
-
when :language
|
123
|
-
target_data.map { |language| { :language_code => language.to_s.downcase } }
|
124
|
-
# example: ['cz','sk'] => [{:language_code => 'cz'}, {:language_code => 'sk'}]
|
125
|
-
when :geo
|
126
|
-
target_data.map do |geo_type, geo_values|
|
127
|
-
case geo_type
|
128
|
-
when :proximity
|
129
|
-
radius_in_units, radius_units = parse_radius(geo_values[:radius])
|
130
|
-
long, lat = parse_geodata(geo_values[:geo_point])
|
131
|
-
|
132
|
-
{
|
133
|
-
:xsi_type => "#{geo_type.to_s.capitalize}Target",
|
134
|
-
:excluded => false,
|
135
|
-
:radius_in_units => radius_in_units,
|
136
|
-
:radius_distance_units => radius_units,
|
137
|
-
:geo_point => {
|
138
|
-
:longitude_in_micro_degrees => long,
|
139
|
-
:latitude_in_micro_degrees => lat
|
140
|
-
}
|
141
|
-
}
|
142
|
-
|
143
|
-
when :city
|
144
|
-
geo_values.merge(
|
145
|
-
:xsi_type => "#{geo_type.to_s.capitalize}Target",
|
146
|
-
:excluded => false
|
147
|
-
)
|
148
|
-
|
149
|
-
else # :country, :province
|
150
|
-
{
|
151
|
-
:xsi_type => "#{geo_type.to_s.capitalize}Target",
|
152
|
-
:excluded => false,
|
153
|
-
"#{geo_type}_code".to_sym => to_uppercase(geo_values)
|
154
|
-
}
|
155
|
-
end
|
156
|
-
end
|
157
|
-
else nil
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def self.parse_radius(radius)
|
162
|
-
radius_in_units, radius_units = radius.split(' ', 2)
|
163
|
-
[
|
164
|
-
radius_in_units.to_i,
|
165
|
-
(radius_units == 'm') ? 'MILES' : 'KILOMETERS'
|
166
|
-
]
|
167
|
-
end
|
168
|
-
|
169
|
-
# parse longitude and lattitude from string in this format:
|
170
|
-
# "longitude,lattitude" to [int,int] in Google microdegrees
|
171
|
-
# for example: "38.89859,-77.035971" -> [38898590, -77035971]
|
172
|
-
#
|
173
|
-
def self.parse_geodata(long_lat)
|
174
|
-
long_lat.split(',', 2).map { |x| to_microdegrees(x) }
|
175
|
-
end
|
176
|
-
|
177
|
-
# convert latitude or longitude data to microdegrees,
|
178
|
-
# a format with AdWords API accepts
|
179
|
-
#
|
180
|
-
# TODO alias :to_microdegrees :to_micro_units
|
181
|
-
#
|
182
|
-
def self.to_microdegrees(x)
|
183
|
-
Api.to_micro_units(x)
|
184
|
-
end
|
185
|
-
|
186
|
-
# convert either single value or array of value to uppercase
|
187
|
-
#
|
188
|
-
def self.to_uppercase(values)
|
189
|
-
if values.is_a?(Array)
|
190
|
-
values.map { |value| value.to_s.upcase }
|
191
|
-
else
|
192
|
-
values.to_s.upcase
|
193
|
-
end
|
75
|
+
nil
|
194
76
|
end
|
195
77
|
|
196
78
|
end
|