lstejskal/adapi

View on GitHub
lib/adapi/ad/text_ad.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# encoding: utf-8

module Adapi
  # http://code.google.com/apis/adwords/docs/reference/latest/AdGroupAdService.TextAd.html
  #
  # Ad::TextAd == AdGroupAd::TextAd
  #
  # This model supports both individual and batch create/update of ads.
  # If :ads parameter is not nil, it is considered as array of ads to
  # be updated in batch.
  #
  class Ad::TextAd < Ad

    ATTRIBUTES = [ :headline, :description1, :description2, :ads ]

    attr_accessor *ATTRIBUTES

    def attributes
      super.merge Hash[ ATTRIBUTES.map { |k| [k, self.send(k)] } ]
    end

    alias to_hash attributes

    def initialize(params = {})
      params[:service_name] = :AdGroupAdService

      @xsi_type = 'TextAd'

      ATTRIBUTES.each do |param_name|
        self.send("#{param_name}=", params[param_name])
      end

      super(params)
    end

    def save
      self.new? ? self.create : self.update
    end
 
    def create
      @ads = [ self.attributes ] unless @ads 

      operations = []

      @ads.each do |ad_params|

        ad = TextAd.new(ad_params)

        operand = ad.attributes.delete_if do |k|
          [ :campaign_id, :ad_group_id, :id, :status, :ads ].include?(k.to_sym)
        end.symbolize_keys

        operations << {
          :operator => 'ADD',
          :operand => {
            :ad_group_id => ad.ad_group_id,
            :status => ad.status,
            :ad => operand
          }
        }
      end

      self.mutate(operations)

      # check for PolicyViolationErrors, set exemptions and try again
      # do it only once. from my experience with AdWords API, multiple retries are bad practice
      if self.errors[:PolicyViolationError].any?

        self.errors[:PolicyViolationError].each do |e|
          i = e.delete(:operation_index) 
          operations[i][:exemption_requests] = [ { :key => e } ]
        end

        self.errors.clear

        self.mutate(operations)
      end

      return false if self.errors.any?

      # FIXME return ids of newly created ads
      # self.id = response[:value].first[:ad][:id] rescue nil
  
      true
    end

    # except for status, we cannot edit ad fields
    # gotta delete an ad and create a new one instead
    # this means that this method returns new ad id! 
    # 
    # REFACTOR this method shpould be removed (but that might break something,
    # os let's keep it here for the moment)
    #
    def update(params = {})
      # set attributes for the "updated" ad
      create_attributes = self.attributes.merge(params).symbolize_keys
      create_attributes.delete(:id)

      # delete current ad
      return false unless self.destroy

      # create new add
      TextAd.create(create_attributes)
    end

    def find # == refresh
      TextAd.find(:first, :ad_group_id => self.ad_group_id, :id => self.id)
    end

    def self.find(amount = :all, params = {})
      params.symbolize_keys!
      first_only = (amount.to_sym == :first)

      # for ActiveRecord compatibility, we don't use anything besides conditions
      # params for now
      params = params[:conditions] if params[:conditions]

      # we need ad_group_id
      raise ArgumentError, "AdGroup ID is required" unless params[:ad_group_id]
 
      # supported condition parameters: ad_group_id and id
      predicates = [ :ad_group_id, :id ].map do |param_name|
        if params[param_name]
          { :field => param_name.to_s.camelcase, :operator => 'IN', :values => Array( params[param_name] ) }
        end
      end.compact

      selector = {
        :fields => ['Id', 'AdGroupId', 'Headline' ],
        :ordering => [{:field => 'Id', :sort_order => 'ASCENDING'}],
        :predicates => predicates
      }

      response = TextAd.new.service.get(selector)

      response = (response and response[:entries]) ? response[:entries] : []

      response.map! do |data|
        TextAd.new(data[:ad].merge(:ad_group_id => data[:ad_group_id], :status => data[:status]))
      end

      # TODO convert to TextAd instances
      first_only ? response.first : response
    end

  end
end