zuck 0.0.9 → 0.1.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.
data/README.markdown CHANGED
@@ -10,10 +10,6 @@ for a nice diagram that explains how things work.
10
10
 
11
11
  ![](https://dl.dropbox.com/u/1953503/kw29_koelner_gelierzucker_31_oder_diamant_feinster_zucker_462982.jpeg)
12
12
 
13
- We just hacked this up and are not using it in production yet,
14
- so handle with care. On the other hand, simplecov reports 100%
15
- coverage. But as it is a gem in a very early stage, a warning was
16
- due.
17
13
 
18
14
  Usage
19
15
  =====
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.9
1
+ 0.1.0
@@ -0,0 +1,45 @@
1
+ module Zuck
2
+ module AdKeyword
3
+ extend Zuck::Helpers
4
+ extend self
5
+
6
+ # Given a keyword, make a guess on what the closest keyword
7
+ # on the facebook ads api is. It tends to use a # prefixed
8
+ # keyword if available, and also a more popular one over a less
9
+ # popular one
10
+ def best_guess(graph, keyword)
11
+ search(graph, keyword).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
14
+ else
15
+ b[:keyword].length <=> a[:keyword].length
16
+ end
17
+ end.last
18
+ end
19
+
20
+ # Checks the ad api to see if the given keywords are valid
21
+ # @return [Hash] The keys are the (lowercased) keywords and the values their validity
22
+ def validate(graph, keywords)
23
+ keywords = normalize_array(keywords).map{|k| k.gsub(',', '%2C')}
24
+ search = graph.search(nil, type: 'adkeywordvalid', keyword_list: keywords.join(","))
25
+ results = {}
26
+ search.each do |r|
27
+ results[r['name']] = r['valid']
28
+ end
29
+ results
30
+ end
31
+
32
+ # Ad keyword search
33
+ def search(graph, keyword)
34
+ results = graph.search(keyword, type: :adkeyword).map do |r|
35
+ audience = r['description'].scan(/[0-9]+/).join('').to_i rescue nil
36
+ {
37
+ keyword: r['name'],
38
+ id: r['id'],
39
+ audience: audience
40
+ }
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -49,6 +49,8 @@ module Zuck
49
49
  # => 12345
50
50
  #
51
51
  class TargetingSpec
52
+ include Zuck::Helpers
53
+
52
54
  attr_reader :spec, :graph
53
55
 
54
56
  # @param graph [Koala::Facebook::API] The koala graph object to use
@@ -103,23 +105,11 @@ module Zuck
103
105
  def validate_keyword(keyword)
104
106
  if @validated_keywords[keyword] == nil
105
107
  keywords = normalize_array([@spec[:keywords]] + [keyword])
106
- @validated_keywords = self.class.validate_keywords(@graph, keywords)
108
+ @validated_keywords = Zuck::AdKeyword.validate(@graph, keywords)
107
109
  end
108
110
  @validated_keywords[keyword] == true
109
111
  end
110
112
 
111
- # Checks the ad api to see if the given keywords are valid
112
- # @return [Hash] The keys are the (lowercased) keywords and the values their validity
113
- def self.validate_keywords(graph, keywords)
114
- keywords = normalize_array(keywords).map{|k| k.gsub(',', '%2C')}
115
- search = graph.search(nil, type: 'adkeywordvalid', keyword_list: keywords.join(","))
116
- results = {}
117
- search.each do |r|
118
- results[r['name']] = r['valid']
119
- end
120
- results
121
- end
122
-
123
113
  # Fetches a bunch of reach estimates from facebook at once.
124
114
  # @param graph Koala graph instance
125
115
  # @param specs [Array<Hash>] An array of specs as you would pass to {#initialize}
@@ -162,22 +152,6 @@ module Zuck
162
152
 
163
153
  private
164
154
 
165
- def self.normalize_array(arr)
166
- [arr].flatten.compact.map(&:to_s).uniq.sort
167
- end
168
-
169
- def self.normalize_countries(countries)
170
- normalize_array(countries).map(&:upcase)
171
- end
172
-
173
- def normalize_array(arr)
174
- self.class.normalize_array(arr)
175
- end
176
-
177
- def normalize_countries(countries)
178
- self.class.normalize_countries(countries)
179
- end
180
-
181
155
  def validate_spec
182
156
  @spec[:countries] = normalize_countries(@spec[:countries])
183
157
  @spec[:keywords] = normalize_array(@spec[:keywords])
@@ -0,0 +1,11 @@
1
+ module Zuck
2
+ module Helpers
3
+ def normalize_array(arr)
4
+ [arr].flatten.compact.map(&:to_s).uniq.sort
5
+ end
6
+
7
+ def normalize_countries(countries)
8
+ normalize_array(countries).map(&:upcase)
9
+ end
10
+ end
11
+ end
data/lib/zuck.rb CHANGED
@@ -2,6 +2,7 @@ require 'active_support/all'
2
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
  require 'zuck/koala/koala_methods'
4
4
  require 'zuck/fb_object'
5
+ require 'zuck/helpers'
5
6
  Dir[File.expand_path("../zuck/facebook/**/*.rb", __FILE__)].each{ |f| require f}
6
7
 
7
8
  module Zuck
@@ -0,0 +1,53 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://graph.facebook.com/search?access_token=CAAEvJ5vzhl8BAE7m0kztNbbASKHymRlXoBZCdZCtMsebNEgaR0yOmZCBfeTIXT8MnuV3ZCH5lBDQOcC4S9geswWZBuF707gJ42lV9DHgGILsRaiG2upipiHggl7UZAeDVgBSSsap9s9uv1ghZCxNsmH&q=disney&type=adkeyword
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.7
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - ! '*/*'
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Access-Control-Allow-Origin:
22
+ - ! '*'
23
+ Cache-Control:
24
+ - private, no-cache, no-store, must-revalidate
25
+ Content-Type:
26
+ - application/json; charset=UTF-8
27
+ Etag:
28
+ - ! '"167c644dd8fab56d0277dc91306a5636a7994715"'
29
+ Expires:
30
+ - Sat, 01 Jan 2000 00:00:00 GMT
31
+ Pragma:
32
+ - no-cache
33
+ X-Fb-Rev:
34
+ - '880776'
35
+ X-Fb-Debug:
36
+ - Fc7xjOmnC+MtzLHCEOi9yDh3oWYLK/7+pqNc61S25kg=
37
+ Date:
38
+ - Fri, 19 Jul 2013 09:46:15 GMT
39
+ Connection:
40
+ - keep-alive
41
+ Content-Length:
42
+ - '589'
43
+ body:
44
+ encoding: US-ASCII
45
+ string: ! '{"data":[{"name":"Disney Channel","id":6003711659516},{"name":"Disneyland","id":6003361056187},{"name":"Disney","id":6003663755307},{"name":"#The
46
+ Walt Disney Company","description":"Audience: 72,500,000","id":6003270522085},{"name":"#Disneyland","description":"Audience:
47
+ 24,100,000","id":6003274193708},{"name":"#Disney Channel","description":"Audience:
48
+ 17,800,000","id":6003327130854},{"name":"#The Walt Disney Company#Walt Disney
49
+ Pictures","description":"Audience: 14,600,000","id":6004710425192},{"name":"#Walt
50
+ Disney Pictures","description":"Audience: 14,600,000","id":6003050377616}]}'
51
+ http_version:
52
+ recorded_at: Fri, 19 Jul 2013 09:46:19 GMT
53
+ recorded_with: VCR 2.3.0
@@ -0,0 +1,48 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://graph.facebook.com/search?access_token=CAAEvJ5vzhl8BAE7m0kztNbbASKHymRlXoBZCdZCtMsebNEgaR0yOmZCBfeTIXT8MnuV3ZCH5lBDQOcC4S9geswWZBuF707gJ42lV9DHgGILsRaiG2upipiHggl7UZAeDVgBSSsap9s9uv1ghZCxNsmH&q=ick%20spickeby&type=adkeyword
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.7
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - ! '*/*'
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Access-Control-Allow-Origin:
22
+ - ! '*'
23
+ Cache-Control:
24
+ - private, no-cache, no-store, must-revalidate
25
+ Content-Type:
26
+ - application/json; charset=UTF-8
27
+ Etag:
28
+ - ! '"1050253aec7b29caff644806927dabfa81406eee"'
29
+ Expires:
30
+ - Sat, 01 Jan 2000 00:00:00 GMT
31
+ Pragma:
32
+ - no-cache
33
+ X-Fb-Rev:
34
+ - '880776'
35
+ X-Fb-Debug:
36
+ - 9OW6+vCkQZB0G4ete2rLeuHXg2keTeb0soekntO0k0c=
37
+ Date:
38
+ - Fri, 19 Jul 2013 10:16:13 GMT
39
+ Connection:
40
+ - keep-alive
41
+ Content-Length:
42
+ - '11'
43
+ body:
44
+ encoding: US-ASCII
45
+ string: ! '{"data":[]}'
46
+ http_version:
47
+ recorded_at: Fri, 19 Jul 2013 10:16:17 GMT
48
+ recorded_with: VCR 2.3.0
@@ -0,0 +1,49 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://graph.facebook.com/search?access_token=CAAEvJ5vzhl8BAE7m0kztNbbASKHymRlXoBZCdZCtMsebNEgaR0yOmZCBfeTIXT8MnuV3ZCH5lBDQOcC4S9geswWZBuF707gJ42lV9DHgGILsRaiG2upipiHggl7UZAeDVgBSSsap9s9uv1ghZCxNsmH&q=steve%20carell&type=adkeyword
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.7
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - ! '*/*'
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Access-Control-Allow-Origin:
22
+ - ! '*'
23
+ Cache-Control:
24
+ - private, no-cache, no-store, must-revalidate
25
+ Content-Type:
26
+ - application/json; charset=UTF-8
27
+ Etag:
28
+ - ! '"60ec5ec86640fb83726a422cc072f56d0f507bc4"'
29
+ Expires:
30
+ - Sat, 01 Jan 2000 00:00:00 GMT
31
+ Pragma:
32
+ - no-cache
33
+ X-Fb-Rev:
34
+ - '880776'
35
+ X-Fb-Debug:
36
+ - kQjivQ0nnGBYWqGHMoygWVVEMyXLEezpddYiJnNYitM=
37
+ Date:
38
+ - Fri, 19 Jul 2013 10:09:06 GMT
39
+ Connection:
40
+ - keep-alive
41
+ Content-Length:
42
+ - '248'
43
+ body:
44
+ encoding: US-ASCII
45
+ string: ! '{"data":[{"name":"Anything With Steve Carell","id":6003332902745},{"name":"Keep
46
+ Steve Carell On Office","id":6003656406820},{"name":"Steve Carell","id":6004016779789}]}'
47
+ http_version:
48
+ recorded_at: Fri, 19 Jul 2013 10:09:10 GMT
49
+ recorded_with: VCR 2.3.0
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zuck::AdKeyword do
4
+ let(:graph){ Koala::Facebook::API.new('CAAEvJ5vzhl8BAE7m0kztNbbASKHymRlXoBZCdZCtMsebNEgaR0yOmZCBfeTIXT8MnuV3ZCH5lBDQOcC4S9geswWZBuF707gJ42lV9DHgGILsRaiG2upipiHggl7UZAeDVgBSSsap9s9uv1ghZCxNsmH')}
5
+
6
+ it "finds the best keyword with a #" do
7
+ VCR.use_cassette('ad_keyword_search_disney') do
8
+ Zuck::AdKeyword.best_guess(graph, 'disney')[:keyword].should == '#The Walt Disney Company'
9
+ end
10
+ end
11
+
12
+ it "finds the best keyword when no keyword with # is available" do
13
+ VCR.use_cassette('ad_keyword_search_steve_carell') do
14
+ Zuck::AdKeyword.best_guess(graph, 'steve carell')[:keyword].should == 'Steve Carell'
15
+ end
16
+ end
17
+
18
+ it "returns nil when nothing could be found" do
19
+ VCR.use_cassette('ad_keyword_search_nonexistant') do
20
+ Zuck::AdKeyword.best_guess(graph, 'ick spickeby').should be_nil
21
+ end
22
+ end
23
+ end
@@ -3,41 +3,6 @@ require 'spec_helper'
3
3
  describe Zuck::TargetingSpec do
4
4
  let(:ad_account){ "2ijdsfoij" }
5
5
  let(:graph){ mock('koala') }
6
- let(:reach_response){
7
- { # These can probably go since we have
8
- "users" => 23688420, # vcr cassetes with http requests and
9
- "bid_estimations" => [ # responses in place
10
- {
11
- "location" => 3,
12
- "cpc_min" => 37,
13
- "cpc_median" => 44,
14
- "cpc_max" => 57,
15
- "cpm_min" => 6,
16
- "cpm_median" => 12,
17
- "cpm_max" => 33
18
- }
19
- ],
20
- "imp_estimates" => [
21
- ],
22
- "data" => {
23
- "users" => 23688420,
24
- "bid_estimations" => [
25
- {
26
- "location" => 3,
27
- "cpc_min" => 37,
28
- "cpc_median" => 44,
29
- "cpc_max" => 57,
30
- "cpm_min" => 6,
31
- "cpm_median" => 12,
32
- "cpm_max" => 33
33
- }
34
- ],
35
- "imp_estimates" => [
36
- ]
37
- }
38
- }
39
- }
40
-
41
6
 
42
7
  describe "validating keywords" do
43
8
 
@@ -166,7 +131,13 @@ describe Zuck::TargetingSpec do
166
131
  describe "Batch processing" do
167
132
  let(:graph){ Koala::Facebook::API.new('AAAEvJ5vzhl8BAPLr6fQgNy2wdUHDJ7ZAoX9PTZCFnebwuTBZBEqO7lNTVZA3XNsTHPTATpTmVFs6o6Jp1pZAL8ZA54BRBXWYtztVug8bm2BAZDZD') }
168
133
  let(:ad_account){ 'act_10150585630710217' }
134
+ let(:spec_mock){ mock(fetch_reach: {some: :data}) }
169
135
 
136
+ it "fetches each reach" do
137
+ requests = [{some: :thing}] * 51
138
+ Zuck::TargetingSpec.should_receive(:new).exactly(51).and_return spec_mock
139
+ Zuck::TargetingSpec.batch_reaches(graph, ad_account, requests)
140
+ end
170
141
  it "doesn't split up small bunches" do
171
142
  requests = [{some: :thing}] * 50
172
143
  graph.should_receive(:batch).once.and_return([])
@@ -178,5 +149,15 @@ describe Zuck::TargetingSpec do
178
149
  graph.should_receive(:batch).twice.and_return([])
179
150
  Zuck::TargetingSpec.batch_reaches(graph, ad_account, requests)
180
151
  end
152
+
153
+ it "reformats results including errors" do
154
+ responses = [{facebook: :response}, Koala::KoalaError.new]
155
+ requests = [{some: :thing}] * 51
156
+ graph.should_receive(:batch).twice.and_return(responses)
157
+ reaches = Zuck::TargetingSpec.batch_reaches(graph, ad_account, requests)
158
+
159
+ reaches[0][:success].should == true
160
+ reaches[1][:success].should == false
161
+ end
181
162
  end
182
163
  end
data/zuck.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "zuck"
8
- s.version = "0.0.9"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jannis Hermanns"]
12
- s.date = "2013-07-04"
12
+ s.date = "2013-07-19"
13
13
  s.description = "This gem allows to easily access facebook's ads api in ruby. See https://developers.facebook.com/docs/reference/ads-api/"
14
14
  s.email = "jannis@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
34
34
  "lib/zuck/facebook/ad_campaign.rb",
35
35
  "lib/zuck/facebook/ad_creative.rb",
36
36
  "lib/zuck/facebook/ad_group.rb",
37
+ "lib/zuck/facebook/ad_keyword.rb",
37
38
  "lib/zuck/facebook/targeting_spec.rb",
38
39
  "lib/zuck/fb_object.rb",
39
40
  "lib/zuck/fb_object/dsl.rb",
@@ -43,9 +44,13 @@ Gem::Specification.new do |s|
43
44
  "lib/zuck/fb_object/read.rb",
44
45
  "lib/zuck/fb_object/read_only.rb",
45
46
  "lib/zuck/fb_object/write.rb",
47
+ "lib/zuck/helpers.rb",
46
48
  "lib/zuck/koala/koala_methods.rb",
47
49
  "spec/fixtures/a_single_account.yml",
48
50
  "spec/fixtures/a_single_campaign.yml",
51
+ "spec/fixtures/ad_keyword_search_disney.yml",
52
+ "spec/fixtures/ad_keyword_search_nonexistant.yml",
53
+ "spec/fixtures/ad_keyword_search_steve_carell.yml",
49
54
  "spec/fixtures/create_ad_campaign.yml",
50
55
  "spec/fixtures/create_ad_group.yml",
51
56
  "spec/fixtures/delete_ad_group.yml",
@@ -60,6 +65,7 @@ Gem::Specification.new do |s|
60
65
  "spec/fixtures/reach_for_valid_keywords_male_young.yml",
61
66
  "spec/lib/zuck/facebook/ad_account_spec.rb",
62
67
  "spec/lib/zuck/facebook/ad_campaign_spec.rb",
68
+ "spec/lib/zuck/facebook/ad_keyword_spec.rb",
63
69
  "spec/lib/zuck/facebook/targeting_spec_spec.rb",
64
70
  "spec/lib/zuck/fb_object/helpers_spec.rb",
65
71
  "spec/lib/zuck/koala/koala_methods_spec.rb",
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: zuck
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.9
5
+ version: 0.1.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Jannis Hermanns
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-04 00:00:00.000000000 Z
12
+ date: 2013-07-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rvm
@@ -324,6 +324,7 @@ files:
324
324
  - lib/zuck/facebook/ad_campaign.rb
325
325
  - lib/zuck/facebook/ad_creative.rb
326
326
  - lib/zuck/facebook/ad_group.rb
327
+ - lib/zuck/facebook/ad_keyword.rb
327
328
  - lib/zuck/facebook/targeting_spec.rb
328
329
  - lib/zuck/fb_object.rb
329
330
  - lib/zuck/fb_object/dsl.rb
@@ -333,9 +334,13 @@ files:
333
334
  - lib/zuck/fb_object/read.rb
334
335
  - lib/zuck/fb_object/read_only.rb
335
336
  - lib/zuck/fb_object/write.rb
337
+ - lib/zuck/helpers.rb
336
338
  - lib/zuck/koala/koala_methods.rb
337
339
  - spec/fixtures/a_single_account.yml
338
340
  - spec/fixtures/a_single_campaign.yml
341
+ - spec/fixtures/ad_keyword_search_disney.yml
342
+ - spec/fixtures/ad_keyword_search_nonexistant.yml
343
+ - spec/fixtures/ad_keyword_search_steve_carell.yml
339
344
  - spec/fixtures/create_ad_campaign.yml
340
345
  - spec/fixtures/create_ad_group.yml
341
346
  - spec/fixtures/delete_ad_group.yml
@@ -350,6 +355,7 @@ files:
350
355
  - spec/fixtures/reach_for_valid_keywords_male_young.yml
351
356
  - spec/lib/zuck/facebook/ad_account_spec.rb
352
357
  - spec/lib/zuck/facebook/ad_campaign_spec.rb
358
+ - spec/lib/zuck/facebook/ad_keyword_spec.rb
353
359
  - spec/lib/zuck/facebook/targeting_spec_spec.rb
354
360
  - spec/lib/zuck/fb_object/helpers_spec.rb
355
361
  - spec/lib/zuck/koala/koala_methods_spec.rb
@@ -371,7 +377,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
371
377
  - !ruby/object:Gem::Version
372
378
  segments:
373
379
  - 0
374
- hash: 2966043831358265203
380
+ hash: 1542161049211835241
375
381
  version: '0'
376
382
  none: false
377
383
  required_rubygems_version: !ruby/object:Gem::Requirement