zuck 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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