facebook_ads 0.6.11 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +27 -0
- data/.rubocop.yml +11 -10
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +2 -2
- data/LICENSE.md +1 -1
- data/README.md +2 -3
- data/bin/facebook_ads_console +1 -1
- data/facebook_ads.gemspec +5 -3
- data/lib/facebook_ads.rb +71 -5
- data/lib/facebook_ads/ad_account.rb +18 -23
- data/lib/facebook_ads/ad_audience.rb +7 -7
- data/lib/facebook_ads/ad_campaign.rb +7 -7
- data/lib/facebook_ads/ad_creative.rb +2 -2
- data/lib/facebook_ads/ad_exception.rb +1 -0
- data/lib/facebook_ads/ad_image.rb +2 -2
- data/lib/facebook_ads/ad_insight.rb +3 -3
- data/lib/facebook_ads/ad_set.rb +1 -1
- data/lib/facebook_ads/ad_targeting.rb +2 -4
- data/lib/facebook_ads/base.rb +9 -16
- data/spec/facebook_ads/ad_account_spec.rb +16 -13
- data/spec/facebook_ads/ad_campaign_spec.rb +0 -3
- data/spec/facebook_ads/facebook_ads_spec.rb +50 -10
- data/spec/fixtures/cassettes/FacebookAds/_stubbornly_get/executes_the_request_for_the_provided_url.yml +63 -0
- data/spec/spec_helper.rb +1 -4
- data/spec/support/rest_client.rb +1 -1
- metadata +15 -32
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -7
- data/spec/facebook_ads/ad_creative_spec.rb +0 -6
- data/spec/facebook_ads/ad_image_spec.rb +0 -6
- data/spec/facebook_ads/ad_insight_spec.rb +0 -6
- data/spec/facebook_ads/ad_product_catalog_spec.rb +0 -29
- data/spec/facebook_ads/ad_product_feed_spec.rb +0 -6
- data/spec/facebook_ads/ad_product_set_spec.rb +0 -6
- data/spec/facebook_ads/ad_product_spec.rb +0 -23
- data/spec/facebook_ads/ad_set_activity_spec.rb +0 -6
- data/spec/facebook_ads/ad_set_spec.rb +0 -49
- data/spec/facebook_ads/ad_spec.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a7277800ac2a81f155ef378e2f5a0b78a85cf00c284ccb18c521b0288d6d467
|
4
|
+
data.tar.gz: 603886d3c551084d3b317fa18a0f55a9992113a7a29ae78bd8036490d56bd18c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c943be4095453ed6ef0da92ec2c8b1e542c92e0f10d898c958855f593e857c769697a36b0d0a6b168f8fa6ef7c47f616b84418807c0916b5dda447665130751b
|
7
|
+
data.tar.gz: 0ccc1ad73365c2791e1a58329265964d18c6af52fb1ffe85d3d95314971f3802ca716a2d6ca5403b7874882922ca2f2fe55c53104d9199f9e91ef1618489ad1c
|
@@ -0,0 +1,27 @@
|
|
1
|
+
version: 2.1
|
2
|
+
|
3
|
+
orbs:
|
4
|
+
aws-cli: circleci/aws-cli@1.2.1
|
5
|
+
ruby: circleci/ruby@1.1.0
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
test:
|
9
|
+
docker:
|
10
|
+
- image: circleci/ruby:2.7.0-node
|
11
|
+
executor: ruby/default
|
12
|
+
working_directory: ~/repo
|
13
|
+
environment:
|
14
|
+
APP_BUNDLER_VERSION: '2.1.2'
|
15
|
+
steps:
|
16
|
+
- checkout:
|
17
|
+
path: ~/repo
|
18
|
+
- ruby/install-deps:
|
19
|
+
with-cache: false
|
20
|
+
- ruby/rspec-test
|
21
|
+
- ruby/rubocop-check
|
22
|
+
|
23
|
+
workflows:
|
24
|
+
version: 2
|
25
|
+
build:
|
26
|
+
jobs:
|
27
|
+
- test
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 2.
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
NewCops: enable
|
3
4
|
|
4
5
|
Metrics/AbcSize:
|
5
6
|
Enabled: false
|
@@ -7,9 +8,6 @@ Metrics/CyclomaticComplexity:
|
|
7
8
|
Enabled: false
|
8
9
|
Metrics/PerceivedComplexity:
|
9
10
|
Enabled: false
|
10
|
-
|
11
|
-
Metrics/LineLength:
|
12
|
-
Enabled: false
|
13
11
|
Metrics/ClassLength:
|
14
12
|
Enabled: false
|
15
13
|
Metrics/MethodLength:
|
@@ -19,15 +17,18 @@ Metrics/BlockLength:
|
|
19
17
|
Metrics/ParameterLists:
|
20
18
|
Enabled: false
|
21
19
|
|
22
|
-
|
20
|
+
Layout/ElseAlignment:
|
23
21
|
Enabled: false
|
24
|
-
|
25
|
-
Layout/IndentationWidth:
|
22
|
+
Layout/EndAlignment:
|
26
23
|
Enabled: false
|
27
|
-
Layout/
|
24
|
+
Layout/RescueEnsureAlignment:
|
25
|
+
Enabled: false
|
26
|
+
Layout/EmptyLineAfterGuardClause:
|
27
|
+
Enabled: false
|
28
|
+
Layout/LineLength:
|
29
|
+
Enabled: false
|
30
|
+
Layout/IndentationWidth:
|
28
31
|
Enabled: false
|
29
32
|
|
30
33
|
Style/Documentation:
|
31
34
|
Enabled: false
|
32
|
-
Style/GuardClause:
|
33
|
-
Enabled: false
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
facebook-ruby-ads-sdk
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 0.7.0 (2020-09-29)
|
2
|
+
- Add stubborn network call helpers.
|
3
|
+
- Update default version to v7.0.
|
4
|
+
- Switch from TravisCI to CircleCI.
|
5
|
+
- Remove coveralls.
|
6
|
+
- Require ruby 2.5+.
|
7
|
+
- Update Rubocop & fix warnings.
|
8
|
+
|
1
9
|
## 0.6.11 (2020-02-28)
|
2
10
|
- Fixed memoization in `FacebookAds::Ad`.
|
3
11
|
|
data/Gemfile
CHANGED
@@ -7,14 +7,14 @@ group :development, :test do
|
|
7
7
|
gem 'awesome_print'
|
8
8
|
gem 'pry'
|
9
9
|
gem 'rake'
|
10
|
-
gem 'rubocop'
|
10
|
+
gem 'rubocop'
|
11
11
|
end
|
12
12
|
|
13
13
|
group :test do
|
14
14
|
gem 'approvals'
|
15
|
-
gem 'coveralls', require: false
|
16
15
|
gem 'multi_json'
|
17
16
|
gem 'rspec'
|
17
|
+
gem 'rspec_junit_formatter'
|
18
18
|
gem 'vcr'
|
19
19
|
gem 'webmock'
|
20
20
|
end
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
[![Gem Version](https://badge.fury.io/rb/facebook_ads.svg)](https://badge.fury.io/rb/facebook_ads)
|
2
|
-
[![Build Status](https://
|
3
|
-
[![Coverage Status](https://coveralls.io/repos/github/tophatter/facebook-ruby-ads-sdk/badge.svg?branch=master)](https://coveralls.io/github/tophatter/facebook-ruby-ads-sdk?branch=master)
|
2
|
+
[![Build Status](https://circleci.com/gh/tophatter/facebook-ruby-ads-sdk.svg?style=svg&circle-token=3fa538bc2aece310fdc07b82ce77d117758afb02)](https://circleci.com/gh/tophatter/facebook-ruby-ads-sdk)
|
4
3
|
|
5
4
|
# [Facebook Marketing API](https://developers.facebook.com/docs/marketing-apis) SDK for Ruby
|
6
5
|
|
@@ -17,7 +16,7 @@ gem install facebook_ads
|
|
17
16
|
Or, add the following to your Gemfile:
|
18
17
|
|
19
18
|
```ruby
|
20
|
-
gem 'facebook_ads', '~> 0.
|
19
|
+
gem 'facebook_ads', '~> 0.7'
|
21
20
|
```
|
22
21
|
|
23
22
|
## Permissions
|
data/bin/facebook_ads_console
CHANGED
@@ -19,7 +19,7 @@ rescue Errno::ENOENT
|
|
19
19
|
puts 'Create a test_business_id file and add your Business ID to it.'
|
20
20
|
end
|
21
21
|
|
22
|
-
FacebookAds.logger = Logger.new(
|
22
|
+
FacebookAds.logger = Logger.new($stdout)
|
23
23
|
FacebookAds.logger.level = Logger::Severity::DEBUG
|
24
24
|
|
25
25
|
require 'awesome_print'
|
data/facebook_ads.gemspec
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
# To publish the next version:
|
4
4
|
# gem build facebook_ads.gemspec
|
5
|
-
# gem push facebook_ads-0.
|
5
|
+
# gem push facebook_ads-0.7.0.gem
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = 'facebook_ads'
|
8
|
-
s.version = '0.
|
8
|
+
s.version = '0.7.0'
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.licenses = ['MIT']
|
11
11
|
s.authors = ['Chris Estreich']
|
@@ -16,7 +16,9 @@ Gem::Specification.new do |s|
|
|
16
16
|
|
17
17
|
s.extra_rdoc_files = ['README.md']
|
18
18
|
|
19
|
-
s.
|
19
|
+
s.required_ruby_version = '>= 2.5'
|
20
|
+
|
21
|
+
s.add_dependency 'hashie'
|
20
22
|
s.add_dependency 'rest-client', '>= 1.6'
|
21
23
|
|
22
24
|
s.files = `git ls-files`.split("\n")
|
data/lib/facebook_ads.rb
CHANGED
@@ -9,10 +9,31 @@ require 'openssl'
|
|
9
9
|
|
10
10
|
# Internal requires.
|
11
11
|
require 'facebook_ads/base'
|
12
|
-
|
12
|
+
require 'facebook_ads/account'
|
13
|
+
require 'facebook_ads/ad_account'
|
14
|
+
require 'facebook_ads/ad_audience'
|
15
|
+
require 'facebook_ads/ad_campaign'
|
16
|
+
require 'facebook_ads/ad_creative'
|
17
|
+
require 'facebook_ads/ad_exception'
|
18
|
+
require 'facebook_ads/ad_image'
|
19
|
+
require 'facebook_ads/ad_insight'
|
20
|
+
require 'facebook_ads/ad_product_catalog'
|
21
|
+
require 'facebook_ads/ad_product_feed'
|
22
|
+
require 'facebook_ads/ad_product'
|
23
|
+
require 'facebook_ads/ad_set_activity'
|
24
|
+
require 'facebook_ads/ad_set'
|
25
|
+
require 'facebook_ads/ad_targeting'
|
26
|
+
require 'facebook_ads/ad_user'
|
27
|
+
require 'facebook_ads/ad'
|
28
|
+
require 'facebook_ads/advertisable_application'
|
13
29
|
|
14
30
|
# The primary namespace for this gem.
|
15
31
|
module FacebookAds
|
32
|
+
REQUEST_HEADERS = { accept: :json, accept_encoding: :identity }.freeze
|
33
|
+
RETRYABLE_ERRORS = [RestClient::ExceptionWithResponse, Errno::ECONNRESET, Errno::ECONNREFUSED].freeze
|
34
|
+
RETRY_LIMIT = 10
|
35
|
+
RECOVERABLE_CODES = [1, 2, 2601].freeze
|
36
|
+
|
16
37
|
def self.logger=(logger)
|
17
38
|
@logger = logger
|
18
39
|
end
|
@@ -31,17 +52,16 @@ module FacebookAds
|
|
31
52
|
end
|
32
53
|
|
33
54
|
def self.base_uri
|
34
|
-
@base_uri
|
35
|
-
@base_uri
|
55
|
+
@base_uri ||= "https://graph.facebook.com/v#{api_version}"
|
36
56
|
end
|
37
57
|
|
38
58
|
def self.api_version=(api_version)
|
39
59
|
@api_version = api_version
|
60
|
+
@base_uri = nil
|
40
61
|
end
|
41
62
|
|
42
63
|
def self.api_version
|
43
|
-
@api_version
|
44
|
-
@api_version
|
64
|
+
@api_version ||= '7.0'
|
45
65
|
end
|
46
66
|
|
47
67
|
def self.access_token=(access_token)
|
@@ -71,4 +91,50 @@ module FacebookAds
|
|
71
91
|
def self.business_id
|
72
92
|
@business_id
|
73
93
|
end
|
94
|
+
|
95
|
+
# Stubborn network calls.
|
96
|
+
|
97
|
+
def self.stubbornly(retry_limit: RETRY_LIMIT, recoverable_codes: RECOVERABLE_CODES, debug: false)
|
98
|
+
raise ArgumentError unless block_given?
|
99
|
+
|
100
|
+
response = nil
|
101
|
+
retry_count = 0
|
102
|
+
|
103
|
+
loop do
|
104
|
+
response = yield
|
105
|
+
break
|
106
|
+
rescue *RETRYABLE_ERRORS => e
|
107
|
+
if e.is_a?(RestClient::ExceptionWithResponse)
|
108
|
+
error = begin
|
109
|
+
JSON.parse(e.response)
|
110
|
+
rescue JSON::ParserError
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
code = error&.[]('code')
|
115
|
+
raise e if code && !recoverable_codes.include?(code)
|
116
|
+
end
|
117
|
+
|
118
|
+
raise e if retry_count >= retry_limit
|
119
|
+
|
120
|
+
retry_count += 1
|
121
|
+
wait = (retry_count**2) + 15 + (rand(15) * (retry_count + 1))
|
122
|
+
puts "retry ##{retry_count} will start in #{wait}s" if debug
|
123
|
+
sleep wait
|
124
|
+
end
|
125
|
+
|
126
|
+
response
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.stubbornly_get(url, retry_limit: RETRY_LIMIT, recoverable_codes: RECOVERABLE_CODES, debug: false)
|
130
|
+
stubbornly(retry_limit: retry_limit, recoverable_codes: recoverable_codes, debug: debug) do
|
131
|
+
RestClient.get(url, REQUEST_HEADERS)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.stubbornly_post(url, payload, retry_limit: RETRY_LIMIT, recoverable_codes: RECOVERABLE_CODES, debug: false)
|
136
|
+
stubbornly(retry_limit: retry_limit, recoverable_codes: recoverable_codes, debug: debug) do
|
137
|
+
RestClient.post(url, payload, REQUEST_HEADERS)
|
138
|
+
end
|
139
|
+
end
|
74
140
|
end
|
@@ -30,15 +30,15 @@ module FacebookAds
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def create_ad_campaign(name:, objective:, status: 'ACTIVE')
|
33
|
-
raise
|
34
|
-
raise
|
33
|
+
raise ArgumentError, "Objective must be one of: #{AdCampaign::OBJECTIVES.join(', ')}" unless AdCampaign::OBJECTIVES.include?(objective)
|
34
|
+
raise ArgumentError, "Status must be one of: #{AdCampaign::STATUSES.join(', ')}" unless AdCampaign::STATUSES.include?(status)
|
35
35
|
query = { name: name, objective: objective, status: status }
|
36
36
|
result = AdCampaign.post("/#{id}/campaigns", query: query)
|
37
37
|
AdCampaign.find(result['id'])
|
38
38
|
end
|
39
39
|
|
40
40
|
def create_dynamic_ad_campaign(name:, product_catalog_id:, status: 'ACTIVE')
|
41
|
-
raise
|
41
|
+
raise ArgumentError, "Status must be one of: #{AdCampaign::STATUSES.join(', ')}" unless AdCampaign::STATUSES.include?(status)
|
42
42
|
query = { name: name, objective: 'PRODUCT_CATALOG_SALES', status: status, promoted_object: { product_catalog_id: product_catalog_id } }
|
43
43
|
result = AdCampaign.post("/#{id}/campaigns", query: query)
|
44
44
|
AdCampaign.find(result['id'])
|
@@ -73,7 +73,7 @@ module FacebookAds
|
|
73
73
|
end.to_h
|
74
74
|
|
75
75
|
response = AdImage.post("/#{id}/adimages", query: files)
|
76
|
-
files.
|
76
|
+
files.each_value { |file| File.delete(file.path) }
|
77
77
|
!response['images'].nil? ? ad_images(hashes: response['images'].map { |_key, hash| hash['hash'] }) : []
|
78
78
|
end
|
79
79
|
|
@@ -84,8 +84,9 @@ module FacebookAds
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def create_ad_creative(creative, creative_type: nil, carousel: false)
|
87
|
-
# Support old deprecated carousel param
|
87
|
+
# Support old deprecated carousel param.
|
88
88
|
return create_carousel_ad_creative(creative) if carousel
|
89
|
+
|
89
90
|
case creative_type
|
90
91
|
when 'carousel'
|
91
92
|
create_carousel_ad_creative(creative)
|
@@ -127,14 +128,11 @@ module FacebookAds
|
|
127
128
|
end
|
128
129
|
|
129
130
|
def reach_estimate(targeting:, optimization_goal:, currency: 'USD')
|
130
|
-
raise
|
131
|
+
raise ArgumentError, "Optimization goal must be one of: #{AdSet::OPTIMIZATION_GOALS.join(', ')}" unless AdSet::OPTIMIZATION_GOALS.include?(optimization_goal)
|
131
132
|
|
132
133
|
if targeting.is_a?(AdTargeting)
|
133
|
-
|
134
|
-
|
135
|
-
else
|
136
|
-
raise Exception, 'The provided targeting spec is not valid.'
|
137
|
-
end
|
134
|
+
raise ArgumentError, 'The provided targeting spec is not valid.' unless targeting.validate!
|
135
|
+
targeting = targeting.to_hash
|
138
136
|
end
|
139
137
|
|
140
138
|
query = {
|
@@ -147,14 +145,11 @@ module FacebookAds
|
|
147
145
|
end
|
148
146
|
|
149
147
|
def delivery_estimate(targeting:, optimization_goal:, currency: 'USD')
|
150
|
-
raise
|
148
|
+
raise ArgumentError, "Optimization goal must be one of: #{AdSet::OPTIMIZATION_GOALS.join(', ')}" unless AdSet::OPTIMIZATION_GOALS.include?(optimization_goal)
|
151
149
|
|
152
150
|
if targeting.is_a?(AdTargeting)
|
153
|
-
|
154
|
-
|
155
|
-
else
|
156
|
-
raise Exception, 'The provided targeting spec is not valid.'
|
157
|
-
end
|
151
|
+
raise ArgumentError, 'The provided targeting spec is not valid.' unless targeting.validate!
|
152
|
+
targeting = targeting.to_hash
|
158
153
|
end
|
159
154
|
|
160
155
|
query = {
|
@@ -172,12 +167,12 @@ module FacebookAds
|
|
172
167
|
required = %i[name page_id link message assets call_to_action_type multi_share_optimized multi_share_end_card]
|
173
168
|
|
174
169
|
unless (keys = required - creative.keys).length.zero?
|
175
|
-
raise
|
170
|
+
raise ArgumentError, "Creative is missing the following: #{keys.join(', ')}"
|
176
171
|
end
|
177
172
|
|
178
|
-
raise
|
173
|
+
raise ArgumentError, "Creative call_to_action_type must be one of: #{AdCreative::CALL_TO_ACTION_TYPES.join(', ')}" unless AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
|
179
174
|
|
180
|
-
query = if creative[:product_set_id]
|
175
|
+
query = if creative[:product_set_id]
|
181
176
|
AdCreative.product_set(
|
182
177
|
name: creative[:name],
|
183
178
|
page_id: creative[:page_id],
|
@@ -199,10 +194,10 @@ module FacebookAds
|
|
199
194
|
required = %i[name page_id message link link_title image_hash call_to_action_type]
|
200
195
|
|
201
196
|
unless (keys = required - creative.keys).length.zero?
|
202
|
-
raise
|
197
|
+
raise ArgumentError, "Creative is missing the following: #{keys.join(', ')}"
|
203
198
|
end
|
204
199
|
|
205
|
-
raise
|
200
|
+
raise ArgumentError, "Creative call_to_action_type must be one of: #{AdCreative::CALL_TO_ACTION_TYPES.join(', ')}" unless AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
|
206
201
|
query = AdCreative.photo(creative)
|
207
202
|
result = AdCreative.post("/#{id}/adcreatives", query: query)
|
208
203
|
AdCreative.find(result['id'])
|
@@ -212,7 +207,7 @@ module FacebookAds
|
|
212
207
|
required = %i[name title body object_url link_url image_hash page_id]
|
213
208
|
|
214
209
|
unless (keys = required - creative.keys).length.zero?
|
215
|
-
raise
|
210
|
+
raise ArgumentError, "Creative is missing the following: #{keys.join(', ')}"
|
216
211
|
end
|
217
212
|
|
218
213
|
query = AdCreative.link(creative)
|
@@ -14,13 +14,13 @@ module FacebookAds
|
|
14
14
|
# actions
|
15
15
|
|
16
16
|
def share(account_id)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
AdAccount.post(
|
18
|
+
"/#{id}/share_with_objects",
|
19
|
+
query: {
|
20
|
+
share_with_object_id: account_id,
|
21
|
+
share_with_object_type: 'Account'
|
22
|
+
}
|
23
|
+
)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -59,10 +59,10 @@ module FacebookAds
|
|
59
59
|
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
60
60
|
end
|
61
61
|
|
62
|
-
def create_ad_set(name:, promoted_object: {},
|
63
|
-
raise
|
64
|
-
raise
|
65
|
-
raise
|
62
|
+
def create_ad_set(name:, targeting:, optimization_goal:, promoted_object: {}, daily_budget: nil, lifetime_budget: nil, end_time: nil, billing_event: 'IMPRESSIONS', status: 'ACTIVE', bid_strategy: nil, bid_amount: nil)
|
63
|
+
raise ArgumentError, "Optimization goal must be one of: #{AdSet::OPTIMIZATION_GOALS.join(', ')}" unless AdSet::OPTIMIZATION_GOALS.include?(optimization_goal)
|
64
|
+
raise ArgumentError, "Billing event must be one of: #{AdSet::BILLING_EVENTS.join(', ')}" unless AdSet::BILLING_EVENTS.include?(billing_event)
|
65
|
+
raise ArgumentError, "Bid strategy must be one of: #{AdSet::BID_STRATEGIES.join(', ')}" unless AdSet::BID_STRATEGIES.include?(bid_strategy)
|
66
66
|
|
67
67
|
if targeting.is_a?(Hash)
|
68
68
|
# NOP
|
@@ -83,9 +83,9 @@ module FacebookAds
|
|
83
83
|
bid_amount: bid_amount
|
84
84
|
}
|
85
85
|
|
86
|
-
if daily_budget && lifetime_budget
|
87
|
-
|
88
|
-
|
86
|
+
raise ArgumentError 'Only one budget may be set between daily_budget and life_budget' if daily_budget && lifetime_budget
|
87
|
+
|
88
|
+
if daily_budget
|
89
89
|
query[:daily_budget] = daily_budget
|
90
90
|
elsif lifetime_budget
|
91
91
|
query[:lifetime_budget] = lifetime_budget
|
@@ -8,7 +8,7 @@ module FacebookAds
|
|
8
8
|
CALL_TO_ACTION_TYPES = %w[SHOP_NOW INSTALL_MOBILE_APP USE_MOBILE_APP SIGN_UP DOWNLOAD BUY_NOW NO_BUTTON].freeze
|
9
9
|
|
10
10
|
class << self
|
11
|
-
def photo(name:, page_id:,
|
11
|
+
def photo(name:, page_id:, message:, link:, link_title:, image_hash:, call_to_action_type:, instagram_actor_id: nil, app_link: nil, link_description: nil)
|
12
12
|
object_story_spec = {
|
13
13
|
'page_id' => page_id, # 300664329976860
|
14
14
|
'instagram_actor_id' => instagram_actor_id, # 503391023081924
|
@@ -36,7 +36,7 @@ module FacebookAds
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# https://developers.facebook.com/docs/marketing-api/guides/videoads
|
39
|
-
def carousel(name:, page_id:,
|
39
|
+
def carousel(name:, page_id:, link:, message:, assets:, call_to_action_type:, multi_share_optimized:, multi_share_end_card:, instagram_actor_id: nil, app_link: nil)
|
40
40
|
object_story_spec = {
|
41
41
|
'page_id' => page_id, # 300664329976860
|
42
42
|
'instagram_actor_id' => instagram_actor_id, # 503391023081924
|
@@ -8,7 +8,7 @@ module FacebookAds
|
|
8
8
|
|
9
9
|
class << self
|
10
10
|
def find(_id)
|
11
|
-
raise
|
11
|
+
raise NotImplementedError
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -21,7 +21,7 @@ module FacebookAds
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def update(_data)
|
24
|
-
raise
|
24
|
+
raise NotImplementedError
|
25
25
|
end
|
26
26
|
|
27
27
|
def destroy
|
@@ -10,16 +10,16 @@ module FacebookAds
|
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def find(_id)
|
13
|
-
raise
|
13
|
+
raise NotImplementedError
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
def update(_data)
|
18
|
-
raise
|
18
|
+
raise NotImplementedError
|
19
19
|
end
|
20
20
|
|
21
21
|
def destroy
|
22
|
-
raise
|
22
|
+
raise NotImplementedError
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
data/lib/facebook_ads/ad_set.rb
CHANGED
@@ -40,16 +40,14 @@ module FacebookAds
|
|
40
40
|
|
41
41
|
def validate!
|
42
42
|
{ gender: genders, countries: countries, user_os: user_os, user_device: user_device, custom_locations: custom_locations }.each_pair do |key, array|
|
43
|
-
if !array.nil? && !array.is_a?(Array)
|
44
|
-
raise Exception, "#{self.class.name}: #{key} must be an array"
|
45
|
-
end
|
43
|
+
raise ArgumentError, "#{self.class.name}: #{key} must be an array" if !array.nil? && !array.is_a?(Array)
|
46
44
|
end
|
47
45
|
|
48
46
|
{ genders: [genders, GENDERS], user_os: [user_os, OSES], user_device: [user_device, DEVICES] }.each_pair do |key, provided_and_acceptable|
|
49
47
|
provided, acceptable = provided_and_acceptable
|
50
48
|
|
51
49
|
if !provided.nil? && !(invalid = provided.detect { |value| !acceptable.include?(value) }).nil?
|
52
|
-
raise
|
50
|
+
raise ArgumentError, "#{self.class.name}: #{invalid} is an invalid #{key}"
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
data/lib/facebook_ads/base.rb
CHANGED
@@ -6,13 +6,13 @@ module FacebookAds
|
|
6
6
|
class << self
|
7
7
|
# HTTP verbs.
|
8
8
|
|
9
|
-
def get(path, query: {}
|
9
|
+
def get(path, objectify:, query: {})
|
10
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
14
|
response = begin
|
15
|
-
RestClient.get(uri,
|
15
|
+
RestClient.get(uri, FacebookAds::REQUEST_HEADERS)
|
16
16
|
rescue RestClient::Exception => e
|
17
17
|
exception(:get, path, e)
|
18
18
|
end
|
@@ -116,19 +116,12 @@ module FacebookAds
|
|
116
116
|
end
|
117
117
|
|
118
118
|
def unpack(response, objectify:)
|
119
|
-
raise
|
119
|
+
raise ArgumentError, 'Invalid nil response' if response.nil?
|
120
120
|
response = response.body if response.is_a?(RestClient::Response)
|
121
|
+
response = JSON.parse(response) if response.is_a?(String)
|
121
122
|
|
122
|
-
|
123
|
-
|
124
|
-
JSON.parse(response)
|
125
|
-
rescue JSON::ParserError
|
126
|
-
raise Exception, "Invalid JSON response: #{response.inspect}"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
raise Exception, "Invalid response: #{response.class.name} #{response.inspect}" unless response.is_a?(Hash)
|
131
|
-
raise Exception, "[#{response['error']['code']}] #{response['error']['message']} - raw response: #{response.inspect}" unless response['error'].nil?
|
123
|
+
raise ArgumentError, "Invalid response: #{response.class.name} #{response.inspect}" unless response.is_a?(Hash)
|
124
|
+
raise ArgumentError, "[#{response['error']['code']}] #{response['error']['message']} - raw response: #{response.inspect}" unless response['error'].nil?
|
132
125
|
return response unless objectify
|
133
126
|
|
134
127
|
if response.key?('data') && (data = response['data']).is_a?(Array)
|
@@ -138,8 +131,8 @@ module FacebookAds
|
|
138
131
|
end
|
139
132
|
end
|
140
133
|
|
141
|
-
def escape(
|
142
|
-
URI.encode_www_form_component(
|
134
|
+
def escape(str)
|
135
|
+
URI.encode_www_form_component(str)
|
143
136
|
end
|
144
137
|
|
145
138
|
# https://github.com/rack/rack/blob/master/lib/rack/utils.rb
|
@@ -190,7 +183,7 @@ module FacebookAds
|
|
190
183
|
def save
|
191
184
|
return nil if changes.nil? || changes.length.zero?
|
192
185
|
data = {}
|
193
|
-
changes.
|
186
|
+
changes.each_key { |key| data[key] = self[key] }
|
194
187
|
return nil unless update(data)
|
195
188
|
self.class.find(id)
|
196
189
|
end
|
@@ -70,19 +70,22 @@ describe FacebookAds::AdAccount do
|
|
70
70
|
describe '.create_ad_creative' do
|
71
71
|
it 'creates carousel ad creative', :vcr do
|
72
72
|
ad_images = account.create_ad_images(%w[https://img0.etsystatic.com/108/1/13006112/il_570xN.1047856494_l2gp.jpg https://img1.etsystatic.com/143/0/13344107/il_570xN.1141249285_xciv.jpg])
|
73
|
-
carousel_ad_creative = account.create_ad_creative(
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
73
|
+
carousel_ad_creative = account.create_ad_creative(
|
74
|
+
{
|
75
|
+
name: 'Test Carousel Creative',
|
76
|
+
page_id: '300664329976860', # Add your Facebook Page ID here.
|
77
|
+
link: 'http://play.google.com/store/apps/details?id=com.tophatter', # Add your Play Store ID here.
|
78
|
+
message: 'A message.',
|
79
|
+
assets: [
|
80
|
+
{ hash: ad_images[0].hash, title: 'Image #1 Title' },
|
81
|
+
{ hash: ad_images[1].hash, title: 'Image #2 Title' }
|
82
|
+
],
|
83
|
+
call_to_action_type: 'SHOP_NOW',
|
84
|
+
multi_share_optimized: true,
|
85
|
+
multi_share_end_card: false
|
86
|
+
},
|
87
|
+
creative_type: 'carousel'
|
88
|
+
)
|
86
89
|
expect(carousel_ad_creative.id).to eq('120330000008134415')
|
87
90
|
end
|
88
91
|
end
|
@@ -5,24 +5,64 @@ require 'spec_helper'
|
|
5
5
|
# rspec spec/facebook_ads/facebook_ads_spec.rb
|
6
6
|
describe FacebookAds do
|
7
7
|
describe '#base_uri' do
|
8
|
-
|
9
|
-
|
8
|
+
subject { FacebookAds.base_uri }
|
9
|
+
|
10
|
+
context 'when the version is not overridden' do
|
11
|
+
it { is_expected.to eq('https://graph.facebook.com/v2.9') }
|
10
12
|
end
|
11
13
|
|
12
|
-
|
13
|
-
FacebookAds.
|
14
|
-
|
14
|
+
context 'when the version is overridden' do
|
15
|
+
before { FacebookAds.api_version = '2.10' }
|
16
|
+
after { FacebookAds.api_version = '2.9' }
|
17
|
+
|
18
|
+
it { is_expected.to eq('https://graph.facebook.com/v2.10') }
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
18
22
|
describe '#api_version' do
|
19
|
-
|
20
|
-
|
23
|
+
subject { FacebookAds.api_version }
|
24
|
+
|
25
|
+
context 'when the version is not overridden' do
|
26
|
+
it { is_expected.to eq('2.9') }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when the version is overridden' do
|
30
|
+
before { FacebookAds.api_version = '2.10' }
|
31
|
+
after { FacebookAds.api_version = '2.9' }
|
32
|
+
|
33
|
+
it { is_expected.to eq('2.10') }
|
21
34
|
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#stubbornly' do
|
38
|
+
subject { FacebookAds.stubbornly { true } }
|
39
|
+
|
40
|
+
it { is_expected.to eq(true) }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#stubbornly_get', :vcr do
|
44
|
+
subject(:response) { FacebookAds.stubbornly_get('http://worldtimeapi.org/api/ip') }
|
45
|
+
|
46
|
+
let(:parsed_response) { JSON.parse(response) }
|
22
47
|
|
23
|
-
it '
|
24
|
-
|
25
|
-
|
48
|
+
it 'executes the request for the provided url' do
|
49
|
+
expect(parsed_response).to eq(
|
50
|
+
'abbreviation' => 'AKDT',
|
51
|
+
'client_ip' => '66.58.139.118',
|
52
|
+
'datetime' => '2020-09-29T11:59:09.743279-08:00',
|
53
|
+
'day_of_week' => 2,
|
54
|
+
'day_of_year' => 273,
|
55
|
+
'dst' => true,
|
56
|
+
'dst_from' => '2020-03-08T11:00:00+00:00',
|
57
|
+
'dst_offset' => 3600,
|
58
|
+
'dst_until' => '2020-11-01T10:00:00+00:00',
|
59
|
+
'raw_offset' => -32_400,
|
60
|
+
'timezone' => 'America/Anchorage',
|
61
|
+
'unixtime' => 1_601_409_549,
|
62
|
+
'utc_datetime' => '2020-09-29T19:59:09.743279+00:00',
|
63
|
+
'utc_offset' => '-08:00',
|
64
|
+
'week_number' => 40
|
65
|
+
)
|
26
66
|
end
|
27
67
|
end
|
28
68
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: http://worldtimeapi.org/api/ip
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- application/json
|
12
|
+
Accept-Encoding:
|
13
|
+
- identity
|
14
|
+
User-Agent:
|
15
|
+
- Ruby
|
16
|
+
Host:
|
17
|
+
- worldtimeapi.org
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 200
|
21
|
+
message: OK
|
22
|
+
headers:
|
23
|
+
Connection:
|
24
|
+
- keep-alive
|
25
|
+
Access-Control-Allow-Credentials:
|
26
|
+
- 'true'
|
27
|
+
Access-Control-Allow-Origin:
|
28
|
+
- "*"
|
29
|
+
Access-Control-Expose-Headers:
|
30
|
+
- ''
|
31
|
+
Cache-Control:
|
32
|
+
- max-age=0, private, must-revalidate
|
33
|
+
Content-Length:
|
34
|
+
- '401'
|
35
|
+
Content-Type:
|
36
|
+
- application/json; charset=utf-8
|
37
|
+
Cross-Origin-Window-Policy:
|
38
|
+
- deny
|
39
|
+
Date:
|
40
|
+
- Tue, 29 Sep 2020 19:59:09 GMT
|
41
|
+
Server:
|
42
|
+
- Cowboy
|
43
|
+
X-Content-Type-Options:
|
44
|
+
- nosniff
|
45
|
+
X-Download-Options:
|
46
|
+
- noopen
|
47
|
+
X-Frame-Options:
|
48
|
+
- SAMEORIGIN
|
49
|
+
X-Permitted-Cross-Domain-Policies:
|
50
|
+
- none
|
51
|
+
X-Request-Id:
|
52
|
+
- 28a7b795-69b3-423c-b539-08675ebfa8f9
|
53
|
+
X-Runtime:
|
54
|
+
- 16ms
|
55
|
+
X-Xss-Protection:
|
56
|
+
- 1; mode=block
|
57
|
+
Via:
|
58
|
+
- 1.1 vegur
|
59
|
+
body:
|
60
|
+
encoding: UTF-8
|
61
|
+
string: '{"abbreviation":"AKDT","client_ip":"66.58.139.118","datetime":"2020-09-29T11:59:09.743279-08:00","day_of_week":2,"day_of_year":273,"dst":true,"dst_from":"2020-03-08T11:00:00+00:00","dst_offset":3600,"dst_until":"2020-11-01T10:00:00+00:00","raw_offset":-32400,"timezone":"America/Anchorage","unixtime":1601409549,"utc_datetime":"2020-09-29T19:59:09.743279+00:00","utc_offset":"-08:00","week_number":40}'
|
62
|
+
recorded_at: Tue, 29 Sep 2020 19:59:09 GMT
|
63
|
+
recorded_with: VCR 6.0.0
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'coveralls'
|
4
|
-
Coveralls.wear!
|
5
|
-
|
6
3
|
require 'awesome_print'
|
7
4
|
require 'facebook_ads'
|
8
5
|
|
9
|
-
Dir[File.expand_path(
|
6
|
+
Dir[File.expand_path('support/*.rb', __dir__)].sort.each { |f| require f }
|
data/spec/support/rest_client.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: facebook_ads
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Estreich
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rest-client
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -47,11 +47,12 @@ extensions: []
|
|
47
47
|
extra_rdoc_files:
|
48
48
|
- README.md
|
49
49
|
files:
|
50
|
-
- ".
|
50
|
+
- ".circleci/config.yml"
|
51
51
|
- ".gitignore"
|
52
52
|
- ".rspec"
|
53
53
|
- ".rubocop.yml"
|
54
|
-
- ".
|
54
|
+
- ".ruby-gemset"
|
55
|
+
- ".ruby-version"
|
55
56
|
- CHANGELOG.md
|
56
57
|
- Gemfile
|
57
58
|
- LICENSE.md
|
@@ -81,16 +82,6 @@ files:
|
|
81
82
|
- lib/facebook_ads/base.rb
|
82
83
|
- spec/facebook_ads/ad_account_spec.rb
|
83
84
|
- spec/facebook_ads/ad_campaign_spec.rb
|
84
|
-
- spec/facebook_ads/ad_creative_spec.rb
|
85
|
-
- spec/facebook_ads/ad_image_spec.rb
|
86
|
-
- spec/facebook_ads/ad_insight_spec.rb
|
87
|
-
- spec/facebook_ads/ad_product_catalog_spec.rb
|
88
|
-
- spec/facebook_ads/ad_product_feed_spec.rb
|
89
|
-
- spec/facebook_ads/ad_product_set_spec.rb
|
90
|
-
- spec/facebook_ads/ad_product_spec.rb
|
91
|
-
- spec/facebook_ads/ad_set_activity_spec.rb
|
92
|
-
- spec/facebook_ads/ad_set_spec.rb
|
93
|
-
- spec/facebook_ads/ad_spec.rb
|
94
85
|
- spec/facebook_ads/ad_targeting_spec.rb
|
95
86
|
- spec/facebook_ads/facebook_ads_spec.rb
|
96
87
|
- spec/fixtures/approvals/facebookads_adaccount/ad_campaigns/lists_campaigns.approved.json
|
@@ -103,6 +94,7 @@ files:
|
|
103
94
|
- spec/fixtures/approvals/facebookads_adaccount/create_ad_images/creates_an_image.approved.json
|
104
95
|
- spec/fixtures/approvals/facebookads_adaccount/find_by/finds_a_specific_account.approved.json
|
105
96
|
- spec/fixtures/approvals/facebookads_adaccount/reach_estimate/estimates_the_reach_of_a_targeting_spec.approved.json
|
97
|
+
- spec/fixtures/cassettes/FacebookAds/_stubbornly_get/executes_the_request_for_the_provided_url.yml
|
106
98
|
- spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_campaigns/lists_campaigns.yml
|
107
99
|
- spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_creatives/lists_creatives.yml
|
108
100
|
- spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_images/lists_images.yml
|
@@ -125,7 +117,7 @@ homepage: https://github.com/tophatter/facebook-ruby-ads-sdk
|
|
125
117
|
licenses:
|
126
118
|
- MIT
|
127
119
|
metadata: {}
|
128
|
-
post_install_message:
|
120
|
+
post_install_message:
|
129
121
|
rdoc_options: []
|
130
122
|
require_paths:
|
131
123
|
- lib
|
@@ -133,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
125
|
requirements:
|
134
126
|
- - ">="
|
135
127
|
- !ruby/object:Gem::Version
|
136
|
-
version: '
|
128
|
+
version: '2.5'
|
137
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
130
|
requirements:
|
139
131
|
- - ">="
|
@@ -141,22 +133,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
133
|
version: '0'
|
142
134
|
requirements: []
|
143
135
|
rubygems_version: 3.1.2
|
144
|
-
signing_key:
|
136
|
+
signing_key:
|
145
137
|
specification_version: 4
|
146
138
|
summary: Facebook Marketing API SDK for Ruby.
|
147
139
|
test_files:
|
148
140
|
- spec/facebook_ads/ad_account_spec.rb
|
149
141
|
- spec/facebook_ads/ad_campaign_spec.rb
|
150
|
-
- spec/facebook_ads/ad_creative_spec.rb
|
151
|
-
- spec/facebook_ads/ad_image_spec.rb
|
152
|
-
- spec/facebook_ads/ad_insight_spec.rb
|
153
|
-
- spec/facebook_ads/ad_product_catalog_spec.rb
|
154
|
-
- spec/facebook_ads/ad_product_feed_spec.rb
|
155
|
-
- spec/facebook_ads/ad_product_set_spec.rb
|
156
|
-
- spec/facebook_ads/ad_product_spec.rb
|
157
|
-
- spec/facebook_ads/ad_set_activity_spec.rb
|
158
|
-
- spec/facebook_ads/ad_set_spec.rb
|
159
|
-
- spec/facebook_ads/ad_spec.rb
|
160
142
|
- spec/facebook_ads/ad_targeting_spec.rb
|
161
143
|
- spec/facebook_ads/facebook_ads_spec.rb
|
162
144
|
- spec/fixtures/approvals/facebookads_adaccount/ad_campaigns/lists_campaigns.approved.json
|
@@ -169,6 +151,7 @@ test_files:
|
|
169
151
|
- spec/fixtures/approvals/facebookads_adaccount/create_ad_images/creates_an_image.approved.json
|
170
152
|
- spec/fixtures/approvals/facebookads_adaccount/find_by/finds_a_specific_account.approved.json
|
171
153
|
- spec/fixtures/approvals/facebookads_adaccount/reach_estimate/estimates_the_reach_of_a_targeting_spec.approved.json
|
154
|
+
- spec/fixtures/cassettes/FacebookAds/_stubbornly_get/executes_the_request_for_the_provided_url.yml
|
172
155
|
- spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_campaigns/lists_campaigns.yml
|
173
156
|
- spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_creatives/lists_creatives.yml
|
174
157
|
- spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_images/lists_images.yml
|
data/.coveralls.yml
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
service_name: travis-ci
|
data/.travis.yml
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe FacebookAds::AdProductCatalog do
|
6
|
-
# def test_all
|
7
|
-
# vcr do
|
8
|
-
# catalog = FacebookAds::AdProductCatalog.all.first
|
9
|
-
# assert_equal '197871307315718', catalog.id
|
10
|
-
# assert_equal 'Test', catalog.name
|
11
|
-
# assert_equal 'commerce', catalog.vertical
|
12
|
-
# assert_equal 1, catalog.product_count
|
13
|
-
# assert_equal 1, catalog.feed_count
|
14
|
-
# end
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# def test_create
|
18
|
-
# vcr do
|
19
|
-
# catalog = FacebookAds::AdProductCatalog.create(name: 'Foo Bar')
|
20
|
-
# assert_equal 'Foo Bar', catalog.name
|
21
|
-
# assert_equal 'commerce', catalog.vertical
|
22
|
-
# assert_equal 0, catalog.product_count
|
23
|
-
# assert_equal 0, catalog.feed_count
|
24
|
-
# catalog = FacebookAds::AdProductCatalog.find(catalog.id)
|
25
|
-
# assert_equal true, catalog.destroy
|
26
|
-
# assert_nil FacebookAds::AdProductCatalog.find_by(name: 'Foo Bar')
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe FacebookAds::AdProduct do
|
6
|
-
# def test_list
|
7
|
-
# vcr do
|
8
|
-
# catalog = FacebookAds::AdProductCatalog.find_by(name: 'Test')
|
9
|
-
# products = catalog.ad_products
|
10
|
-
# product = products.first
|
11
|
-
# assert_equal '1489112071104048', product.id
|
12
|
-
# assert_equal 'retro-ruby-earrings', product.retailer_id
|
13
|
-
# assert_equal 'Precious Retro .925 Sterling Silver Artificial Ruby Earrings', product.name
|
14
|
-
# assert_equal '$5 shipping - at your doorstep within 25-27 business days.', product.description
|
15
|
-
# assert_equal 'Adoration-Precious', product.brand
|
16
|
-
# assert_equal 'Jewelry | Earrings', product.category
|
17
|
-
# assert_equal 'USD', product.currency
|
18
|
-
# assert_equal '$156.00', product.price
|
19
|
-
# assert_equal 'https://external.xx.fbcdn.net/safe_image.php?d=AQALnOfu1mhkpb4T&url=https%3A%2F%2Fd38eepresuu519.cloudfront.net%2F12c85243d743ac3cb21b71c34192b252%2Flarge.jpg&cfs=1&_nc_hash=AQBlxlDCq59tO6sE', product.image_url
|
20
|
-
# assert_equal 'https://tophatter.com/lots/23275728', product.url
|
21
|
-
# end
|
22
|
-
# end
|
23
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe FacebookAds::AdSet do
|
6
|
-
# def test_create
|
7
|
-
# vcr do
|
8
|
-
# ad_campaign = FacebookAds::AdCampaign.find('6069436026057')
|
9
|
-
# ad_set = ad_campaign.create_ad_set(
|
10
|
-
# name: 'Test',
|
11
|
-
# promoted_object: {
|
12
|
-
# pixel_id: '1472889202927380',
|
13
|
-
# custom_event_type: 'PURCHASE'
|
14
|
-
# },
|
15
|
-
# targeting: {
|
16
|
-
# age_max: 65,
|
17
|
-
# age_min: 18,
|
18
|
-
# app_install_state: 'not_installed',
|
19
|
-
# excluded_custom_audiences: [{ "name"=>"All-Users-2016-07-28", "id"=>"6068994792257" }],
|
20
|
-
# genders: [2],
|
21
|
-
# geo_locations: { countries: %w(US), location_types: %w(home recent) },
|
22
|
-
# locales: [24, 6],
|
23
|
-
# publisher_platforms: %w(facebook instagram audience_network),
|
24
|
-
# device_platforms: %w(desktop),
|
25
|
-
# facebook_positions: %w(feed right_hand_column)
|
26
|
-
# },
|
27
|
-
# daily_budget: 1000,
|
28
|
-
# optimization_goal: 'OFFSITE_CONVERSIONS'
|
29
|
-
# )
|
30
|
-
# assert_equal '6081945233457', ad_set.id
|
31
|
-
# assert_equal '1000', ad_set.daily_budget
|
32
|
-
# assert_equal 'OFFSITE_CONVERSIONS', ad_set.optimization_goal
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# def test_update
|
37
|
-
# vcr do
|
38
|
-
# ad_set = FacebookAds::AdSet.find('6078462927057')
|
39
|
-
# assert ad_set.update(daily_budget: 149900)
|
40
|
-
# end
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# def test_destroy
|
44
|
-
# vcr do
|
45
|
-
# ad_set = FacebookAds::AdSet.find('6081945233457')
|
46
|
-
# assert ad_set.destroy
|
47
|
-
# end
|
48
|
-
# end
|
49
|
-
end
|