facebook_ads 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +23 -0
  4. data/.travis.yml +1 -1
  5. data/Gemfile +4 -5
  6. data/Gemfile.lock +34 -82
  7. data/{README.markdown → README.md} +43 -10
  8. data/Rakefile +6 -0
  9. data/bin/console +15 -1
  10. data/facebook_ads.gemspec +11 -13
  11. data/lib/facebook_ads.rb +11 -7
  12. data/lib/facebook_ads/ad.rb +6 -8
  13. data/lib/facebook_ads/ad_account.rb +65 -54
  14. data/lib/facebook_ads/ad_audience.rb +22 -0
  15. data/lib/facebook_ads/ad_campaign.rb +21 -17
  16. data/lib/facebook_ads/ad_creative.rb +7 -9
  17. data/lib/facebook_ads/ad_image.rb +3 -5
  18. data/lib/facebook_ads/ad_insight.rb +4 -5
  19. data/lib/facebook_ads/ad_product.rb +6 -0
  20. data/lib/facebook_ads/ad_product_catalog.rb +60 -0
  21. data/lib/facebook_ads/ad_product_feed.rb +6 -0
  22. data/lib/facebook_ads/ad_product_set.rb +6 -0
  23. data/lib/facebook_ads/ad_set.rb +9 -11
  24. data/lib/facebook_ads/ad_targeting.rb +19 -21
  25. data/lib/facebook_ads/base.rb +107 -62
  26. data/test/ad_account_test.rb +27 -0
  27. data/test/ad_campaign_test.rb +32 -0
  28. data/test/ad_creative_test.rb +7 -0
  29. data/test/ad_image_test.rb +27 -0
  30. data/test/ad_insight_test.rb +7 -0
  31. data/test/ad_product_catalog_test.rb +28 -0
  32. data/test/ad_product_feed_test.rb +7 -0
  33. data/test/ad_product_set_test.rb +7 -0
  34. data/test/ad_product_test.rb +22 -0
  35. data/test/ad_set_test.rb +7 -0
  36. data/test/ad_targeting_test.rb +7 -0
  37. data/test/ad_test.rb +7 -0
  38. data/test/facebook_ads_test.rb +8 -0
  39. data/test/test_helper.rb +64 -0
  40. data/test/vcr_cassettes/AdAccountTest-test_all.yml +70 -0
  41. data/test/vcr_cassettes/AdAccountTest-test_applications.yml +130 -0
  42. data/test/vcr_cassettes/AdAccountTest-test_find_by.yml +71 -0
  43. data/test/vcr_cassettes/AdCampaignTest-test_create.yml +295 -0
  44. data/test/vcr_cassettes/AdCampaignTest-test_list.yml +133 -0
  45. data/test/vcr_cassettes/AdImageTest-test_create.yml +2963 -0
  46. data/test/vcr_cassettes/AdImageTest-test_list.yml +137 -0
  47. data/test/vcr_cassettes/AdProductCatalogTest-test_all.yml +60 -0
  48. data/test/vcr_cassettes/AdProductCatalogTest-test_create.yml +256 -0
  49. data/test/vcr_cassettes/AdProductTest-test_list.yml +130 -0
  50. metadata +44 -49
  51. data/spec/ad_account_spec.rb +0 -78
  52. data/spec/ad_campaign_spec.rb +0 -13
  53. data/spec/ad_creative_spec.rb +0 -14
  54. data/spec/ad_image_spec.rb +0 -11
  55. data/spec/ad_insight_spec.rb +0 -11
  56. data/spec/ad_set_spec.rb +0 -13
  57. data/spec/ad_spec.rb +0 -13
  58. data/spec/ad_targeting_spec.rb +0 -4
  59. data/spec/spec_helper.rb +0 -15
  60. data/spec/support/fixtures.sh +0 -17
  61. data/spec/support/fixtures/6057330925170.json +0 -1
  62. data/spec/support/fixtures/6057810634370.json +0 -1
  63. data/spec/support/fixtures/6057810946970.json +0 -1
  64. data/spec/support/fixtures/6057824295570.json +0 -1
  65. data/spec/support/fixtures/act_861827983860489.json +0 -1
  66. data/spec/support/fixtures/act_861827983860489/adcreatives.json +0 -1
  67. data/spec/support/fixtures/act_861827983860489/adimages.json +0 -1
  68. data/spec/support/fixtures/act_861827983860489/ads.json +0 -1
  69. data/spec/support/fixtures/act_861827983860489/adsets.json +0 -1
  70. data/spec/support/fixtures/act_861827983860489/campaigns.json +0 -1
  71. data/spec/support/fixtures/me/adaccounts.json +0 -1
  72. data/spec/support/rack_facebook.rb +0 -22
data/lib/facebook_ads.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # External requires.
2
- require 'active_support/all'
3
- require 'httmultiparty'
2
+ require 'json'
3
+ require 'rest-client'
4
4
  require 'hashie'
5
+ require 'logger'
5
6
 
6
7
  # Internal requires.
7
8
  require 'facebook_ads/base'
@@ -9,7 +10,6 @@ Dir[File.expand_path('../facebook_ads/*.rb', __FILE__)].each { |f| require f }
9
10
 
10
11
  # The primary namespace for this gem.
11
12
  module FacebookAds
12
-
13
13
  def self.logger=(logger)
14
14
  @logger = logger
15
15
  end
@@ -28,10 +28,7 @@ module FacebookAds
28
28
  end
29
29
 
30
30
  def self.base_uri
31
- unless defined?(@base_uri)
32
- @base_uri = 'https://graph.facebook.com/v2.6'
33
- end
34
-
31
+ @base_uri = 'https://graph.facebook.com/v2.8' unless defined?(@base_uri)
35
32
  @base_uri
36
33
  end
37
34
 
@@ -43,4 +40,11 @@ module FacebookAds
43
40
  @access_token
44
41
  end
45
42
 
43
+ def self.business_id=(business_id)
44
+ @business_id = business_id
45
+ end
46
+
47
+ def self.business_id
48
+ @business_id
49
+ end
46
50
  end
@@ -2,33 +2,31 @@ module FacebookAds
2
2
  # An ad belongs to an ad set. It is created using an ad creative.
3
3
  # https://developers.facebook.com/docs/marketing-api/reference/adgroup
4
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)
5
+ 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).freeze
6
+ STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED).freeze
8
7
 
9
8
  # belongs_to ad_account
10
9
 
11
10
  def ad_account
12
- @ad_set ||= FacebookAds::AdAccount.find(account_id)
11
+ @ad_set ||= AdAccount.find(account_id)
13
12
  end
14
13
 
15
14
  # belongs_to ad_campaign
16
15
 
17
16
  def ad_campaign
18
- @ad_set ||= FacebookAds::AdCampaign.find(campaign_id)
17
+ @ad_set ||= AdCampaign.find(campaign_id)
19
18
  end
20
19
 
21
20
  # belongs_to ad_set
22
21
 
23
22
  def ad_set
24
- @ad_set ||= FacebookAds::AdSet.find(adset_id)
23
+ @ad_set ||= AdSet.find(adset_id)
25
24
  end
26
25
 
27
26
  # belongs_to ad_creative
28
27
 
29
28
  def ad_creative
30
- @ad_creative ||= FacebookAds::AdCreative.find(creative['id'])
29
+ @ad_creative ||= AdCreative.find(creative['id'])
31
30
  end
32
-
33
31
  end
34
32
  end
@@ -2,8 +2,7 @@ module FacebookAds
2
2
  # An ad account has many ad campaigns, ad images, and ad creatives.
3
3
  # https://developers.facebook.com/docs/marketing-api/reference/ad-account
4
4
  class AdAccount < Base
5
-
6
- FIELDS = %w(id account_id account_status age created_time currency name last_used_time)
5
+ FIELDS = %w(id account_id account_status age created_time currency name).freeze
7
6
 
8
7
  class << self
9
8
  def all
@@ -11,9 +10,9 @@ module FacebookAds
11
10
  end
12
11
 
13
12
  def find_by(conditions)
14
- all.detect do |ad_account|
13
+ all.detect do |object|
15
14
  conditions.all? do |key, value|
16
- ad_account.send(key) == value
15
+ object.send(key) == value
17
16
  end
18
17
  end
19
18
  end
@@ -22,90 +21,63 @@ module FacebookAds
22
21
  # has_many campaigns
23
22
 
24
23
  def ad_campaigns(effective_status: ['ACTIVE'], limit: 100)
25
- FacebookAds::AdCampaign.paginate("/#{id}/campaigns", query: { effective_status: effective_status, limit: limit })
24
+ AdCampaign.paginate("/#{id}/campaigns", query: { effective_status: effective_status, limit: limit })
26
25
  end
27
26
 
28
27
  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)
28
+ raise Exception, "Objective must be one of: #{AdCampaign::OBJECTIVES.to_sentence}" unless AdCampaign::OBJECTIVES.include?(objective)
29
+ raise Exception, "Status must be one of: #{AdCampaign::STATUSES.to_sentence}" unless AdCampaign::STATUSES.include?(status)
30
+ campaign = AdCampaign.post("/#{id}/campaigns", query: { name: name, objective: objective, status: status }, objectify: true)
31
+ AdCampaign.find(campaign.id)
33
32
  end
34
33
 
35
34
  # has_many ad_images
36
35
 
37
36
  def ad_images(hashes: nil, limit: 100)
38
- if hashes.present?
39
- FacebookAds::AdImage.get("/#{id}/adimages", query: { hashes: hashes }, objectify: true)
37
+ if !hashes.nil?
38
+ AdImage.get("/#{id}/adimages", query: { hashes: hashes }, objectify: true)
40
39
  else
41
- FacebookAds::AdImage.paginate("/#{id}/adimages", query: { limit: limit })
40
+ AdImage.paginate("/#{id}/adimages", query: { limit: limit })
42
41
  end
43
42
  end
44
43
 
45
44
  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)]
45
+ files = urls.map do |url|
46
+ name, path = download(url)
47
+ [name, File.open(path)]
55
48
  end.to_h
56
49
 
57
- response = FacebookAds::AdImage.post("/#{id}/adimages", query: files, objectify: false)
50
+ response = AdImage.post("/#{id}/adimages", query: files, objectify: false)
58
51
  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
52
+ !response['images'].nil? ? ad_images(hashes: response['images'].map { |_key, hash| hash['hash'] }) : []
66
53
  end
67
54
 
68
55
  # has_many ad_creatives
69
56
 
70
57
  def ad_creatives(limit: 100)
71
- FacebookAds::AdCreative.paginate("/#{id}/adcreatives", query: { limit: limit })
58
+ AdCreative.paginate("/#{id}/adcreatives", query: { limit: limit })
72
59
  end
73
60
 
74
61
  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)
62
+ carousel ? create_carousel_ad_creative(creative) : create_image_ad_creative(creative)
97
63
  end
98
64
 
99
65
  # has_many ad_sets
100
66
 
101
67
  def ad_sets(effective_status: ['ACTIVE'], limit: 100)
102
- FacebookAds::AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
68
+ AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
103
69
  end
104
70
 
105
71
  # has_many ads
106
72
 
107
73
  def ads(effective_status: ['ACTIVE'], limit: 100)
108
- FacebookAds::Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
74
+ Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
75
+ end
76
+
77
+ # has_many ad_audiences
78
+
79
+ def ad_audiences
80
+ AdAudience.paginate("/#{id}/customaudiences")
109
81
  end
110
82
 
111
83
  # has_many ad_insights
@@ -122,5 +94,44 @@ module FacebookAds
122
94
  self.class.get("/#{id}/advertisable_applications", objectify: false)
123
95
  end
124
96
 
97
+ private
98
+
99
+ def create_carousel_ad_creative(creative)
100
+ required = %i(name page_id link message assets call_to_action_type multi_share_optimized multi_share_end_card)
101
+
102
+ unless (keys = required - creative.keys).length.zero?
103
+ raise Exception, "Creative is missing the following: #{keys.to_sentence}"
104
+ end
105
+
106
+ raise Exception, "Creative call_to_action_type must be one of: #{AdCreative::CALL_TO_ACTION_TYPES.to_sentence}" unless AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
107
+ query = AdCreative.carousel(creative)
108
+ creative = AdCreative.post("/#{id}/adcreatives", query: query, objectify: true) # Returns an AdCreative instance.
109
+ AdCreative.find(creative.id)
110
+ end
111
+
112
+ def create_image_ad_creative(creative)
113
+ required = %i(name page_id message link link_title image_hash call_to_action_type)
114
+
115
+ unless (keys = required - creative.keys).length.zero?
116
+ raise Exception, "Creative is missing the following: #{keys.to_sentence}"
117
+ end
118
+
119
+ raise Exception, "Creative call_to_action_type must be one of: #{AdCreative::CALL_TO_ACTION_TYPES.to_sentence}" unless AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
120
+ query = AdCreative.photo(creative)
121
+ creative = AdCreative.post("/#{id}/adcreatives", query: query, objectify: true) # Returns an AdCreative instance.
122
+ AdCreative.find(creative.id)
123
+ end
124
+
125
+ def download(url)
126
+ pathname = Pathname.new(url)
127
+ name = "#{pathname.dirname.basename}.jpg"
128
+ # @FIXME: Need to handle exception here.
129
+ data = RestClient.get(url).body
130
+ file = File.open("/tmp/#{name}", 'w') # Assume *nix-based system.
131
+ file.binmode
132
+ file.write(data)
133
+ file.close
134
+ [name, file.path]
135
+ end
125
136
  end
126
137
  end
@@ -0,0 +1,22 @@
1
+ module FacebookAds
2
+ # https://developers.facebook.com/docs/marketing-api/reference/custom-audience
3
+ class AdAudience < Base
4
+ FIELDS = %w(id account_id subtype name description approximate_count data_source delivery_status external_event_source lookalike_audience_ids lookalike_spec operation_status opt_out_link permission_for_actions pixel_id retention_days rule time_content_updated time_created time_updated).freeze
5
+
6
+ # belongs_to ad_account
7
+
8
+ def ad_account
9
+ @ad_account ||= AdAccount.find("act_#{account_id}")
10
+ end
11
+
12
+ # actions
13
+
14
+ def share(account_id)
15
+ query = {
16
+ share_with_object_id: account_id,
17
+ share_with_object_type: 'Account'
18
+ }
19
+ AdAccount.post("/#{id}/share_with_objects", query: query, objectify: false)
20
+ end
21
+ end
22
+ end
@@ -2,53 +2,57 @@ module FacebookAds
2
2
  # An ad campaign has many ad sets and belongs to an ad account.
3
3
  # https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group
4
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)
5
+ 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).freeze
6
+ STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED).freeze
7
+ OBJECTIVES = %w(CONVERSIONS MOBILE_APP_INSTALLS).freeze
9
8
 
10
9
  # belongs_to ad_account
11
10
 
12
11
  def ad_account
13
- @ad_account ||= FacebookAds::AdAccount.find("act_#{account_id}")
12
+ @ad_account ||= AdAccount.find("act_#{account_id}")
14
13
  end
15
14
 
16
15
  # has_many ad_sets
17
16
 
18
17
  def ad_sets(effective_status: ['ACTIVE'], limit: 100)
19
- FacebookAds::AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
18
+ AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
20
19
  end
21
20
 
22
21
  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)
22
+ raise Exception, "Optimization goal must be one of: #{AdSet::OPTIMIZATION_GOALS.to_sentence}" unless AdSet::OPTIMIZATION_GOALS.include?(optimization_goal)
23
+ raise Exception, "Billing event must be one of: #{AdSet::BILLING_EVENTS.to_sentence}" unless AdSet::BILLING_EVENTS.include?(billing_event)
25
24
 
26
- targeting.validate! # Will raise if invalid.
25
+ if targeting.is_a?(Hash)
26
+ # NOP
27
+ else
28
+ targeting.validate! # Will raise if invalid.
29
+ targeting = targeting.to_hash
30
+ end
27
31
 
28
- ad_set = FacebookAds::AdSet.post("/act_#{account_id}/adsets", query: { # Returns a FacebookAds::AdSet instance.
32
+ query = {
29
33
  campaign_id: id,
30
34
  name: name,
31
- targeting: targeting.to_hash.to_json,
35
+ targeting: targeting.to_json,
32
36
  promoted_object: promoted_object.to_json,
33
37
  optimization_goal: optimization_goal,
34
38
  daily_budget: daily_budget,
35
39
  billing_event: billing_event,
36
40
  status: status,
37
41
  is_autobid: is_autobid
38
- }, objectify: true)
39
-
40
- FacebookAds::AdSet.find(ad_set.id)
42
+ }
43
+ ad_set = AdSet.post("/act_#{account_id}/adsets", query: query, objectify: true) # Returns an AdSet instance.
44
+ AdSet.find(ad_set.id)
41
45
  end
42
46
 
43
47
  # has_many ad_insights
44
48
 
45
49
  def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
46
- FacebookAds::AdInsight.paginate("/#{id}/insights", query: {
50
+ query = {
47
51
  level: level,
48
52
  time_increment: time_increment,
49
53
  time_range: { 'since': range.first.to_s, 'until': range.last.to_s }
50
- })
54
+ }
55
+ AdInsight.paginate("/#{id}/insights", query: query)
51
56
  end
52
-
53
57
  end
54
58
  end
@@ -2,12 +2,10 @@ module FacebookAds
2
2
  # Ad ad creative has many ad images and belongs to an ad account.
3
3
  # https://developers.facebook.com/docs/marketing-api/reference/ad-creative
4
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)
5
+ FIELDS = %w(id name object_story_id object_story_spec object_type thumbnail_url run_status).freeze
6
+ CALL_TO_ACTION_TYPES = %w(SHOP_NOW INSTALL_MOBILE_APP USE_MOBILE_APP SIGN_UP DOWNLOAD BUY_NOW).freeze
8
7
 
9
8
  class << self
10
-
11
9
  def photo(name:, page_id:, instagram_actor_id: nil, message:, link:, link_title:, image_hash:, call_to_action_type:)
12
10
  object_story_spec = {
13
11
  'page_id' => page_id, # 300664329976860
@@ -26,13 +24,14 @@ module FacebookAds
26
24
  }
27
25
  }
28
26
  }
27
+
29
28
  {
30
29
  name: name,
31
30
  object_story_spec: object_story_spec.to_json
32
31
  }
33
32
  end
34
33
 
35
- # https://developers.facebook.com/docs/marketing-api/guides/carousel-ads/v2.6
34
+ # https://developers.facebook.com/docs/marketing-api/guides/videoads
36
35
  def carousel(name:, page_id:, instagram_actor_id: nil, link:, message:, assets:, call_to_action_type:, multi_share_optimized:, multi_share_end_card:)
37
36
  object_story_spec = {
38
37
  'page_id' => page_id, # 300664329976860
@@ -41,7 +40,7 @@ module FacebookAds
41
40
  'link' => link, # https://tophatter.com/, https://itunes.apple.com/app/id619460348, http://play.google.com/store/apps/details?id=com.tophatter
42
41
  'message' => message,
43
42
  'call_to_action' => { 'type' => call_to_action_type },
44
- 'child_attachments' => assets.collect { |asset|
43
+ 'child_attachments' => assets.collect do |asset|
45
44
  {
46
45
  'link' => link,
47
46
  'image_hash' => asset[:hash],
@@ -49,18 +48,17 @@ module FacebookAds
49
48
  # 'description' => asset[:title],
50
49
  'call_to_action' => { 'type' => call_to_action_type } # Redundant?
51
50
  }
52
- },
51
+ end,
53
52
  'multi_share_optimized' => multi_share_optimized,
54
53
  'multi_share_end_card' => multi_share_end_card
55
54
  }
56
55
  }
56
+
57
57
  {
58
58
  name: name,
59
59
  object_story_spec: object_story_spec.to_json
60
60
  }
61
61
  end
62
-
63
62
  end
64
-
65
63
  end
66
64
  end
@@ -3,11 +3,10 @@ module FacebookAds
3
3
  # An image will always produce the same hash.
4
4
  # https://developers.facebook.com/docs/marketing-api/reference/ad-image
5
5
  class AdImage < Base
6
-
7
- FIELDS = %w(id hash account_id name permalink_url original_width original_height)
6
+ FIELDS = %w(id hash account_id name permalink_url original_width original_height).freeze
8
7
 
9
8
  class << self
10
- def find(id)
9
+ def find(_id)
11
10
  raise Exception, 'NOT IMPLEMENTED'
12
11
  end
13
12
  end
@@ -16,13 +15,12 @@ module FacebookAds
16
15
  self[:hash]
17
16
  end
18
17
 
19
- def update(data)
18
+ def update(_data)
20
19
  raise Exception, 'NOT IMPLEMENTED'
21
20
  end
22
21
 
23
22
  def destroy
24
23
  super(path: "/act_#{account_id}/adimages", query: { hash: hash })
25
24
  end
26
-
27
25
  end
28
26
  end