Class: Adapi::Campaign
Constant Summary collapse
- NETWORK_SETTING_KEYS =
[ :target_google_search, :target_search_network, :target_content_network, :target_partner_search_network ]
- ATTRIBUTES =
[ :name, :status, :serving_status, :start_date, :end_date, :budget, :bidding_strategy, :network_setting, :campaign_stats, :criteria, :ad_groups, :ad_serving_optimization_status, :settings ]
Constants inherited from Api
Api::API_EXCEPTIONS, Api::LOGGER
Instance Attribute Summary
Attributes inherited from Api
#adwords, #id, #params, #service, #status, #version, #xsi_type
Class Method Summary collapse
-
.find(amount = :all, params = {}) ⇒ Object
Searches for campaign/s according to given parameters.
-
.find_complete(campaign_id) ⇒ Object
Returns complete campaign data: criteria, ad groups, keywords and ads.
Instance Method Summary collapse
- #activate ⇒ Object
- #attributes ⇒ Object (also: #to_hash)
-
#bidding_strategy=(params = {}) ⇒ Object
setter for converting bidding_strategy to google format can be either string (just xsi_type) or hash (xsi_type with params) TODO validations for xsi_type.
-
#budget=(params = {}) ⇒ Object
setter for converting budget to GoogleApi budget can be integer (amount) or hash.
-
#create ⇒ Object
create campaign with ad_groups and ads.
-
#delete ⇒ Object
Deletes campaign - which means simply setting its status to deleted.
- #end_date=(a_date) ⇒ Object
-
#find ⇒ Object
Shortcut method, often used for refreshing campaign after create/update REFACTOR into :refresh method.
- #find_ad_groups(first_only = true) ⇒ Object
-
#initialize(params = {}) ⇒ Campaign
constructor
A new instance of Campaign.
- #parse_date(a_date) ⇒ Object
- #pause ⇒ Object
- #rename(new_name) ⇒ Object
-
#rollback! ⇒ Object
Deletes campaign if not already deleted.
-
#settings=(setting_options = []) ⇒ Object
setter for campaign settings (array of hashes).
- #start_date=(a_date) ⇒ Object
-
#update(params = {}) ⇒ Object
Sets campaign data en masse, including criteria and ad_groups with keywords and ads.
-
#update_ad_groups!(ad_groups = []) ⇒ Object
helper method that updates ad_groups.
Methods inherited from Api
#[], #[]=, #check_for_errors, create, #mutate, #new?, #persisted?, #store_errors, to_micro_units, #to_param, update
Constructor Details
#initialize(params = {}) ⇒ Campaign
Returns a new instance of Campaign.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/adapi/campaign.rb', line 28 def initialize(params = {}) params.symbolize_keys! params[:service_name] = :CampaignService @xsi_type = 'Campaign' ATTRIBUTES.each do |param_name| self.send("#{param_name}=", params[param_name]) end # HOTFIX backward compatibility with old field for criteria @criteria ||= params[:targets] || {} @ad_groups ||= [] super(params) end |
Class Method Details
.find(amount = :all, params = {}) ⇒ Object
Searches for campaign/s according to given parameters
Input parameters are dynamic. Special case: single number or string on input is considered to be id and we want to search for a single campaign by id
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/adapi/campaign.rb', line 340 def self.find(amount = :all, params = {}) # find single campaign by id if params.empty? and not amount.is_a?(Symbol) params[:id] = amount.to_i amount = :first end params.symbolize_keys! first_only = (amount.to_sym == :first) predicates = [ :id ].map do |param_name| if params[param_name] # convert to array value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]] { field: param_name.to_s.camelcase, operator: 'IN', values: value } end end.compact # TODO make configurable (but for the moment, return everything) select_fields = %w{ Id Name Status ServingStatus StartDate EndDate AdServingOptimizationStatus } # retrieve CampaignStats fields select_fields += %w{ Clicks Impressions Cost Ctr } # retrieve Budget fields select_fields += %w{ Amount Period DeliveryMethod } # retrieve BiddingStrategy fields select_fields += %w{ BiddingStrategy BidCeiling EnhancedCpcEnabled } # retrieve NetworkSetting fields select_fields += NETWORK_SETTING_KEYS.map { |k| k.to_s.camelize } selector = { :fields => select_fields, :ordering => [ { field: 'Name', sort_order: 'ASCENDING' } ], :predicates => predicates } response = Campaign.new.service.get(selector) response = (response and response[:entries]) ? response[:entries] : [] response.map! do |campaign_data| campaign = Campaign.new(campaign_data) # TODO allow mass assignment of :id campaign.id = campaign_data[:id] campaign end first_only ? response.first : response end |
.find_complete(campaign_id) ⇒ Object
Returns complete campaign data: criteria, ad groups, keywords and ads. Basically everything what you can set when creating a campaign.
397 398 399 400 401 402 403 404 405 |
# File 'lib/adapi/campaign.rb', line 397 def self.find_complete(campaign_id) campaign = self.find(campaign_id) campaign[:criteria] = CampaignCriterion.find(:campaign_id => campaign.to_param) campaign[:ad_groups] = AdGroup.find(:all, :campaign_id => campaign.to_param).map { |ag| ag.to_hash } campaign end |
Instance Method Details
#activate ⇒ Object
300 |
# File 'lib/adapi/campaign.rb', line 300 def activate; update(:status => 'ACTIVE'); end |
#attributes ⇒ Object Also known as: to_hash
19 20 21 |
# File 'lib/adapi/campaign.rb', line 19 def attributes super.merge Hash[ ATTRIBUTES.map { |k| [k, self.send(k)] } ] end |
#bidding_strategy=(params = {}) ⇒ Object
setter for converting bidding_strategy to google format can be either string (just xsi_type) or hash (xsi_type with params) TODO validations for xsi_type
TODO watch out when doing update. according to documentation: “to modify an existing campaign’s bidding strategy, use CampaignOperation.biddingTransition”
71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/adapi/campaign.rb', line 71 def bidding_strategy=(params = {}) unless params.is_a?(Hash) params = { xsi_type: params } else if params[:bid_ceiling] and not params[:bid_ceiling].is_a?(Hash) params[:bid_ceiling] = { micro_amount: Api.to_micro_units(params[:bid_ceiling]) } end end @bidding_strategy = params end |
#budget=(params = {}) ⇒ Object
setter for converting budget to GoogleApi budget can be integer (amount) or hash
TODO return error for missing :amount
90 91 92 93 94 95 96 97 98 99 |
# File 'lib/adapi/campaign.rb', line 90 def budget=(params = {}) # if it's single value, it's a budget amount params = { amount: params } unless params.is_a?(Hash) if params[:amount] and not params[:amount].is_a?(Hash) params[:amount] = { micro_amount: Api.to_micro_units(params[:amount]) } end @budget = params.clone end |
#create ⇒ Object
create campaign with ad_groups and ads
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/adapi/campaign.rb', line 119 def create return false unless self.valid? # set defaults for budget for campaign.create only self.budget = budget.reverse_merge( period: 'DAILY', delivery_method: 'STANDARD' ) # create basic campaign attributes operand = Hash[ [ :name, :status, :start_date, :end_date, :budget, :bidding_strategy, :network_setting, :settings ].map do |k| [ k.to_sym, self.send(k) ] if self.send(k) end.compact ] # set default values for settings (for create only - should we set it also for update?) # PS: KeywordMatchSetting is required since 201206 operand[:settings] ||= [] unless operand[:settings].map { |s| s[:xsi_type] }.include?('KeywordMatchSetting') operand[:settings] << { :xsi_type => 'KeywordMatchSetting', :opt_in => false } end response = self.mutate( operator: 'ADD', operand: operand ) check_for_errors(self) self.id = response[:value].first[:id] rescue nil if criteria && criteria.size > 0 new_criteria = Adapi::CampaignCriterion.create( campaign_id: @id, criteria: criteria ) check_for_errors(new_criteria) end ad_groups.each do |ad_group_data| ad_group = Adapi::AdGroup.create( ad_group_data.merge( campaign_id: @id ) ) check_for_errors(ad_group, :prefix => "AdGroup \"#{ad_group[:id] || ad_group[:name]}\"") end self.errors.empty? rescue CampaignError => e false end |
#delete ⇒ Object
Deletes campaign - which means simply setting its status to deleted
306 |
# File 'lib/adapi/campaign.rb', line 306 def delete; update(:status => 'DELETED'); end |
#end_date=(a_date) ⇒ Object
51 52 53 |
# File 'lib/adapi/campaign.rb', line 51 def end_date=(a_date) @end_date = parse_date(a_date) if a_date.present? end |
#find ⇒ Object
Shortcut method, often used for refreshing campaign after create/update REFACTOR into :refresh method
330 331 332 |
# File 'lib/adapi/campaign.rb', line 330 def find Campaign.find(:first, :id => @id) end |
#find_ad_groups(first_only = true) ⇒ Object
390 391 392 |
# File 'lib/adapi/campaign.rb', line 390 def find_ad_groups(first_only = true) AdGroup.find( (first_only ? :first : :all), :campaign_id => self.id ) end |
#parse_date(a_date) ⇒ Object
55 56 57 58 59 60 61 |
# File 'lib/adapi/campaign.rb', line 55 def parse_date(a_date) case a_date when DateTime, Date, Time then a_date # FIXME distiguish between timestamp and YYYYMMDD string else DateTime.parse(a_date).strftime('%Y%m%d') end end |
#pause ⇒ Object
302 |
# File 'lib/adapi/campaign.rb', line 302 def pause; update(:status => 'PAUSED'); end |
#rename(new_name) ⇒ Object
308 |
# File 'lib/adapi/campaign.rb', line 308 def rename(new_name); update(:name => new_name); end |
#rollback! ⇒ Object
Deletes campaign if not already deleted. This is usually done after unsuccessfull complex operation (create/update complete campaign)
313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/adapi/campaign.rb', line 313 def rollback! if (@status == 'DELETED') self.errors.add(:base, 'Campaign is already deleted.') return false end self.errors.clear self.update( :name => "#{@name}_DELETED_#{(Time.now.to_f * 1000).to_i}", :status => 'DELETED' ) end |
#settings=(setting_options = []) ⇒ Object
setter for campaign settings (array of hashes)
103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/adapi/campaign.rb', line 103 def settings=( = []) # for arrays, set in raw form @settings = if .is_a?(Array) # set optional shortcuts for settings # :keyword_match_setting => { :opt_in => false } # => # { :xsi_type => 'KeywordMatchSetting', :opt_in => false } elsif .is_a?(Hash) .map do |key, values| { :xsi_type => key.to_s.camelcase }.merge(values).symbolize_keys end end end |
#start_date=(a_date) ⇒ Object
47 48 49 |
# File 'lib/adapi/campaign.rb', line 47 def start_date=(a_date) @start_date = parse_date(a_date) if a_date.present? end |
#update(params = {}) ⇒ Object
Sets campaign data en masse, including criteria and ad_groups with keywords and ads
Warning: campaign data are not refreshed after update! We’d have to do it by get method and that would slow us down. If you want to see latest data, you have to fetch them again manually: Campaign#find or Campaign#find_complete
TODO implement primarily as class method, instance will be just a redirect with campaign_id
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/adapi/campaign.rb', line 180 def update(params = {}) # REFACTOR for the moment, we use separate campaign object just to prepare and execute # campaign update request. This is kinda ugly and should be eventually refactored (if # only because of weird transfer of potential errors later when dealing with response). # # campaign basic data workflow: # parse given params by loading them into Campaign.new and reading them back, parsed # REFACTOR should be parsed by separate Campaign class method # campaign = Adapi::Campaign.new(params) # HOTFIX remove :service_name param inserted byu initialize method params.delete(:service_name) # ...and load parsed params back into the hash params.keys.each { |k| params[k] = campaign.send(k) } params[:id] = @id @criteria = params.delete(:criteria) params.delete(:targets) @ad_groups = params.delete(:ad_groups) || [] @bidding_strategy = params.delete(:bidding_strategy) operation = { operator: 'SET', operand: params } # BiddingStrategy update has slightly different DSL from other params # https://developers.google.com/adwords/api/docs/reference/v201109_1/CampaignService.BiddingTransition # # See this post about BiddingTransition limitations: # https://groups.google.com/forum/?fromgroups#!topic/adwords-api/tmRk1m7PbhU # "ManualCPC can transition to anything and everything else can only transition to ManualCPC" if @bidding_strategy operation[:bidding_transition] = { target_bidding_strategy: @bidding_strategy } end campaign.mutate(operation) check_for_errors(campaign) # update campaign criteria if @criteria && @criteria.size > 0 new_criteria = Adapi::CampaignCriterion.new( :campaign_id => @id, :criteria => @criteria ) new_criteria.update! check_for_errors(new_criteria) end self.update_ad_groups!(@ad_groups) self.errors.empty? rescue CampaignError => e false end |
#update_ad_groups!(ad_groups = []) ⇒ Object
helper method that updates ad_groups. called from Campaign#update method
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/adapi/campaign.rb', line 243 def update_ad_groups!(ad_groups = []) return true if ad_groups.nil? or ad_groups.empty? # FIXME deep symbolize_keys ad_groups.map! { |ag| ag.symbolize_keys } # check if every ad_group has either :id or :name parameter ad_groups.each do |ag| if ag[:id].blank? && ag[:name].blank? self.errors.add("AdGroup", "required parameter (:id or :name) is missing") return false end end # get current ad_groups original_ad_groups = AdGroup.find(:all, :campaign_id => @id) ad_groups.each do |ad_group_data| ad_group_data[:campaign_id] = @id # find ad_group by id or name k, v = ad_group_data.has_key?(:id) ? [:id, ad_group_data[:id]] : [:name, ad_group_data[:name]] ad_group = original_ad_groups.find { |ag| ag[k] == v } # update existing ad_group if ad_group.present? ad_group.update(ad_group_data) original_ad_groups.delete_if { |ag| ag[k] == v } # create new ad_group # FIXME report error if searching by :id, because such ad_group should exists else ad_group_data.delete(:id) ad_group = AdGroup.create(ad_group_data) end check_for_errors(ad_group, :prefix => "AdGroup \"#{ad_group[:id] || ad_group[:name]}\"") end # delete ad_groups which haven't been updated original_ad_groups.each do |ag| unless ag.delete # FIXME storing error twice for the moment because neither # of these errors says all the needed information self.errors.add("AdGroup #{ag[:id]}", "could not be deleted") self.store_errors(ad_group, "AdGroup #{ag[:id]}") return false end end self.errors.empty? rescue CampaignError => e false end |