facebook_ads 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +129 -0
- data/README.markdown +380 -0
- data/bin/console +13 -0
- data/facebook_ads.gemspec +26 -0
- data/lib/facebook_ads.rb +46 -0
- data/lib/facebook_ads/ad.rb +34 -0
- data/lib/facebook_ads/ad_account.rb +126 -0
- data/lib/facebook_ads/ad_campaign.rb +54 -0
- data/lib/facebook_ads/ad_creative.rb +66 -0
- data/lib/facebook_ads/ad_image.rb +28 -0
- data/lib/facebook_ads/ad_insight.rb +24 -0
- data/lib/facebook_ads/ad_set.rb +35 -0
- data/lib/facebook_ads/ad_targeting.rb +61 -0
- data/lib/facebook_ads/base.rb +151 -0
- data/spec/ad_account_spec.rb +78 -0
- data/spec/ad_campaign_spec.rb +13 -0
- data/spec/ad_creative_spec.rb +14 -0
- data/spec/ad_image_spec.rb +11 -0
- data/spec/ad_insight_spec.rb +11 -0
- data/spec/ad_set_spec.rb +13 -0
- data/spec/ad_spec.rb +13 -0
- data/spec/ad_targeting_spec.rb +4 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/fixtures.sh +17 -0
- data/spec/support/fixtures/6057330925170.json +1 -0
- data/spec/support/fixtures/6057810634370.json +1 -0
- data/spec/support/fixtures/6057810946970.json +1 -0
- data/spec/support/fixtures/6057824295570.json +1 -0
- data/spec/support/fixtures/act_861827983860489.json +1 -0
- data/spec/support/fixtures/act_861827983860489/adcreatives.json +1 -0
- data/spec/support/fixtures/act_861827983860489/adimages.json +1 -0
- data/spec/support/fixtures/act_861827983860489/ads.json +1 -0
- data/spec/support/fixtures/act_861827983860489/adsets.json +1 -0
- data/spec/support/fixtures/act_861827983860489/campaigns.json +1 -0
- data/spec/support/fixtures/me/adaccounts.json +1 -0
- data/spec/support/rack_facebook.rb +22 -0
- metadata +127 -0
data/bin/console
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'facebook_ads'
|
5
|
+
require 'awesome_print'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
FacebookAds.access_token = File.read('test_access_token').squish
|
9
|
+
FacebookAds.logger = Logger.new(STDOUT)
|
10
|
+
FacebookAds.logger.level = Logger::Severity::DEBUG
|
11
|
+
|
12
|
+
AwesomePrint.pry!
|
13
|
+
binding.pry
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# To release a new version:
|
2
|
+
# gem build facebook_ads.gemspec
|
3
|
+
# gem push facebook_ads-0.1.5.gem
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'facebook_ads'
|
6
|
+
s.version = '0.1.5'
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.licenses = ['MIT']
|
9
|
+
s.authors = ['Chris Estreich']
|
10
|
+
s.email = 'cestreich@gmail.com'
|
11
|
+
s.homepage = 'https://github.com/cte/facebook-ads-sdk-ruby'
|
12
|
+
s.summary = "Facebook Marketing API SDK for Ruby."
|
13
|
+
s.description = "This gem allows to easily manage your Facebook ads via Facebook's Marketing API in ruby."
|
14
|
+
|
15
|
+
s.extra_rdoc_files = ['README.markdown']
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
|
21
|
+
s.required_ruby_version = '~> 2.0'
|
22
|
+
|
23
|
+
s.add_dependency 'activesupport', '~> 4.2'
|
24
|
+
s.add_dependency 'httmultiparty', '~> 0.3'
|
25
|
+
s.add_dependency 'hashie', '~> 3.4'
|
26
|
+
end
|
data/lib/facebook_ads.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# External requires.
|
2
|
+
require 'active_support/all'
|
3
|
+
require 'httmultiparty'
|
4
|
+
require 'hashie'
|
5
|
+
|
6
|
+
# Internal requires.
|
7
|
+
require 'facebook_ads/base'
|
8
|
+
Dir[File.expand_path('../facebook_ads/*.rb', __FILE__)].each { |f| require f }
|
9
|
+
|
10
|
+
# The primary namespace for this gem.
|
11
|
+
module FacebookAds
|
12
|
+
|
13
|
+
def self.logger=(logger)
|
14
|
+
@logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.logger
|
18
|
+
unless defined?(@logger)
|
19
|
+
@logger = Logger.new('/dev/null')
|
20
|
+
@logger.level = Logger::Severity::UNKNOWN
|
21
|
+
end
|
22
|
+
|
23
|
+
@logger
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.base_uri=(base_uri)
|
27
|
+
@base_uri = base_uri
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.base_uri
|
31
|
+
unless defined?(@base_uri)
|
32
|
+
@base_uri = 'https://graph.facebook.com/v2.6'
|
33
|
+
end
|
34
|
+
|
35
|
+
@base_uri
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.access_token=(access_token)
|
39
|
+
@access_token = access_token
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.access_token
|
43
|
+
@access_token
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# An ad belongs to an ad set. It is created using an ad creative.
|
3
|
+
# https://developers.facebook.com/docs/marketing-api/reference/adgroup
|
4
|
+
class Ad < Base
|
5
|
+
|
6
|
+
FIELDS = %w(id account_id campaign_id adset_id adlabels bid_amount bid_info bid_type configured_status conversion_specs created_time creative effective_status last_updated_by_app_id name tracking_specs updated_time ad_review_feedback)
|
7
|
+
STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED)
|
8
|
+
|
9
|
+
# belongs_to ad_account
|
10
|
+
|
11
|
+
def ad_account
|
12
|
+
@ad_set ||= FacebookAds::AdAccount.find(account_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
# belongs_to ad_campaign
|
16
|
+
|
17
|
+
def ad_campaign
|
18
|
+
@ad_set ||= FacebookAds::AdCampaign.find(campaign_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
# belongs_to ad_set
|
22
|
+
|
23
|
+
def ad_set
|
24
|
+
@ad_set ||= FacebookAds::AdSet.find(adset_id)
|
25
|
+
end
|
26
|
+
|
27
|
+
# belongs_to ad_creative
|
28
|
+
|
29
|
+
def ad_creative
|
30
|
+
@ad_creative ||= FacebookAds::AdCreative.find(creative['id'])
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# An ad account has many ad campaigns, ad images, and ad creatives.
|
3
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-account
|
4
|
+
class AdAccount < Base
|
5
|
+
|
6
|
+
FIELDS = %w(id account_id account_status age created_time currency name last_used_time)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def all
|
10
|
+
get('/me/adaccounts', objectify: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_by(conditions)
|
14
|
+
all.detect do |ad_account|
|
15
|
+
conditions.all? do |key, value|
|
16
|
+
ad_account.send(key) == value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# has_many campaigns
|
23
|
+
|
24
|
+
def ad_campaigns(effective_status: ['ACTIVE'], limit: 100)
|
25
|
+
FacebookAds::AdCampaign.paginate("/#{id}/campaigns", query: { effective_status: effective_status, limit: limit })
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_ad_campaign(name:, objective:, status: 'ACTIVE')
|
29
|
+
raise Exception, "Objective must be one of: #{FacebookAds::AdCampaign::OBJECTIVES.to_sentence}" unless FacebookAds::AdCampaign::OBJECTIVES.include?(objective)
|
30
|
+
raise Exception, "Status must be one of: #{FacebookAds::AdCampaign::STATUSES.to_sentence}" unless FacebookAds::AdCampaign::STATUSES.include?(status)
|
31
|
+
campaign = FacebookAds::AdCampaign.post("/#{id}/campaigns", query: { name: name, objective: objective, status: status }, objectify: true)
|
32
|
+
FacebookAds::AdCampaign.find(campaign.id)
|
33
|
+
end
|
34
|
+
|
35
|
+
# has_many ad_images
|
36
|
+
|
37
|
+
def ad_images(hashes: nil, limit: 100)
|
38
|
+
if hashes.present?
|
39
|
+
FacebookAds::AdImage.get("/#{id}/adimages", query: { hashes: hashes }, objectify: true)
|
40
|
+
else
|
41
|
+
FacebookAds::AdImage.paginate("/#{id}/adimages", query: { limit: limit })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_ad_images(urls)
|
46
|
+
files = urls.collect do |url|
|
47
|
+
pathname = Pathname.new(url)
|
48
|
+
name = "#{pathname.dirname.basename}.jpg"
|
49
|
+
data = HTTParty.get(url).body
|
50
|
+
file = File.open("/tmp/#{name}", 'w') # Assume *nix-based system.
|
51
|
+
file.binmode
|
52
|
+
file.write(data)
|
53
|
+
file.close
|
54
|
+
[name, File.open(file.path)]
|
55
|
+
end.to_h
|
56
|
+
|
57
|
+
response = FacebookAds::AdImage.post("/#{id}/adimages", query: files, objectify: false)
|
58
|
+
files.values.each { |file| File.delete(file.path) }
|
59
|
+
|
60
|
+
if response['images'].present?
|
61
|
+
hashes = response['images'].map { |key, hash| hash['hash'] }
|
62
|
+
ad_images(hashes: hashes)
|
63
|
+
else
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# has_many ad_creatives
|
69
|
+
|
70
|
+
def ad_creatives(limit: 100)
|
71
|
+
FacebookAds::AdCreative.paginate("/#{id}/adcreatives", query: { limit: limit })
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_ad_creative(creative, carousel: true)
|
75
|
+
optional = %i(instagram_actor_id)
|
76
|
+
|
77
|
+
required = if carousel
|
78
|
+
%i(name page_id link message assets call_to_action_type multi_share_optimized multi_share_end_card)
|
79
|
+
else
|
80
|
+
%i(name page_id message link link_title image_hash call_to_action_type)
|
81
|
+
end
|
82
|
+
|
83
|
+
if (keys = required - creative.keys).present?
|
84
|
+
raise Exception, "Creative is missing the following: #{keys.to_sentence}"
|
85
|
+
end
|
86
|
+
|
87
|
+
raise Exception, "Creative call_to_action_type must be one of: #{FacebookAds::AdCreative::CALL_TO_ACTION_TYPES.to_sentence}" unless FacebookAds::AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
|
88
|
+
|
89
|
+
query = if carousel
|
90
|
+
FacebookAds::AdCreative.carousel(creative)
|
91
|
+
else
|
92
|
+
FacebookAds::AdCreative.photo(creative)
|
93
|
+
end
|
94
|
+
|
95
|
+
creative = FacebookAds::AdCreative.post("/#{id}/adcreatives", query: query, objectify: true) # Returns a FacebookAds::AdCreative instance.
|
96
|
+
FacebookAds::AdCreative.find(creative.id)
|
97
|
+
end
|
98
|
+
|
99
|
+
# has_many ad_sets
|
100
|
+
|
101
|
+
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
102
|
+
FacebookAds::AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
103
|
+
end
|
104
|
+
|
105
|
+
# has_many ads
|
106
|
+
|
107
|
+
def ads(effective_status: ['ACTIVE'], limit: 100)
|
108
|
+
FacebookAds::Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
|
109
|
+
end
|
110
|
+
|
111
|
+
# has_many ad_insights
|
112
|
+
|
113
|
+
def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
|
114
|
+
ad_campaigns.map do |ad_campaign|
|
115
|
+
ad_campaign.ad_insights(range: range, level: level, time_increment: time_increment)
|
116
|
+
end.flatten
|
117
|
+
end
|
118
|
+
|
119
|
+
# has_many applications
|
120
|
+
|
121
|
+
def applications
|
122
|
+
self.class.get("/#{id}/advertisable_applications", objectify: false)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# An ad campaign has many ad sets and belongs to an ad account.
|
3
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group
|
4
|
+
class AdCampaign < Base
|
5
|
+
|
6
|
+
FIELDS = %w(id account_id buying_type can_use_spend_cap configured_status created_time effective_status name objective start_time stop_time updated_time spend_cap)
|
7
|
+
STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED)
|
8
|
+
OBJECTIVES = %w(CONVERSIONS MOBILE_APP_INSTALLS) # %w(BRAND_AWARENESS CANVAS_APP_ENGAGEMENT CANVAS_APP_INSTALLS CONVERSIONS EVENT_RESPONSES EXTERNAL LEAD_GENERATION LINK_CLICKS LOCAL_AWARENESS MOBILE_APP_ENGAGEMENT MOBILE_APP_INSTALLS OFFER_CLAIMS PAGE_LIKES POST_ENGAGEMENT PRODUCT_CATALOG_SALES REACH VIDEO_VIEWS)
|
9
|
+
|
10
|
+
# belongs_to ad_account
|
11
|
+
|
12
|
+
def ad_account
|
13
|
+
@ad_account ||= FacebookAds::AdAccount.find("act_#{account_id}")
|
14
|
+
end
|
15
|
+
|
16
|
+
# has_many ad_sets
|
17
|
+
|
18
|
+
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
19
|
+
FacebookAds::AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_ad_set(name:, promoted_object:, targeting:, daily_budget:, optimization_goal:, billing_event: 'IMPRESSIONS', status: 'ACTIVE', is_autobid: true)
|
23
|
+
raise Exception, "Optimization goal must be one of: #{FacebookAds::AdSet::OPTIMIZATION_GOALS.to_sentence}" unless FacebookAds::AdSet::OPTIMIZATION_GOALS.include?(optimization_goal)
|
24
|
+
raise Exception, "Billing event must be one of: #{FacebookAds::AdSet::BILLING_EVENTS.to_sentence}" unless FacebookAds::AdSet::BILLING_EVENTS.include?(billing_event)
|
25
|
+
|
26
|
+
targeting.validate! # Will raise if invalid.
|
27
|
+
|
28
|
+
ad_set = FacebookAds::AdSet.post("/act_#{account_id}/adsets", query: { # Returns a FacebookAds::AdSet instance.
|
29
|
+
campaign_id: id,
|
30
|
+
name: name,
|
31
|
+
targeting: targeting.to_hash.to_json,
|
32
|
+
promoted_object: promoted_object.to_json,
|
33
|
+
optimization_goal: optimization_goal,
|
34
|
+
daily_budget: daily_budget,
|
35
|
+
billing_event: billing_event,
|
36
|
+
status: status,
|
37
|
+
is_autobid: is_autobid
|
38
|
+
}, objectify: true)
|
39
|
+
|
40
|
+
FacebookAds::AdSet.find(ad_set.id)
|
41
|
+
end
|
42
|
+
|
43
|
+
# has_many ad_insights
|
44
|
+
|
45
|
+
def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
|
46
|
+
FacebookAds::AdInsight.paginate("/#{id}/insights", query: {
|
47
|
+
level: level,
|
48
|
+
time_increment: time_increment,
|
49
|
+
time_range: { 'since': range.first.to_s, 'until': range.last.to_s }
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# Ad ad creative has many ad images and belongs to an ad account.
|
3
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-creative
|
4
|
+
class AdCreative < Base
|
5
|
+
|
6
|
+
FIELDS = %w(id name object_story_id object_story_spec object_type thumbnail_url run_status)
|
7
|
+
CALL_TO_ACTION_TYPES = %w(SHOP_NOW INSTALL_MOBILE_APP USE_MOBILE_APP SIGN_UP DOWNLOAD BUY_NOW) # %w(OPEN_LINK LIKE_PAGE SHOP_NOW PLAY_GAME INSTALL_APP USE_APP INSTALL_MOBILE_APP USE_MOBILE_APP BOOK_TRAVEL LISTEN_MUSIC WATCH_VIDEO LEARN_MORE SIGN_UP DOWNLOAD WATCH_MORE NO_BUTTON CALL_NOW BUY_NOW GET_OFFER GET_OFFER_VIEW GET_DIRECTIONS MESSAGE_PAGE SUBSCRIBE SELL_NOW DONATE_NOW GET_QUOTE CONTACT_US RECORD_NOW VOTE_NOW OPEN_MOVIES)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def photo(name:, page_id:, instagram_actor_id: nil, message:, link:, link_title:, image_hash:, call_to_action_type:)
|
12
|
+
object_story_spec = {
|
13
|
+
'page_id' => page_id, # 300664329976860
|
14
|
+
'instagram_actor_id' => instagram_actor_id, # 503391023081924
|
15
|
+
'link_data' => {
|
16
|
+
'link' => link, # https://tophatter.com/, https://itunes.apple.com/app/id619460348, http://play.google.com/store/apps/details?id=com.tophatter
|
17
|
+
'message' => message,
|
18
|
+
'image_hash' => image_hash,
|
19
|
+
'call_to_action' => {
|
20
|
+
'type' => call_to_action_type,
|
21
|
+
'value' => {
|
22
|
+
# 'application' =>,
|
23
|
+
'link' => link,
|
24
|
+
'link_title' => link_title
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
{
|
30
|
+
name: name,
|
31
|
+
object_story_spec: object_story_spec.to_json
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
# https://developers.facebook.com/docs/marketing-api/guides/carousel-ads/v2.6
|
36
|
+
def carousel(name:, page_id:, instagram_actor_id: nil, link:, message:, assets:, call_to_action_type:, multi_share_optimized:, multi_share_end_card:)
|
37
|
+
object_story_spec = {
|
38
|
+
'page_id' => page_id, # 300664329976860
|
39
|
+
'instagram_actor_id' => instagram_actor_id, # 503391023081924
|
40
|
+
'link_data' => {
|
41
|
+
'link' => link, # https://tophatter.com/, https://itunes.apple.com/app/id619460348, http://play.google.com/store/apps/details?id=com.tophatter
|
42
|
+
'message' => message,
|
43
|
+
'call_to_action' => { 'type' => call_to_action_type },
|
44
|
+
'child_attachments' => assets.collect { |asset|
|
45
|
+
{
|
46
|
+
'link' => link,
|
47
|
+
'image_hash' => asset[:hash],
|
48
|
+
'name' => asset[:title],
|
49
|
+
# 'description' => asset[:title],
|
50
|
+
'call_to_action' => { 'type' => call_to_action_type } # Redundant?
|
51
|
+
}
|
52
|
+
},
|
53
|
+
'multi_share_optimized' => multi_share_optimized,
|
54
|
+
'multi_share_end_card' => multi_share_end_card
|
55
|
+
}
|
56
|
+
}
|
57
|
+
{
|
58
|
+
name: name,
|
59
|
+
object_story_spec: object_story_spec.to_json
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# An ad image belongs to an ad account.
|
3
|
+
# An image will always produce the same hash.
|
4
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-image
|
5
|
+
class AdImage < Base
|
6
|
+
|
7
|
+
FIELDS = %w(id hash account_id name permalink_url original_width original_height)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def find(id)
|
11
|
+
raise Exception, 'NOT IMPLEMENTED'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
self[:hash]
|
17
|
+
end
|
18
|
+
|
19
|
+
def update(data)
|
20
|
+
raise Exception, 'NOT IMPLEMENTED'
|
21
|
+
end
|
22
|
+
|
23
|
+
def destroy
|
24
|
+
super(path: "/act_#{account_id}/adimages", query: { hash: hash })
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# Ad insights exist for ad accounts, ad campaigns, ad sets, and ads.
|
3
|
+
# A lot more can be done here.
|
4
|
+
# https://developers.facebook.com/docs/marketing-api/insights/overview
|
5
|
+
class AdInsight < Base
|
6
|
+
|
7
|
+
FIELDS = %w(ad_id objective impressions unique_actions cost_per_unique_action_type clicks cpc cpm ctr spend)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def find(id)
|
11
|
+
raise Exception, 'NOT IMPLEMENTED'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(data)
|
16
|
+
raise Exception, 'NOT IMPLEMENTED'
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy
|
20
|
+
raise Exception, 'NOT IMPLEMENTED'
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# An ad set belongs to a campaign and has many ads.
|
3
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign
|
4
|
+
class AdSet < Base
|
5
|
+
|
6
|
+
FIELDS = %w(id account_id campaign_id adlabels adset_schedule bid_amount bid_info billing_event configured_status created_time creative_sequence effective_status end_time frequency_cap frequency_cap_reset_period frequency_control_specs is_autobid lifetime_frequency_cap lifetime_imps name optimization_goal promoted_object rf_prediction_id rtb_flag start_time targeting updated_time use_new_app_click pacing_type budget_remaining daily_budget lifetime_budget)
|
7
|
+
STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED)
|
8
|
+
BILLING_EVENTS = %w(APP_INSTALLS IMPRESSIONS) # %w(CLICKS LINK_CLICKS OFFER_CLAIMS PAGE_LIKES POST_ENGAGEMENT VIDEO_VIEWS)
|
9
|
+
OPTIMIZATION_GOALS = %w(APP_INSTALLS OFFSITE_CONVERSIONS) # %w(NONE BRAND_AWARENESS CLICKS ENGAGED_USERS EXTERNAL EVENT_RESPONSES IMPRESSIONS LEAD_GENERATION LINK_CLICKS OFFER_CLAIMS PAGE_ENGAGEMENT PAGE_LIKES POST_ENGAGEMENT REACH SOCIAL_IMPRESSIONS VIDEO_VIEWS)
|
10
|
+
|
11
|
+
# belongs_to ad_account
|
12
|
+
|
13
|
+
def ad_account
|
14
|
+
@ad_set ||= FacebookAds::AdAccount.find(account_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
# belongs_to ad_campaign
|
18
|
+
|
19
|
+
def ad_campaign
|
20
|
+
@campaign ||= FacebookAds::AdCampaign.find(campaign_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
# has_many ads
|
24
|
+
|
25
|
+
def ads(effective_status: ['ACTIVE'], limit: 100)
|
26
|
+
FacebookAds::Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_ad(name:, creative_id:)
|
30
|
+
ad = FacebookAds::Ad.post("/act_#{account_id}/ads", query: { name: name, adset_id: id, creative: { creative_id: creative_id }.to_json }, objectify: true) # Returns a FacebookAds::Ad instance.
|
31
|
+
FacebookAds::Ad.find(ad.id)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|