zuck 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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]])