Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions examples/incentives/apply_incentive.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env ruby

#
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example applies an incentive to a user's account.
#
# This example is a no-op if the user already has an accepted incentive. If the
# user attempts to apply a new incentive, the response will simply return the
# existing incentive that has already been applied to the account. Use the
# fetch_incentives.rb example to get the available incentives.

require "optparse"
require "google/ads/google_ads"

# [START apply_incentive]
def apply_incentive(customer_id, incentive_id, country_code)
# GoogleAdsClient will read a config file from
# ENV['HOME']/google_ads_config.rb when called without parameters
client = Google::Ads::GoogleAds::GoogleAdsClient.new
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resource Optimization: Instantiating the client inside the method is expensive for repetitive calls. Consider passing a shared client instance to allow for connection pooling.


request_params = {
customer_id: customer_id,
selected_incentive_id: incentive_id
}

if country_code
request_params[:country_code] = country_code
end

response = client.service.incentive.apply_incentive(request_params)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idempotency Check: Since the API returns the existing incentive if one is already applied, add a check here to tell the user whether this was a new application or a retrieval of an existing one to prevent confusion.


puts "Applied incentive."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditional Feedback: The message 'Applied incentive' might be misleading if the user already had one (as per the header comments). Change this to 'Incentive details retrieved/applied' for better accuracy.

puts "Coupon Code: #{response.coupon_code}"
puts "Creation Time: #{response.creation_time}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Date Formatting: The raw creation time might be a timestamp object. Formatting this (e.g., .strftime('%Y-%m-%d %H:%M:%S')) makes the console output much more readable.

end
# [END apply_incentive]

if __FILE__ == $0
options = {}
# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
options[:customer_id] = "INSERT_CUSTOMER_ID_HERE"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safety Guard: Implement a check to ensure INSERT_ placeholders are replaced. Running this with dummy strings results in a wasted API call and a 400-range error.

options[:incentive_id] = "INSERT_INCENTIVE_ID_HERE"

OptionParser.new do |opts|
opts.banner = sprintf("Usage: %s [options]", File.basename(__FILE__))

opts.separator ""
opts.separator "Options:"

opts.on("-c", "--customer-id CUSTOMER-ID", String, "The Google Ads customer ID") do |v|
options[:customer_id] = v
end

opts.on("-i", "--incentive-id INCENTIVE-ID", Integer, "The incentive ID to select") do |v|
options[:incentive_id] = v
end

opts.on("-k", "--country-code COUNTRY-CODE", String, "The country code of the user (e.g. 'US')") do |v|
options[:country_code] = v
end

opts.separator ""
opts.separator "Help:"

opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end.parse!

begin
apply_incentive(
options.fetch(:customer_id).tr("-", ""),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input Sanitation: Moving the .tr("-", "") logic into a helper method or the apply_incentive method itself ensures consistency if you ever call this function from a different part of the app.

options.fetch(:incentive_id),
options[:country_code]
)
rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
e.failure.errors.each do |error|
$stderr.printf("Error with message: %s\n", error.message)
error.location&.field_path_elements&.each do |field_path_element|
$stderr.printf("\tOn field: %s\n", field_path_element.field_name)
end
error.error_code.to_h.each do |k, v|
next if v == :UNSPECIFIED
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detailed Logging: "In a production environment, you should log these errors to a persistent file (e.g., ads_api.log) rather than just printing to $stderr, which is lost after the terminal closes."

$stderr.printf("\tType: %s\n\tCode: %s\n", k, v)
end
end
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a comment to explain why the error is re-raised or apply the comment from fetch_incentives.rb.

end
end
112 changes: 112 additions & 0 deletions examples/incentives/fetch_incentives.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env ruby

#
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example returns incentives for a given user.
#
# To apply an incentive, use apply_incentive.rb.

require "optparse"
require "google/ads/google_ads"

# [START fetch_incentives]
def fetch_incentives(email_address, language_code, country_code)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependency Injection: Instead of instantiating the client inside the method, pass it as an argument. This allows you to share one authenticated session across multiple API calls, reducing overhead.

# GoogleAdsClient will read a config file from
# ENV['HOME']/google_ads_config.rb when called without parameters
client = Google::Ads::GoogleAds::GoogleAdsClient.new

response = client.service.incentive.fetch_incentive(
email: email_address,
language_code: language_code,
country_code: country_code
)

if response.incentive_offer&.cyo_incentives
puts "Fetched incentive."
# If the offer type is CHOOSE_YOUR_OWN_INCENTIVE, there will be three
# incentives in the response. At the time this example was written, all
# incentive offers are CYO incentive offers.
cyo_incentives = response.incentive_offer.cyo_incentives
puts cyo_incentives.low_offer.inspect
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Human-Readable Output: Replace .inspect with a helper method to print specific attributes like offer_id or expiration_date. Raw object inspection is difficult for end-users to parse."

puts cyo_incentives.medium_offer.inspect
puts cyo_incentives.high_offer.inspect
else
puts "No incentives found."
end
end
# [END fetch_incentives]

if __FILE__ == $0
options = {}
# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
options[:email_address] = "INSERT_EMAIL_ADDRESS_HERE"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placeholder Check: Add a check to ensure the user actually replaced INSERT_EMAIL_ADDRESS_HERE. You could raise an error immediately if the string still contains 'INSERT' to prevent an unnecessary API round-trip.

options[:language_code] = "en"
options[:country_code] = "US"

OptionParser.new do |opts|
opts.banner = sprintf("Usage: %s [options]", File.basename(__FILE__))

opts.separator ""
opts.separator "Options:"

opts.on("-e", "--email-address EMAIL-ADDRESS", String, "The email of the user to fetch incentives for") do |v|
options[:email_address] = v
end

opts.on("-l", "--language-code LANGUAGE-CODE", String, "The language code of the user (e.g. 'en')") do |v|
options[:language_code] = v
end

opts.on("-k", "--country-code COUNTRY-CODE", String, "The country code of the user (e.g. 'US')") do |v|
options[:country_code] = v
end

opts.separator ""
opts.separator "Help:"

opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end.parse!

begin
fetch_incentives(
options.fetch(:email_address),
options.fetch(:language_code),
options.fetch(:country_code)
)
rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
e.failure.errors.each do |error|
$stderr.printf("Error with message: %s\n", error.message)
error.location&.field_path_elements&.each do |field_path_element|
$stderr.printf("\tOn field: %s\n", field_path_element.field_name)
end
error.error_code.to_h.each do |k, v|
next if v == :UNSPECIFIED
$stderr.printf("\tType: %s\n\tCode: %s\n", k, v)
end
end
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error Bubbling: Instead of re-raising the raw GoogleAdsError, consider wrapping it in a domain-specific error (e.g., IncentiveFetchError). This makes it easier for higher-level services to catch and handle logic errors separately from network errors.

end
end
Loading