adapi 0.0.4 → 0.0.5
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/.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
|