facebook_ads 0.6.3 → 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -10
- data/.travis.yml +1 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -1
- data/README.md +4 -4
- data/Rakefile +2 -0
- data/bin/{console → facebook_ads_console} +1 -0
- data/facebook_ads.gemspec +3 -2
- data/lib/facebook_ads.rb +4 -6
- data/lib/facebook_ads/account.rb +13 -0
- data/lib/facebook_ads/ad.rb +2 -0
- data/lib/facebook_ads/ad_account.rb +45 -46
- data/lib/facebook_ads/ad_audience.rb +2 -0
- data/lib/facebook_ads/ad_campaign.rb +7 -4
- data/lib/facebook_ads/ad_creative.rb +2 -0
- data/lib/facebook_ads/ad_exception.rb +2 -0
- data/lib/facebook_ads/ad_image.rb +6 -5
- data/lib/facebook_ads/ad_insight.rb +2 -0
- data/lib/facebook_ads/ad_product.rb +2 -0
- data/lib/facebook_ads/ad_product_catalog.rb +2 -8
- data/lib/facebook_ads/ad_product_feed.rb +2 -0
- data/lib/facebook_ads/ad_product_set.rb +2 -0
- data/lib/facebook_ads/ad_set.rb +54 -30
- data/lib/facebook_ads/ad_targeting.rb +6 -4
- data/lib/facebook_ads/ad_user.rb +15 -0
- data/lib/facebook_ads/advertisable_application.rb +14 -0
- data/lib/facebook_ads/base.rb +37 -11
- data/spec/facebook_ads/ad_account_spec.rb +2 -0
- data/spec/facebook_ads/ad_campaign_spec.rb +2 -0
- data/spec/facebook_ads/ad_creative_spec.rb +2 -0
- data/spec/facebook_ads/ad_image_spec.rb +2 -0
- data/spec/facebook_ads/ad_insight_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_catalog_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_feed_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_set_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_spec.rb +2 -0
- data/spec/facebook_ads/ad_set_spec.rb +2 -0
- data/spec/facebook_ads/ad_spec.rb +2 -0
- data/spec/facebook_ads/ad_targeting_spec.rb +2 -0
- data/spec/facebook_ads/facebook_ads_spec.rb +2 -0
- data/spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_campaigns/lists_campaigns.yml +1 -1
- data/spec/fixtures/cassettes/FacebookAds_AdAccount/_create_ad_campaign/creates_a_new_ad_campaign.yml +1 -1
- data/spec/fixtures/cassettes/FacebookAds_AdCampaign/_destroy/sets_effective_status_to_deleted.yml +2 -2
- data/spec/spec_helper.rb +2 -0
- data/spec/support/approvals.rb +2 -0
- data/spec/support/facebook_ads.rb +2 -0
- data/spec/support/rest_client.rb +3 -5
- data/spec/support/vcr.rb +2 -0
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35a61cb9e965397a7e50da136ce697ed6ea3437e
|
4
|
+
data.tar.gz: ce5c09cf7acd43f39b0aa8f82d87d3a21962d50e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ad4ac70297f77209589c34323fd9af60d5721e8824cd2b3380e9a320c89de7124b45dd7a1133be489a362a631d35a873e00f059e10c2b9835231eae7f41f9c1
|
7
|
+
data.tar.gz: 365dda2ba4b84cdc4d7d86ca95ba1ccfdfd399dbc34664c2aa800cbba44b2cd41d3e181989653b03dc7821a9071dda4ef96f1b25f947aa71b7fd3861e1d8c236
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 2.
|
2
|
+
TargetRubyVersion: 2.3
|
3
3
|
|
4
4
|
Metrics/AbcSize:
|
5
5
|
Enabled: false
|
@@ -22,17 +22,12 @@ Metrics/ParameterLists:
|
|
22
22
|
Lint/EndAlignment:
|
23
23
|
Enabled: false
|
24
24
|
|
25
|
-
|
25
|
+
Layout/IndentationWidth:
|
26
26
|
Enabled: false
|
27
|
-
|
27
|
+
Layout/ElseAlignment:
|
28
28
|
Enabled: false
|
29
|
-
|
29
|
+
|
30
|
+
Style/Documentation:
|
30
31
|
Enabled: false
|
31
32
|
Style/GuardClause:
|
32
33
|
Enabled: false
|
33
|
-
Style/NumericLiterals:
|
34
|
-
Enabled: false
|
35
|
-
|
36
|
-
# This is temporary - it can be remove after updating rubocop to the latest.
|
37
|
-
Style/PercentLiteralDelimiters:
|
38
|
-
Enabled: false
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
## 0.6.4 (2018-10-03)
|
2
|
+
- Expose `budget_remaining`, `daily_budget`, `lifetime_budget` for Ad Campaigns. These are needed when we have budgets at the Ad Campaign level and not at the Ad Set level.
|
3
|
+
|
1
4
|
## 0.6.3 (2018-05-17)
|
2
5
|
- Replaced deprecated `is_autobid` with new `bid_strategy` field
|
3
6
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -49,7 +49,7 @@ It reads the Access Token from a file called test_access_token.
|
|
49
49
|
|
50
50
|
```bash
|
51
51
|
echo [YOUR_ACCESS_TOKEN] > test_access_token
|
52
|
-
bin/
|
52
|
+
bin/facebook_ads_console
|
53
53
|
```
|
54
54
|
|
55
55
|
## Usage Examples
|
@@ -230,9 +230,9 @@ image_ad_creative = account.create_ad_creative({
|
|
230
230
|
Create a single creative for a web link:
|
231
231
|
```ruby
|
232
232
|
image_ad_creative = account.create_ad_creative({
|
233
|
-
title: 'Test Link Title',
|
234
|
-
body: 'Link Description Text',
|
235
|
-
object_url: 'www.example.com/my-ad-link',
|
233
|
+
title: 'Test Link Title',
|
234
|
+
body: 'Link Description Text',
|
235
|
+
object_url: 'www.example.com/my-ad-link',
|
236
236
|
link_url: 'www.example.com/my-ad-link',
|
237
237
|
image_hash: ad_images.first.hash,
|
238
238
|
}, creative_type: 'link')
|
data/Rakefile
CHANGED
data/facebook_ads.gemspec
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# To publish the next version:
|
4
5
|
# gem build facebook_ads.gemspec
|
5
|
-
# gem push facebook_ads-0.6.
|
6
|
+
# gem push facebook_ads-0.6.4.gem
|
6
7
|
Gem::Specification.new do |s|
|
7
8
|
s.name = 'facebook_ads'
|
8
|
-
s.version = '0.6.
|
9
|
+
s.version = '0.6.4'
|
9
10
|
s.platform = Gem::Platform::RUBY
|
10
11
|
s.licenses = ['MIT']
|
11
12
|
s.authors = ['Chris Estreich']
|
data/lib/facebook_ads.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# External requires.
|
2
4
|
require 'json'
|
3
5
|
require 'rest-client'
|
@@ -38,7 +40,7 @@ module FacebookAds
|
|
38
40
|
end
|
39
41
|
|
40
42
|
def self.api_version
|
41
|
-
@api_version = '
|
43
|
+
@api_version = '3.1' unless defined?(@api_version)
|
42
44
|
@api_version
|
43
45
|
end
|
44
46
|
|
@@ -59,11 +61,7 @@ module FacebookAds
|
|
59
61
|
end
|
60
62
|
|
61
63
|
def self.appsecret_proof
|
62
|
-
OpenSSL::HMAC.hexdigest(
|
63
|
-
OpenSSL::Digest.new('sha256'),
|
64
|
-
@app_secret,
|
65
|
-
@access_token
|
66
|
-
)
|
64
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @app_secret, @access_token)
|
67
65
|
end
|
68
66
|
|
69
67
|
def self.business_id=(business_id)
|
data/lib/facebook_ads/ad.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FacebookAds
|
2
|
-
# An ad account has many ad campaigns, ad images, and ad creatives.
|
3
4
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-account
|
4
5
|
class AdAccount < Base
|
5
6
|
FIELDS = %w[id account_id account_status age created_time currency name].freeze
|
@@ -8,17 +9,21 @@ module FacebookAds
|
|
8
9
|
def all(query = {})
|
9
10
|
get('/me/adaccounts', query: query, objectify: true)
|
10
11
|
end
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
# AdvertisableApplication
|
15
|
+
|
16
|
+
def advertisable_applications(limit: 100)
|
17
|
+
AdvertisableApplication.paginate("/#{id}/advertisable_applications", query: { limit: limit })
|
18
|
+
end
|
19
|
+
|
20
|
+
# AdUser
|
21
|
+
|
22
|
+
def ad_users(limit: 100)
|
23
|
+
AdCampaign.paginate("/#{id}/users", query: { limit: limit })
|
19
24
|
end
|
20
25
|
|
21
|
-
#
|
26
|
+
# AdCampaign
|
22
27
|
|
23
28
|
def ad_campaigns(effective_status: ['ACTIVE'], limit: 100)
|
24
29
|
AdCampaign.paginate("/#{id}/campaigns", query: { effective_status: effective_status, limit: limit })
|
@@ -39,7 +44,19 @@ module FacebookAds
|
|
39
44
|
AdCampaign.find(result['id'])
|
40
45
|
end
|
41
46
|
|
42
|
-
#
|
47
|
+
# AdSet
|
48
|
+
|
49
|
+
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
50
|
+
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
51
|
+
end
|
52
|
+
|
53
|
+
# Ad
|
54
|
+
|
55
|
+
def ads(effective_status: ['ACTIVE'], limit: 100)
|
56
|
+
Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
|
57
|
+
end
|
58
|
+
|
59
|
+
# AdImage
|
43
60
|
|
44
61
|
def ad_images(hashes: nil, limit: 100)
|
45
62
|
if !hashes.nil?
|
@@ -60,7 +77,7 @@ module FacebookAds
|
|
60
77
|
!response['images'].nil? ? ad_images(hashes: response['images'].map { |_key, hash| hash['hash'] }) : []
|
61
78
|
end
|
62
79
|
|
63
|
-
#
|
80
|
+
# AdCreative
|
64
81
|
|
65
82
|
def ad_creatives(limit: 100)
|
66
83
|
AdCreative.paginate("/#{id}/adcreatives", query: { limit: limit })
|
@@ -81,25 +98,27 @@ module FacebookAds
|
|
81
98
|
end
|
82
99
|
end
|
83
100
|
|
84
|
-
#
|
85
|
-
|
86
|
-
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
87
|
-
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
88
|
-
end
|
89
|
-
|
90
|
-
# has_many ads
|
101
|
+
# AdAudience
|
91
102
|
|
92
|
-
def
|
93
|
-
|
103
|
+
def ad_audiences(limit: 100)
|
104
|
+
AdAudience.paginate("/#{id}/customaudiences", query: { limit: limit })
|
94
105
|
end
|
95
106
|
|
96
|
-
|
107
|
+
def create_ad_audience_with_pixel(name:, pixel_id:, event_name:, subtype: 'WEBSITE', retention_days: 15)
|
108
|
+
query = {
|
109
|
+
name: name,
|
110
|
+
pixel_id: pixel_id,
|
111
|
+
subtype: subtype,
|
112
|
+
retention_days: retention_days,
|
113
|
+
rule: { event: { i_contains: event_name } }.to_json,
|
114
|
+
prefill: 1
|
115
|
+
}
|
97
116
|
|
98
|
-
|
99
|
-
AdAudience.
|
117
|
+
result = AdAudience.post("/#{id}/customaudiences", query: query)
|
118
|
+
AdAudience.find(result['id'])
|
100
119
|
end
|
101
120
|
|
102
|
-
#
|
121
|
+
# AdInsight
|
103
122
|
|
104
123
|
def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
|
105
124
|
ad_campaigns.map do |ad_campaign|
|
@@ -123,6 +142,7 @@ module FacebookAds
|
|
123
142
|
optimize_for: optimization_goal,
|
124
143
|
currency: currency
|
125
144
|
}
|
145
|
+
|
126
146
|
self.class.get("/#{id}/reachestimate", query: query, objectify: false)
|
127
147
|
end
|
128
148
|
|
@@ -142,29 +162,8 @@ module FacebookAds
|
|
142
162
|
optimization_goal: optimization_goal,
|
143
163
|
currency: currency
|
144
164
|
}
|
145
|
-
self.class.get("/#{id}/delivery_estimate", query: query, objectify: false)
|
146
|
-
end
|
147
|
-
|
148
|
-
# has_many applications
|
149
|
-
|
150
|
-
def applications
|
151
|
-
self.class.get("/#{id}/advertisable_applications", objectify: false)
|
152
|
-
end
|
153
|
-
|
154
|
-
# hash_many ad_audiences
|
155
|
-
|
156
|
-
def create_ad_audience_with_pixel(name:, pixel_id:, event_name:, subtype: 'WEBSITE', retention_days: 15)
|
157
|
-
query = {
|
158
|
-
name: name,
|
159
|
-
pixel_id: pixel_id,
|
160
|
-
subtype: subtype,
|
161
|
-
retention_days: retention_days,
|
162
|
-
rule: { event: { i_contains: event_name } }.to_json,
|
163
|
-
prefill: 1
|
164
|
-
}
|
165
165
|
|
166
|
-
|
167
|
-
AdAudience.find(result['id'])
|
166
|
+
self.class.get("/#{id}/delivery_estimate", query: query, objectify: false)
|
168
167
|
end
|
169
168
|
|
170
169
|
private
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FacebookAds
|
2
|
-
# An ad campaign has many ad sets and belongs to an ad account.
|
3
4
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group
|
4
5
|
class AdCampaign < Base
|
5
6
|
FIELDS = %w[
|
@@ -15,6 +16,7 @@ module FacebookAds
|
|
15
16
|
start_time
|
16
17
|
stop_time
|
17
18
|
updated_time spend_cap
|
19
|
+
budget_remaining daily_budget lifetime_budget
|
18
20
|
].freeze
|
19
21
|
|
20
22
|
STATUSES = %w[
|
@@ -45,13 +47,13 @@ module FacebookAds
|
|
45
47
|
VIDEO_VIEWS
|
46
48
|
].freeze
|
47
49
|
|
48
|
-
#
|
50
|
+
# AdAccount
|
49
51
|
|
50
52
|
def ad_account
|
51
53
|
@ad_account ||= AdAccount.find("act_#{account_id}")
|
52
54
|
end
|
53
55
|
|
54
|
-
#
|
56
|
+
# AdSet
|
55
57
|
|
56
58
|
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
57
59
|
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
@@ -94,7 +96,7 @@ module FacebookAds
|
|
94
96
|
AdSet.find(result['id'])
|
95
97
|
end
|
96
98
|
|
97
|
-
#
|
99
|
+
# AdInsight
|
98
100
|
|
99
101
|
def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
|
100
102
|
query = {
|
@@ -102,6 +104,7 @@ module FacebookAds
|
|
102
104
|
time_increment: time_increment,
|
103
105
|
time_range: { since: range.first.to_s, until: range.last.to_s }
|
104
106
|
}
|
107
|
+
|
105
108
|
AdInsight.paginate("/#{id}/insights", query: query)
|
106
109
|
end
|
107
110
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FacebookAds
|
2
|
-
# An ad image belongs to an ad account.
|
3
4
|
# An image will always produce the same hash.
|
4
5
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-image
|
5
6
|
class AdImage < Base
|
@@ -11,10 +12,10 @@ module FacebookAds
|
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
# @
|
15
|
-
#
|
16
|
-
|
17
|
-
|
15
|
+
# @FIXME: You are setting a key that conflicts with a built-in method
|
16
|
+
# FacebookAds::AdImage#hash defined in Hash.
|
17
|
+
disable_warnings
|
18
|
+
|
18
19
|
def hash
|
19
20
|
self[:hash]
|
20
21
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FacebookAds
|
2
4
|
# https://developers.facebook.com/docs/marketing-api/reference/product-catalog
|
3
5
|
class AdProductCatalog < Base
|
@@ -8,14 +10,6 @@ module FacebookAds
|
|
8
10
|
get("/#{FacebookAds.business_id}/owned_product_catalogs", query: query, objectify: true)
|
9
11
|
end
|
10
12
|
|
11
|
-
def find_by(conditions)
|
12
|
-
all.detect do |object|
|
13
|
-
conditions.all? do |key, value|
|
14
|
-
object.send(key) == value
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
13
|
def create(name:)
|
20
14
|
query = { name: name }
|
21
15
|
result = post("/#{FacebookAds.business_id}/owned_product_catalogs", query: query)
|
data/lib/facebook_ads/ad_set.rb
CHANGED
@@ -1,38 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FacebookAds
|
2
|
-
#
|
3
|
-
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign
|
4
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-account/adsets
|
4
5
|
class AdSet < Base
|
5
|
-
# Full set of fields:
|
6
|
-
# FIELDS = %w(
|
7
|
-
# id account_id adlabels adset_schedule attribution_window_days
|
8
|
-
# bid_amount bid_info billing_event budget_remaining
|
9
|
-
# campaign campaign_id configured_status created_time
|
10
|
-
# creative_sequence daily_budget effective_status end_time
|
11
|
-
# frequency_cap frequency_cap_reset_period
|
12
|
-
# frequency_control_specs instagram_actor_id
|
13
|
-
# bid_strategy lifetime_budget
|
14
|
-
# lifetime_frequency_cap lifetime_imps name optimization_goal
|
15
|
-
# pacing_type promoted_object recommendations
|
16
|
-
# recurring_budget_semantics rf_prediction_id rtb_flag
|
17
|
-
# start_time status targeting time_based_ad_rotation_id_blocks
|
18
|
-
# time_based_ad_rotation_intervals updated_time
|
19
|
-
# use_new_app_click
|
20
|
-
# ).freeze
|
21
|
-
|
22
|
-
# Fields we might actually care about:
|
23
6
|
FIELDS = %w[
|
24
|
-
id
|
7
|
+
id
|
8
|
+
account_id
|
9
|
+
campaign_id
|
25
10
|
name
|
26
|
-
status
|
27
|
-
|
28
|
-
|
11
|
+
status
|
12
|
+
configured_status
|
13
|
+
effective_status
|
14
|
+
bid_strategy
|
15
|
+
bid_amount
|
16
|
+
billing_event
|
17
|
+
optimization_goal
|
18
|
+
pacing_type
|
19
|
+
daily_budget
|
20
|
+
budget_remaining
|
21
|
+
lifetime_budget
|
29
22
|
promoted_object
|
30
23
|
targeting
|
31
|
-
created_time
|
24
|
+
created_time
|
25
|
+
updated_time
|
32
26
|
].freeze
|
33
27
|
|
34
|
-
STATUSES
|
35
|
-
|
28
|
+
STATUSES = %w[
|
29
|
+
ACTIVE
|
30
|
+
PAUSED
|
31
|
+
DELETED
|
32
|
+
PENDING_REVIEW
|
33
|
+
DISAPPROVED
|
34
|
+
PREAPPROVED
|
35
|
+
PENDING_BILLING_INFO
|
36
|
+
CAMPAIGN_PAUSED
|
37
|
+
ARCHIVED
|
38
|
+
ADSET_PAUSED
|
39
|
+
].freeze
|
40
|
+
|
41
|
+
BILLING_EVENTS = %w[APP_INSTALLS IMPRESSIONS].freeze
|
42
|
+
|
36
43
|
OPTIMIZATION_GOALS = %w[
|
37
44
|
NONE
|
38
45
|
APP_INSTALLS
|
@@ -55,25 +62,26 @@ module FacebookAds
|
|
55
62
|
APP_DOWNLOADS
|
56
63
|
LANDING_PAGE_VIEWS
|
57
64
|
].freeze
|
65
|
+
|
58
66
|
BID_STRATEGIES = %w[
|
59
67
|
LOWEST_COST_WITHOUT_CAP
|
60
68
|
LOWEST_COST_WITH_BID_CAP
|
61
69
|
TARGET_COST
|
62
70
|
].freeze
|
63
71
|
|
64
|
-
#
|
72
|
+
# AdAccount
|
65
73
|
|
66
74
|
def ad_account
|
67
75
|
@ad_account ||= AdAccount.find("act_#{account_id}")
|
68
76
|
end
|
69
77
|
|
70
|
-
#
|
78
|
+
# AdCampaign
|
71
79
|
|
72
80
|
def ad_campaign
|
73
81
|
@campaign ||= AdCampaign.find(campaign_id)
|
74
82
|
end
|
75
83
|
|
76
|
-
#
|
84
|
+
# Ad
|
77
85
|
|
78
86
|
def ads(effective_status: ['ACTIVE'], limit: 100)
|
79
87
|
Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
|
@@ -84,5 +92,21 @@ module FacebookAds
|
|
84
92
|
result = Ad.post("/act_#{account_id}/ads", query: query)
|
85
93
|
Ad.find(result['id'])
|
86
94
|
end
|
95
|
+
|
96
|
+
# AdInsight
|
97
|
+
|
98
|
+
# levels = enum {ad, adset, campaign, account}
|
99
|
+
# breakdowns = list<enum {..., product_id, ...}>
|
100
|
+
def ad_insights(range: Date.today..Date.today, level: nil, breakdowns: [], fields: [], limit: 1_000)
|
101
|
+
query = {
|
102
|
+
time_range: { since: range.first.to_s, until: range.last.to_s },
|
103
|
+
level: level,
|
104
|
+
breakdowns: breakdowns&.join(','),
|
105
|
+
fields: fields&.join(','),
|
106
|
+
limit: limit
|
107
|
+
}.reject { |_key, value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
|
108
|
+
|
109
|
+
AdInsight.paginate("/#{id}/insights", query: query)
|
110
|
+
end
|
87
111
|
end
|
88
112
|
end
|
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FacebookAds
|
2
4
|
# https://developers.facebook.com/docs/marketing-api/targeting-specs
|
3
5
|
class AdTargeting
|
4
6
|
MEN = 1
|
5
7
|
WOMEN = 2
|
6
8
|
GENDERS = [MEN, WOMEN].freeze
|
7
|
-
ANDROID_OS = 'Android'
|
8
|
-
APPLE_OS = 'iOS'
|
9
|
+
ANDROID_OS = 'Android'
|
10
|
+
APPLE_OS = 'iOS'
|
9
11
|
OSES = [ANDROID_OS, APPLE_OS].freeze
|
10
12
|
ANDROID_DEVICES = %w[Android_Smartphone Android_Tablet].freeze
|
11
13
|
APPLE_DEVICES = %w[iPhone iPad iPod].freeze
|
12
14
|
DEVICES = ANDROID_DEVICES + APPLE_DEVICES
|
13
|
-
INSTALLED = 'installed'
|
14
|
-
NOT_INSTALLED = 'not_installed'
|
15
|
+
INSTALLED = 'installed'
|
16
|
+
NOT_INSTALLED = 'not_installed'
|
15
17
|
APP_INSTALL_STATES = [INSTALLED, NOT_INSTALLED].freeze
|
16
18
|
|
17
19
|
attr_accessor :genders, :age_min, :age_max, :countries, :user_os, :user_device, :app_install_state, :custom_locations, :income
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FacebookAds
|
4
|
+
# An ad user belongs to an ad account.
|
5
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group
|
6
|
+
class AdUser < Base
|
7
|
+
FIELDS = %w[].freeze
|
8
|
+
|
9
|
+
# belongs_to ad_account
|
10
|
+
|
11
|
+
def ad_account
|
12
|
+
@ad_account ||= AdAccount.find("act_#{account_id}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FacebookAds
|
4
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-account/advertisable_applications
|
5
|
+
class AdvertisableApplication < Base
|
6
|
+
FIELDS = %w[].freeze
|
7
|
+
|
8
|
+
# belongs_to ad_account
|
9
|
+
|
10
|
+
def ad_account
|
11
|
+
@ad_account ||= AdAccount.find("act_#{account_id}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/facebook_ads/base.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FacebookAds
|
2
4
|
# The base class for all ads objects.
|
3
5
|
class Base < Hashie::Mash
|
4
6
|
class << self
|
5
|
-
|
6
|
-
get("/#{id}", objectify: true)
|
7
|
-
end
|
7
|
+
# HTTP verbs.
|
8
8
|
|
9
9
|
def get(path, query: {}, objectify:)
|
10
|
-
query = pack(query, objectify: objectify)
|
10
|
+
query = pack(query, objectify: objectify)
|
11
11
|
uri = "#{FacebookAds.base_uri}#{path}?" + build_nested_query(query)
|
12
12
|
FacebookAds.logger.debug "GET #{uri}"
|
13
|
+
|
13
14
|
response = begin
|
14
15
|
RestClient.get(uri, accept: :json, accept_encoding: :identity)
|
15
16
|
rescue RestClient::Exception => e
|
16
17
|
exception(:get, path, e)
|
17
18
|
end
|
19
|
+
|
18
20
|
unpack(response, objectify: objectify)
|
19
21
|
end
|
20
22
|
|
@@ -22,11 +24,13 @@ module FacebookAds
|
|
22
24
|
query = pack(query, objectify: false)
|
23
25
|
uri = "#{FacebookAds.base_uri}#{path}"
|
24
26
|
FacebookAds.logger.debug "POST #{uri} #{query}"
|
27
|
+
|
25
28
|
response = begin
|
26
29
|
RestClient.post(uri, query)
|
27
30
|
rescue RestClient::Exception => e
|
28
31
|
exception(:post, path, e)
|
29
32
|
end
|
33
|
+
|
30
34
|
unpack(response, objectify: false)
|
31
35
|
end
|
32
36
|
|
@@ -34,28 +38,50 @@ module FacebookAds
|
|
34
38
|
query = pack(query, objectify: false)
|
35
39
|
uri = "#{FacebookAds.base_uri}#{path}?" + build_nested_query(query)
|
36
40
|
FacebookAds.logger.debug "DELETE #{uri}"
|
41
|
+
|
37
42
|
response = begin
|
38
43
|
RestClient.delete(uri)
|
39
44
|
rescue RestClient::Exception => e
|
40
45
|
exception(:delete, path, e)
|
41
46
|
end
|
47
|
+
|
42
48
|
unpack(response, objectify: false)
|
43
49
|
end
|
44
50
|
|
51
|
+
# Common idioms.
|
52
|
+
|
53
|
+
def all(_query = {})
|
54
|
+
raise StandardError, 'Subclass must implement `all`.'
|
55
|
+
end
|
56
|
+
|
57
|
+
def find(id)
|
58
|
+
get("/#{id}", objectify: true)
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_by(conditions)
|
62
|
+
all.detect do |object|
|
63
|
+
conditions.all? do |key, value|
|
64
|
+
object.send(key) == value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
45
69
|
def paginate(path, query: {})
|
46
|
-
query[:limit]
|
47
|
-
|
48
|
-
response = get(path, query: query
|
49
|
-
data
|
70
|
+
query = query.merge(limit: 100) unless query[:limit]
|
71
|
+
query = query.merge(fields: self::FIELDS.join(',')) unless query[:fields] || self::FIELDS.empty?
|
72
|
+
response = get(path, query: query, objectify: false)
|
73
|
+
data = response['data'].nil? ? [] : response['data']
|
50
74
|
|
51
|
-
if data.length == limit
|
75
|
+
if data.length == query[:limit]
|
52
76
|
while !(paging = response['paging']).nil? && !(url = paging['next']).nil?
|
53
77
|
FacebookAds.logger.debug "GET #{url}"
|
78
|
+
|
54
79
|
response = begin
|
55
80
|
RestClient.get(url)
|
56
81
|
rescue RestClient::Exception => e
|
57
82
|
exception(:get, url, e)
|
58
83
|
end
|
84
|
+
|
59
85
|
response = unpack(response, objectify: false)
|
60
86
|
data += response['data'] unless response['data'].nil?
|
61
87
|
end
|
@@ -85,8 +111,8 @@ module FacebookAds
|
|
85
111
|
def pack(hash, objectify:)
|
86
112
|
hash = hash.merge(access_token: FacebookAds.access_token)
|
87
113
|
hash = hash.merge(appsecret_proof: FacebookAds.appsecret_proof) if FacebookAds.app_secret
|
88
|
-
hash = hash.merge(fields: self::FIELDS.join(',')) if objectify
|
89
|
-
hash.
|
114
|
+
hash = hash.merge(fields: self::FIELDS.join(',')) if objectify && !hash[:fields] && !self::FIELDS.empty?
|
115
|
+
hash.reject { |_key, value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
|
90
116
|
end
|
91
117
|
|
92
118
|
def unpack(response, objectify:)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
http_interactions:
|
3
3
|
- request:
|
4
4
|
method: get
|
5
|
-
uri: https://graph.facebook.com/v<api_version>/act_10152335766987003/campaigns?access_token=<access_token>&appsecret_proof=<appsecret_proof>&effective_status%5B%5D=ACTIVE&fields=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&limit=100
|
5
|
+
uri: https://graph.facebook.com/v<api_version>/act_10152335766987003/campaigns?access_token=<access_token>&appsecret_proof=<appsecret_proof>&effective_status%5B%5D=ACTIVE&fields=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,budget_remaining,daily_budget,lifetime_budget&limit=100
|
6
6
|
body:
|
7
7
|
encoding: US-ASCII
|
8
8
|
string: ''
|
data/spec/fixtures/cassettes/FacebookAds_AdAccount/_create_ad_campaign/creates_a_new_ad_campaign.yml
CHANGED
@@ -61,7 +61,7 @@ http_interactions:
|
|
61
61
|
recorded_at: Wed, 26 Apr 2017 00:48:41 GMT
|
62
62
|
- request:
|
63
63
|
method: get
|
64
|
-
uri: https://graph.facebook.com/v<api_version>/6076262393242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=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
|
64
|
+
uri: https://graph.facebook.com/v<api_version>/6076262393242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=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,budget_remaining,daily_budget,lifetime_budget
|
65
65
|
body:
|
66
66
|
encoding: US-ASCII
|
67
67
|
string: ''
|
data/spec/fixtures/cassettes/FacebookAds_AdCampaign/_destroy/sets_effective_status_to_deleted.yml
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
http_interactions:
|
3
3
|
- request:
|
4
4
|
method: get
|
5
|
-
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=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
|
5
|
+
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=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,budget_remaining,daily_budget,lifetime_budget
|
6
6
|
body:
|
7
7
|
encoding: US-ASCII
|
8
8
|
string: ''
|
@@ -110,7 +110,7 @@ http_interactions:
|
|
110
110
|
recorded_at: Wed, 26 Apr 2017 00:52:20 GMT
|
111
111
|
- request:
|
112
112
|
method: get
|
113
|
-
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=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
|
113
|
+
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=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,budget_remaining,daily_budget,lifetime_budget
|
114
114
|
body:
|
115
115
|
encoding: US-ASCII
|
116
116
|
string: ''
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/approvals.rb
CHANGED
data/spec/support/rest_client.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Force the request headers to only accept JSON, and disable gzip.
|
2
4
|
# This makes our VCR cassettes human readable.
|
3
5
|
module RestClient
|
4
6
|
class Request
|
5
7
|
def default_headers
|
6
|
-
{
|
7
|
-
accept: 'application/json',
|
8
|
-
accept_encoding: 'identity',
|
9
|
-
user_agent: RestClient::Platform.default_user_agent
|
10
|
-
}
|
8
|
+
{ accept: 'application/json', accept_encoding: 'identity' }
|
11
9
|
end
|
12
10
|
end
|
13
11
|
end
|
data/spec/support/vcr.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: facebook_ads
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Estreich
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rest-client
|
@@ -42,7 +42,7 @@ description: This gem allows to easily manage your Facebook ads via Facebook's M
|
|
42
42
|
API in ruby.
|
43
43
|
email: chris@tophatter.com
|
44
44
|
executables:
|
45
|
-
-
|
45
|
+
- facebook_ads_console
|
46
46
|
extensions: []
|
47
47
|
extra_rdoc_files:
|
48
48
|
- README.md
|
@@ -57,9 +57,10 @@ files:
|
|
57
57
|
- LICENSE.md
|
58
58
|
- README.md
|
59
59
|
- Rakefile
|
60
|
-
- bin/
|
60
|
+
- bin/facebook_ads_console
|
61
61
|
- facebook_ads.gemspec
|
62
62
|
- lib/facebook_ads.rb
|
63
|
+
- lib/facebook_ads/account.rb
|
63
64
|
- lib/facebook_ads/ad.rb
|
64
65
|
- lib/facebook_ads/ad_account.rb
|
65
66
|
- lib/facebook_ads/ad_audience.rb
|
@@ -74,6 +75,8 @@ files:
|
|
74
75
|
- lib/facebook_ads/ad_product_set.rb
|
75
76
|
- lib/facebook_ads/ad_set.rb
|
76
77
|
- lib/facebook_ads/ad_targeting.rb
|
78
|
+
- lib/facebook_ads/ad_user.rb
|
79
|
+
- lib/facebook_ads/advertisable_application.rb
|
77
80
|
- lib/facebook_ads/base.rb
|
78
81
|
- spec/facebook_ads/ad_account_spec.rb
|
79
82
|
- spec/facebook_ads/ad_campaign_spec.rb
|
@@ -136,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
139
|
version: '0'
|
137
140
|
requirements: []
|
138
141
|
rubyforge_project:
|
139
|
-
rubygems_version: 2.6.
|
142
|
+
rubygems_version: 2.6.12
|
140
143
|
signing_key:
|
141
144
|
specification_version: 4
|
142
145
|
summary: Facebook Marketing API SDK for Ruby.
|