facebook_ads 0.1.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.
- 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
|