zuck 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.markdown +35 -5
  3. data/Gemfile +3 -2
  4. data/Gemfile.lock +13 -10
  5. data/README.markdown +26 -38
  6. data/VERSION +1 -1
  7. data/console +8 -2
  8. data/lib/zuck/facebook/ad_account.rb +22 -5
  9. data/lib/zuck/facebook/ad_campaign.rb +12 -15
  10. data/lib/zuck/facebook/ad_creative.rb +14 -14
  11. data/lib/zuck/facebook/ad_group.rb +12 -12
  12. data/lib/zuck/facebook/ad_interest.rb +12 -15
  13. data/lib/zuck/facebook/ad_set.rb +30 -0
  14. data/lib/zuck/facebook/targeting_spec.rb +4 -4
  15. data/lib/zuck/fb_object/dsl.rb +15 -7
  16. data/lib/zuck/fb_object/hash_delegator.rb +20 -3
  17. data/lib/zuck/fb_object/write.rb +18 -6
  18. data/lib/zuck/fb_object.rb +1 -1
  19. data/lib/zuck/helpers.rb +20 -1
  20. data/spec/fixtures/a_single_account.yml +146 -29
  21. data/spec/fixtures/a_single_campaign.yml +61 -12
  22. data/spec/fixtures/a_single_group.yml +65 -12
  23. data/spec/fixtures/ad_interest_search_disney.yml +16 -14
  24. data/spec/fixtures/ad_interest_search_moviepilot.yml +15 -98
  25. data/spec/fixtures/ad_interest_search_nonexistant.yml +7 -5
  26. data/spec/fixtures/create_ad_campaign.yml +334 -9
  27. data/spec/fixtures/create_ad_group.yml +154 -9
  28. data/spec/fixtures/create_ad_set.yml +95 -0
  29. data/spec/fixtures/delete_ad_group.yml +55 -8
  30. data/spec/fixtures/find_a_single_group_and_update_it.yml +122 -95
  31. data/spec/fixtures/list_of_ad_accounts.yml +77 -58
  32. data/spec/fixtures/list_of_ad_campaigns.yml +77 -55
  33. data/spec/fixtures/list_of_ad_creatives.yml +61 -13
  34. data/spec/fixtures/list_of_ad_groups.yml +77 -60
  35. data/spec/fixtures/list_of_all_ad_creatives_of_account.yml +77 -73
  36. data/spec/fixtures/reach_for_invalid_interest.yml +29 -111
  37. data/spec/fixtures/reach_for_valid_keywords.yml +11 -54
  38. data/spec/fixtures/reach_for_valid_keywords_male_young.yml +11 -60
  39. data/spec/lib/zuck/facebook/ad_account_spec.rb +1 -1
  40. data/spec/lib/zuck/facebook/ad_interest_spec.rb +2 -2
  41. data/spec/lib/zuck/facebook/targeting_spec_spec.rb +13 -21
  42. data/spec/lib/zuck_spec.rb +34 -23
  43. data/spec/spec_helper.rb +2 -5
  44. data/test_access_token +1 -0
  45. data/zuck.gemspec +15 -9
  46. metadata +63 -46
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MGYzNGI4MTQ0ZjJmNzgxZjQwY2JlNTg3YTgyMTc2NzlhYTE2NWNkZA==
5
- data.tar.gz: !binary |-
6
- ZjllZWZjNTgxYzdhYmRiNjQ1YmE2OTY5NTI5MmMyNmFjY2M2NDgyYQ==
2
+ SHA1:
3
+ metadata.gz: 40becd6a7fffa32053f38e6e550c76ba84480044
4
+ data.tar.gz: 9ad567305d626e899c9b3bc1563297df9f49164b
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NDU3YjViNWY4NWZiNGMzYTAwZTZmZmZmMWZmODVmNWQ2NjEyMzM3Nzc0OWFi
10
- MWM5OWYwYTAyNmM1ODA4MTM1MmExYzI0MWM4YjM1N2MxNzc4NGFmMGYzMDc5
11
- ODMwNzkwNTEzMThhYzgzYTE4ZWEyMWQ3NDM4NTdmMmZlMmY5Nzk=
12
- data.tar.gz: !binary |-
13
- NTZkMDQ0NzZlYzQyMGVmMmJiNjcwZmU2NmYzOTgwZTM1OWY2MGEwNjJhODQ3
14
- YjcxMThmNzkxNDllOWYxMDhkMzhmMTBkNDk2ODg1ODFjY2RmYzBiODRjZDY2
15
- MWMzZjkxNjhkNThmYjJkN2UxYTM3ZjM3ODE4Njg2ZGU1NDBlM2I=
6
+ metadata.gz: 5fc4c51ef900b5b8d4084500aa339ee2d0e3320d2c89baa9ccf1ce044ee965eb379e91c22010d79e68c47281c7f1988d0622f42ca1d296d6d0b3904def9fe5e0
7
+ data.tar.gz: 42e866685f4fefc664e59871b98db3428a0407144a7b4ce015fc68fc358fbc4e313318b8f27a73fd8ec187292555be917ce9152ac849591e2ecbd05a42b7f592
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,35 @@
1
+ 2.0.0
2
+ =====
3
+
4
+ - update Koala to 1.10.1
5
+ - use the `/search?type=adinterestq=interest` endpoint to validate ad interests
6
+ instead of `/search?type=adinterestvalid&interest_list=['interest']`
7
+ - use json objects instead of string values for ad interests
8
+ - don't escape commas with %2C when passing searches on to koala
9
+ - update ad account properties
10
+ - fix consecutive `#save`s without a `#reload` in between
11
+
12
+ Breaking changes:
13
+ ----------------
14
+
15
+ If you upgrade from 1.x, make sure you read the following:
16
+
17
+ - `Zuck::AdInterest.search` results use the `audience_size` key in order to use
18
+ the same names as the facebook api. In zuck 1.x it was `audience`
19
+ - `Zuck::TargetingSpec`
20
+ - `initialize` does not take string interests anymore,
21
+ but expects `{id: '123', name: 'foo'} objects, see
22
+ "[Specifying values for interests as a string will be sunset in favor of a JSON object of id and name](https://developers.facebook.com/docs/apps/migrations/ads-api-changes-2014-04-09)"
23
+ - in your targeting spec, don't specify `{countries: ['US']}`, but
24
+ `{geo_locations: { countries: ['US']}}`
25
+ - `Zuck::AdInterest.best_guess` returns an ad interest object, what was
26
+ formerly the `:interest` key is now called `:name`
27
+ - Ad Campaigns are now called Ad Sets, and Ad Sets can be grouped
28
+ together in Ad Campaigns [see docs](https://developers.facebook.com/docs/reference/ads-api/adcampaign/v2.2)
29
+
30
+
1
31
  1.0.0
2
- -----
32
+ =====
3
33
  - Implement Facebook's April 9 breaking changes, which also change this
4
34
  gem
5
35
  - AdKeyword/keywords are gone, welcome AdInterest/interests
@@ -10,16 +40,16 @@ docs](https://developers.facebook.com/docs/reference/ads-api/interest-targeting)
10
40
  docs](https://developers.facebook.com/docs/reference/ads-api/targeting-specs))
11
41
 
12
42
  0.2.0
13
- -----
43
+ =====
14
44
  - implement Facebook's [Oct 2013 breaking changes](https://developers.facebook.com/roadmap/#q4_2013)
15
45
 
16
46
  0.0.9
17
- -----
47
+ =====
18
48
  - add Zuck::TargetingSpec.valid_countries? method to allow for country
19
49
  validation from the outside
20
50
 
21
51
  0.0.8
22
- -----
52
+ =====
23
53
  - validate the countries before fetching a reach estimate
24
54
 
25
55
  0.0.7
@@ -28,5 +58,5 @@ docs](https://developers.facebook.com/docs/reference/ads-api/targeting-specs))
28
58
  with a # as a prefix
29
59
 
30
60
  0.0.4
31
- -----
61
+ =====
32
62
  - integrated targeting spec to fetch reach estimates
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source 'http://rubygems.org'
2
2
 
3
3
  gem 'rvm'
4
4
  gem 'bundler'
5
- gem 'koala', '~>1.6.0'
5
+ gem 'koala'
6
6
  gem 'activesupport'
7
7
 
8
8
  group :development do
@@ -16,11 +16,12 @@ group :development, :test do
16
16
  gem 'webmock', '~>1.8.0'
17
17
  gem 'rspec'
18
18
  gem 'vcr'
19
+ gem 'pry'
19
20
  end
20
21
 
21
22
  platform :ruby do
22
23
  group :development do
23
- gem 'guard'
24
+ gem 'guard-ctags-bundler'
24
25
  gem 'guard-rspec'
25
26
  gem 'guard-bundler'
26
27
  gem 'guard-yard'
data/Gemfile.lock CHANGED
@@ -4,15 +4,15 @@ GEM
4
4
  activesupport (3.2.9)
5
5
  i18n (~> 0.6)
6
6
  multi_json (~> 1.0)
7
- addressable (2.3.3)
7
+ addressable (2.3.6)
8
8
  bourne (1.1.2)
9
9
  mocha (= 0.10.5)
10
10
  builder (3.2.2)
11
11
  coderay (1.0.8)
12
12
  crack (0.3.1)
13
13
  diff-lcs (1.1.3)
14
- faraday (0.8.7)
15
- multipart-post (~> 1.1)
14
+ faraday (0.8.9)
15
+ multipart-post (~> 1.2.0)
16
16
  git (1.2.6)
17
17
  github_api (0.10.2)
18
18
  addressable
@@ -30,6 +30,8 @@ GEM
30
30
  guard-bundler (1.0.0)
31
31
  bundler (~> 1.0)
32
32
  guard (~> 1.1)
33
+ guard-ctags-bundler (0.2.0)
34
+ guard (>= 1.1)
33
35
  guard-rspec (2.3.3)
34
36
  guard (>= 1.1)
35
37
  rspec (~> 2.11)
@@ -52,10 +54,10 @@ GEM
52
54
  json (1.8.1-java)
53
55
  jwt (0.1.11)
54
56
  multi_json (>= 1.5)
55
- koala (1.6.0)
56
- addressable (~> 2.2)
57
- faraday (~> 0.8)
58
- multi_json (~> 1.3)
57
+ koala (1.10.1)
58
+ addressable
59
+ faraday
60
+ multi_json
59
61
  listen (0.6.0)
60
62
  lumberjack (1.0.2)
61
63
  metaclass (0.0.1)
@@ -63,7 +65,7 @@ GEM
63
65
  mini_portile (0.5.3)
64
66
  mocha (0.10.5)
65
67
  metaclass (~> 0.0.1)
66
- multi_json (1.7.2)
68
+ multi_json (1.10.1)
67
69
  multi_xml (0.5.5)
68
70
  multipart-post (1.2.0)
69
71
  nokogiri (1.6.1)
@@ -122,12 +124,13 @@ DEPENDENCIES
122
124
  activesupport
123
125
  bundler
124
126
  growl
125
- guard
126
127
  guard-bundler
128
+ guard-ctags-bundler
127
129
  guard-rspec
128
130
  guard-yard
129
131
  jeweler (~> 2.0.1)
130
- koala (~> 1.6.0)
132
+ koala
133
+ pry
131
134
  rb-fsevent
132
135
  rdoc (~> 3.12)
133
136
  redcarpet
data/README.markdown CHANGED
@@ -3,7 +3,7 @@
3
3
  Zuck; use facebook's advertisement API with ruby
4
4
  ================
5
5
 
6
- This is a little gem that makes access to facebook's
6
+ This is a little gem that makes access to facebook's
7
7
  ads API a little easier. Check out facebook's
8
8
  [documentation](https://developers.facebook.com/docs/reference/ads-api/)
9
9
  for a nice diagram that explains how things work.
@@ -28,16 +28,12 @@ accounts = Zuck::AdAccount.all
28
28
 
29
29
  # Let's look at an account
30
30
  my_account = accounts.first
31
- => #<Zuck::AdAccount id: "act_10150585630710217", account_id: "10150585630710217", name: "", account_status: 1, currency: "USD", timezone_id: 47, timezone_name: "Europe/Berlin", timezone_offset_hours_utc: 2, is_personal: 0, business_name: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street2: "J\u00e4gerndorfer Zeile 61", business_city: "Berlin", business_state: "Berlin", business_zip: "12209", business_country_code: "DE", vat_status: 3, daily_spend_limit: 25000, users: [{"uid":501730216,"permissions":[1,2,3,4,5,7],"role":1001}], notification_settings: {"501730216":{"1000":{"1":1},"1001":{"1":1},"1002":{"1":1,"2":60},"1003":{"1":1,"2":60},"1004":{"1":1},"1005":{"1":1},"1006":{"1":1},"1009":{"1":1},"1010":{"1":1},"1011":{"1":1},"2000":{"1":1,"2":60},"2001":{"1":1,"2":60},"2002":{"2":60},"2003":{"1":1,"2":60},"2004":{"1":1,"2":60},"2005":{"1":1,"2":60},"3000":{"1":1,"2":60},"3001":{"1":1,"2":60},"3002":{"2":60},"3003":{"1":1,"2":60},"5000":{"1":1},"6000":{"1":1},"6001":{"1":1},"9000":{"1":1,"2":60},"8000":{"1":1,"2":60}}}, capabilities: [], balance: 0, moo_default_conversion_bid: 1000, moo_default_bid: 1000>
31
+ => #<Zuck::AdAccount id: "act_10150585630710217", account_id: "10150585630710217", name: "", account_status: 1, currency: "USD", timezone_id: 47, timezone_name: "Europe/Berlin", timezone_offset_hours_utc: 2, is_personal: 0, business_name: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street2: "J\u00e4gerndorfer Zeile 61", business_city: "Berlin", business_state: "Berlin", business_zip: "12209", business_country_code: "DE", vat_status: 3, daily_spend_limit: 25000, users: [{"uid":501730216,"permissions":[1,2,3,4,5,7],"role":1001}], notification_settings: {"501730216":{"1000":{"1":1},"1001":{"1":1},"1002":{"1":1,"2":60},"1003":{"1":1,"2":60},"1004":{"1":1},"1005":{"1":1},"1006":{"1":1},"1009":{"1":1},"1010":{"1":1},"1011":{"1":1},"2000":{"1":1,"2":60},"2001":{"1":1,"2":60},"2002":{"2":60},"2003":{"1":1,"2":60},"2004":{"1":1,"2":60},"2005":{"1":1,"2":60},"3000":{"1":1,"2":60},"3001":{"1":1,"2":60},"3002":{"2":60},"3003":{"1":1,"2":60},"5000":{"1":1},"6000":{"1":1},"6001":{"1":1},"9000":{"1":1,"2":60},"8000":{"1":1,"2":60}}}, capabilities: [], balance: 0, moo_default_conversion_bid: 1000, moo_default_bid: 1000>
32
32
 
33
33
  # Aha. How do I access properties? The documented properties
34
34
  # have getters:
35
35
  my_account.currency
36
- => "USD"
37
-
38
- # But facebook also returns some non documented stuff
39
- my_account[:moo_default_bid]
40
- => 1000
36
+ => "USD"
41
37
 
42
38
  # Let's fetch the campaigns for this account
43
39
  my_campaign = my_account.ad_campaigns.first
@@ -67,12 +63,12 @@ creative = '{"type":25,"action_spec":{"action.type":"like", "post":1015042041088
67
63
  o = { bid_type: 1,
68
64
  max_bid: 1,
69
65
  name: "My first ad group",
70
- targeting: '{"countries":["US"]}',
66
+ targeting: '{"geo_locations": {"countries":["US"]}}',
71
67
  creative: creative}
72
-
68
+
73
69
  # Create it in the context of my_campaign
74
70
  group = my_campaign.create_ad_group(o)
75
- => #<Zuck::AdGroup adgroup_id: 6005851390151, ad_id: 6005851390151, campaign_id: 6005851032951, name: "My first ad group", adgroup_status: 4, bid_type: 1, max_bid: "1", bid_info: {"1":"1"}, ad_status: 4, account_id: "10150585630710217", id: "6005851390151", creative_ids: [6005851371551], targeting: {"countries":["US"],"friends_of_connections":[{"id":"6005851366351","name":null}]}, conversion_specs: [{"action.type":"like","post":"10150420410887685"}], start_time: null, end_time: null, updated_time: 1343916568, created_time: 1343916568>
71
+ => #<Zuck::AdGroup adgroup_id: 6005851390151, ad_id: 6005851390151, campaign_id: 6005851032951, name: "My first ad group", adgroup_status: 4, bid_type: 1, max_bid: "1", bid_info: {"1":"1"}, ad_status: 4, account_id: "10150585630710217", id: "6005851390151", creative_ids: [6005851371551], targeting: {"geo_locations": {"countries":["US"]},"friends_of_connections":[{"id":"6005851366351","name":null}]}, conversion_specs: [{"action.type":"like","post":"10150420410887685"}], start_time: null, end_time: null, updated_time: 1343916568, created_time: 1343916568>
76
72
 
77
73
  # Shoot, that was the wrong name
78
74
  group.name = "My serious ad group"
@@ -94,31 +90,31 @@ AdInterest convenience methods
94
90
  ```ruby
95
91
  graph = Zuck.graph
96
92
 
97
- # Search for keywords (to auto complete, for example) (yes, facebook sometimes returns ids as string and sometimes as numbers)
93
+ # Search for interests (to auto complete, for example) (yes, facebook sometimes returns ids as string and sometimes as numbers)
98
94
  Zuck::AdInterest.search(graph, "Auto")
99
95
  => [
100
- {:keyword=>"Auto", :id=>"6003156165433", :audience=>nil},
101
- {:keyword=>"#Automobile", :id=>6003176678152, :audience=>97900000},
102
- {:keyword=>"#Auto racing", :id=>6003146718552, :audience=>21800000},
103
- {:keyword=>"#Auto mechanic", :id=>6003109384433, :audience=>14600000}
96
+ {:interest=>"Auto", :id=>"6003156165433", :audience=>nil},
97
+ {:interest=>"#Automobile", :id=>6003176678152, :audience=>97900000},
98
+ {:interest=>"#Auto racing", :id=>6003146718552, :audience=>21800000},
99
+ {:interest=>"#Auto mechanic", :id=>6003109384433, :audience=>14600000}
104
100
  ]
105
101
 
106
- # Quickly check if a keyword is valid
102
+ # Quickly check if a interest is valid
107
103
  Zuck::AdInterest.validate(graph, '#Eminem')
108
104
  => {"#Eminem" => true}
109
105
 
110
- # Quickly check a couple of keywords
106
+ # Quickly check a couple of interests
111
107
  Zuck::AdInterest.validate(graph, ['#Eminem', 'Wil Ferel', 'Bronson'])
112
108
  => {"#Eminem"=>true, "Bronson"=>true, "Wil Ferel"=>false}
113
109
 
114
- # Make a best guess on how a keyword is called on Facebook
110
+ # Make a best guess on how a interest is called on Facebook
115
111
  Zuck::AdInterest.best_guess(graph, 'Disney')
116
- => {:keyword=>"#The Walt Disney Company", :id=>6003270522085, :audience=>72500000}
112
+ => {:interest=>"#The Walt Disney Company", :id=>6003270522085, :audience=>72500000}
117
113
 
118
- # Sometimes a best guess does not return a keyword with a # prefix, and that
119
- # means that we don't know the audience of that keyword:
114
+ # Sometimes a best guess does not return a interest with a # prefix, and that
115
+ # means that we don't know the audience of that interest:
120
116
  Zuck::AdInterest.best_guess(graph, 'Moviepilot')
121
- => {:keyword=>"Moviepilot", :id=>6003327847780, :audience=>nil}
117
+ => {:interest=>"Moviepilot", :id=>6003327847780, :audience=>nil}
122
118
 
123
119
  ```
124
120
 
@@ -137,27 +133,19 @@ Here's a support chart:
137
133
  <th style="text-align:center">.save</th>
138
134
  <th style="text-align:center">.destroy</th>
139
135
  <th style="text-align:center">parent.create_obj*</th>
140
- <th style="text-align:center">Convenience methods**</th>
141
136
  </tr>
142
- <tr><td style="text-align: right">Ad account</td> <td>✔</td><td>-</td><td>✔</td><td>✔</td><td>-</td><td>-</td></tr>
143
- <tr><td style="text-align: right">Ad account group</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
144
- <tr><td style="text-align: right">Ad campaign</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>-</td></tr>
145
- <tr><td style="text-align: right">Ad creative</td> <td>✔</td><td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
146
- <tr><td style="text-align: right">Ad group</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>-</td></tr>
147
- <tr><td style="text-align: right">Ad image</td> <td>●</td><td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
148
- <tr><td style="text-align: right">Ad user</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
137
+ <tr><td style="text-align: right">Ad account</td> <td>✔</td><td>-</td><td>✔</td><td>✔</td><td>-</td></tr>
138
+ <tr><td style="text-align: right">Ad account group</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
139
+ <tr><td style="text-align: right">Ad campaign</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td></tr>
140
+ <tr><td style="text-align: right">Ad creative</td> <td>✔</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
141
+ <tr><td style="text-align: right">Ad group</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td></tr>
142
+ <tr><td style="text-align: right">Ad set</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td></tr>
143
+ <tr><td style="text-align: right">Ad user</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
149
144
  </table>
150
145
 
151
- (*) This means that you can, for example, create a new ad group by calling
146
+ (*) This means if you can, for example, create a new ad group by calling
152
147
  `my_campaign.create_ad_group(data)` or not.
153
148
 
154
- (**) Right now, everything goes right to facebook, but we'll want some
155
- convenience methods that tell you, for example, what
156
- `ad_group.ad_status == 3` actually means
157
-
158
- ( ) These don't exist as their own objects in this gem but live in their
159
- parents. This means you can, for now, only read them:
160
-
161
149
  Users don't exist as objects yet, but you can list all ad users of
162
150
  an account via `my_ad_account.users` and you will get an array of hashes.
163
151
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 2.0.0
data/console CHANGED
@@ -4,10 +4,11 @@ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
4
4
  require 'bundler'
5
5
  Bundler.require
6
6
 
7
- require 'irb'
7
+ require 'pry'
8
8
  require 'zuck'
9
9
 
10
10
  def reload!
11
+ Zuck.graph = Koala::Facebook::API.new(File.read("test_access_token"))
11
12
  @loaded_files ||= {}
12
13
  count = 0
13
14
 
@@ -23,4 +24,9 @@ def reload!
23
24
  "reloaded #{count} files"
24
25
  end
25
26
 
26
- IRB.start
27
+ def graph
28
+ Zuck.graph
29
+ end
30
+
31
+ Zuck.graph = Koala::Facebook::API.new(File.read("test_access_token"))
32
+ binding.pry
@@ -1,9 +1,9 @@
1
1
  module Zuck
2
2
  class AdAccount < RawFbObject
3
+ include Zuck::Helpers
3
4
 
4
- # The [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
5
- # were incomplete, so I added here what the graph explorer
6
- # actually returned.
5
+ # Known keys as per
6
+ # [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
7
7
  known_keys :account_groups,
8
8
  :account_id,
9
9
  :account_status,
@@ -19,25 +19,42 @@ module Zuck
19
19
  :business_street,
20
20
  :business_zip,
21
21
  :capabilities,
22
+ :created_time,
22
23
  :currency,
23
24
  :daily_spend_limit,
25
+ :end_advertiser,
26
+ :funding_source,
27
+ :funding_source_details,
24
28
  :id,
25
29
  :is_personal,
30
+ :media_agency,
26
31
  :name,
32
+ :offsite_pixels_tos_accepted,
33
+ :partner,
27
34
  :spend_cap,
28
35
  :timezone_id,
29
36
  :timezone_name,
30
37
  :timezone_offset_hours_utc,
31
38
  :tos_accepted,
32
39
  :users,
33
- :vat_status
40
+ :tax_id_status
34
41
 
35
42
 
36
43
  list_path 'me/adaccounts'
37
- connections :ad_campaigns
44
+ connections :ad_campaigns, :ad_sets, :ad_groups
38
45
 
39
46
  def self.all(graph = Zuck.graph)
40
47
  super(graph)
41
48
  end
49
+
50
+ def path
51
+ normalize_account_id(id)
52
+ end
53
+
54
+ def set_data(data)
55
+ super
56
+ self.id = normalize_account_id(id)
57
+ end
58
+
42
59
  end
43
60
  end
@@ -1,23 +1,20 @@
1
1
  module Zuck
2
2
  class AdCampaign < RawFbObject
3
3
 
4
- # The [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
5
- # were incomplete, so I added here what the graph explorer
6
- # actually returned.
7
- known_keys :account_id,
8
- :campaign_status,
9
- :created_time,
10
- :daily_imps,
11
- :end_time,
12
- :id,
13
- :lifetime_budget,
4
+ # Known keys as per
5
+ # the [fb docs](https://developers.facebook.com/docs/reference/ads-api/adcampaign/v2.2)
6
+ # as well as undocumented keys returned by the Graph API
7
+ known_keys :id,
8
+ :account_id,
9
+ :objective,
14
10
  :name,
15
- :start_time,
16
- :updated_time
11
+ :adgroups,
12
+ :campaign_group_status,
13
+ :buying_type
17
14
 
18
- parent_object :ad_account
19
- list_path :adcampaigns
20
- connections :ad_groups
15
+ parent_object :ad_account, as: :account_id
16
+ list_path :adcampaign_groups
17
+ connections :ad_groups, :ad_campaigns
21
18
 
22
19
  end
23
20
  end
@@ -4,25 +4,25 @@ module Zuck
4
4
  # Can't create this directly (yet)
5
5
  read_only
6
6
 
7
- # The [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
8
- # were incomplete, so I added here what the graph explorer
9
- # actually returned.
10
- known_keys :name,
11
- :type,
12
- :object_id,
7
+ # Known keys as per
8
+ # [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
9
+ known_keys :actor_id,
13
10
  :body,
11
+ :call_to_action_type,
12
+ :follow_redirect,
13
+ :image_crops,
14
+ :image_file,
14
15
  :image_hash,
15
16
  :image_url,
16
- :id,
17
- :title,
18
17
  :link_url,
18
+ :name,
19
+ :object_id,
20
+ :object_story_id,
21
+ :object_story_spec,
22
+ :object_url,
23
+ :title,
19
24
  :url_tags,
20
- :preview_url,
21
- :related_fan_page,
22
- :follow_redirect,
23
- :auto_update,
24
- :story_id,
25
- :action_spec
25
+ :id
26
26
 
27
27
  parent_object :ad_group
28
28
  list_path :adcreatives
@@ -3,20 +3,20 @@ require 'zuck/facebook/ad_creative'
3
3
  module Zuck
4
4
  class AdGroup < RawFbObject
5
5
 
6
- # The [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
7
- # were incomplete, so I added here what the graph explorer
8
- # actually returned.
9
- known_keys :account_id,
6
+ # Known keys as per
7
+ # [fb docs](https://developers.facebook.com/docs/reference/ads-api/adgroup/v2.2)
8
+ known_keys :id,
9
+ :account_id,
10
10
  :adgroup_status,
11
- :bid_info,
12
11
  :bid_type,
12
+ :bid_info,
13
+ :conversion_specs,
13
14
  :campaign_id,
15
+ :campaign_group_id,
14
16
  :conversion_specs,
15
17
  :created_time,
16
18
  :creative_ids,
17
- :id,
18
- :disapprove_reason_descriptions,
19
- :last_updated_by_app_id,
19
+ :failed_delivery_checks,
20
20
  :name,
21
21
  :targeting,
22
22
  :tracking_specs,
@@ -27,10 +27,10 @@ module Zuck
27
27
  list_path :adgroups
28
28
  connections :ad_creatives
29
29
 
30
- def self.create(graph, data, ad_campaign)
31
- path = ad_campaign.ad_account.path
32
- data['campaign_id'] = ad_campaign.id
33
- super(graph, data, ad_campaign, path)
30
+ def self.create(graph, data, ad_set)
31
+ path = ad_set.ad_account.path
32
+ data['campaign_id'] = ad_set.id
33
+ super(graph, data, ad_set, path)
34
34
  end
35
35
 
36
36
  end
@@ -9,35 +9,32 @@ module Zuck
9
9
  # popular one
10
10
  def best_guess(graph, interest)
11
11
  search(graph, interest).sort do |a,b|
12
- if a[:audience].to_i > 0 || b[:audience].to_i > 0
13
- a[:audience].to_i <=> b[:audience].to_i
12
+ if a[:audience_size].to_i > 0 || b[:audience_size].to_i > 0
13
+ a[:audience_size].to_i <=> b[:audience_size].to_i
14
14
  else
15
- b[:interest].length <=> a[:interest].length
15
+ b[:name].length <=> a[:name].length
16
16
  end
17
17
  end.last
18
18
  end
19
19
 
20
- # Checks the ad api to see if the given interests are valid
20
+ # Checks the ad api to see if the given interests are valid. You can either
21
+ # pass it an array of strings, or an array of hashes with `:name` and `:id` keys.
21
22
  # @return [Hash] The keys are the (lowercased) interests and the values their validity
22
23
  def validate(graph, interests)
23
- interests = normalize_array(interests).map{|k| k.gsub(',', '%2C')}
24
- search = graph.search(nil, type: 'adinterestvalid', interest_list: [interests].flatten)
25
24
  results = {}
26
- search.each do |r|
27
- results[r['name']] = r['valid']
25
+ normalized = normalize_array(values_from_string_or_object_interests(interests))
26
+ normalized.each do |interest|
27
+ # The interest is valid if we found at least one match that
28
+ # has the exactly the same name (ignoring case)
29
+ hits = search(graph, interest).select{|d| d['name'].downcase == interest.downcase}
30
+ results[interest] = hits.count > 0
28
31
  end
29
32
  results
30
33
  end
31
34
 
32
35
  # Ad interest search
33
36
  def search(graph, interest)
34
- results = graph.search(interest, type: :adinterest).map do |r|
35
- {
36
- interest: r['name'],
37
- id: r['id'],
38
- audience: r['audience_size']
39
- }
40
- end
37
+ graph.search(interest, type: :adinterest).map(&:with_indifferent_access)
41
38
  end
42
39
  end
43
40
 
@@ -0,0 +1,30 @@
1
+ require 'zuck/facebook/ad_creative'
2
+
3
+ module Zuck
4
+ class AdSet < RawFbObject
5
+
6
+ # Known keys as per
7
+ # [fb docs](https://developers.facebook.com/docs/reference/ads-api/adset/v2.2)
8
+ known_keys :id,
9
+ :name,
10
+ :account_id,
11
+ :bid_type,
12
+ :bid_info,
13
+ :campaign_group_id,
14
+ :campaign_status,
15
+ :start_time,
16
+ :end_time,
17
+ :updated_time,
18
+ :created_time,
19
+ :daily_budget,
20
+ :lifetime_budget,
21
+ :budget_remaining,
22
+ :targeting,
23
+ :promoted_object
24
+
25
+ parent_object :ad_account, as: :account_id
26
+ list_path :adcampaigns # Yes, this is correct, "for legacy reasons"
27
+ connections :ad_groups, :ad_creatives
28
+
29
+ end
30
+ end
@@ -70,7 +70,7 @@ module Zuck
70
70
  def initialize(graph, ad_account, spec = nil)
71
71
  @validated_interests = {}
72
72
  @graph = graph
73
- @ad_account = "act_#{ad_account}".gsub('act_act_', 'act_')
73
+ @ad_account = normalize_account_id(ad_account)
74
74
  self.spec = spec
75
75
  end
76
76
 
@@ -104,7 +104,7 @@ module Zuck
104
104
  # @return boolean
105
105
  def validate_interest(interest)
106
106
  if @validated_interests[interest] == nil
107
- interests = normalize_array([@spec[:interests]] + [interest])
107
+ interests = [@spec[:interests]] + [interest]
108
108
  @validated_interests = Zuck::AdInterest.validate(@graph, interests)
109
109
  end
110
110
  @validated_interests[interest] == true
@@ -153,7 +153,7 @@ module Zuck
153
153
  private
154
154
 
155
155
  def validate_spec
156
- @spec[:interests] = normalize_array(@spec[:interests])
156
+ @spec[:interests] = @spec[:interests]
157
157
  @spec[:broad_age] ||= false
158
158
  validate_countries
159
159
  unless @spec[:interests].present? or @spec[:connections].present?
@@ -195,7 +195,7 @@ module Zuck
195
195
  @spec[:genders] = [2] if gender.to_s == 'female'
196
196
 
197
197
  interest = spec.delete(:interest)
198
- @spec[:interests] = normalize_array([interest, @spec[:interests]])
198
+ @spec[:interests] = [interest, @spec[:interests]].compact.flatten
199
199
 
200
200
  country = spec.delete(:country)
201
201
  @spec[:countries] = normalize_countries([country, @spec[:countries]])