zuck 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.rvmrc +1 -0
  2. data/.travis.yml +7 -0
  3. data/.yardopts +4 -0
  4. data/CHANGELOG.markdown +4 -0
  5. data/Gemfile +35 -0
  6. data/Gemfile.lock +110 -0
  7. data/Guardfile.dist +45 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.markdown +138 -0
  10. data/Rakefile +39 -0
  11. data/VERSION +1 -0
  12. data/console +26 -0
  13. data/lib/zuck/facebook/ad_account.rb +40 -0
  14. data/lib/zuck/facebook/ad_campaign.rb +24 -0
  15. data/lib/zuck/facebook/ad_creative.rb +30 -0
  16. data/lib/zuck/facebook/ad_group.rb +39 -0
  17. data/lib/zuck/facebook/targeting_spec.rb +200 -0
  18. data/lib/zuck/fb_object/dsl.rb +110 -0
  19. data/lib/zuck/fb_object/error.rb +8 -0
  20. data/lib/zuck/fb_object/hash_delegator.rb +111 -0
  21. data/lib/zuck/fb_object/helpers.rb +57 -0
  22. data/lib/zuck/fb_object/read.rb +147 -0
  23. data/lib/zuck/fb_object/read_only.rb +0 -0
  24. data/lib/zuck/fb_object/write.rb +75 -0
  25. data/lib/zuck/fb_object.rb +53 -0
  26. data/lib/zuck/koala/koala_methods.rb +27 -0
  27. data/lib/zuck.rb +9 -0
  28. data/spec/fixtures/a_single_account.yml +75 -0
  29. data/spec/fixtures/a_single_campaign.yml +48 -0
  30. data/spec/fixtures/create_ad_campaign.yml +49 -0
  31. data/spec/fixtures/create_ad_group.yml +47 -0
  32. data/spec/fixtures/delete_ad_group.yml +50 -0
  33. data/spec/fixtures/find_a_single_campaign_and_update_it.yml +247 -0
  34. data/spec/fixtures/list_of_ad_accounts.yml +75 -0
  35. data/spec/fixtures/list_of_ad_campaigns.yml +76 -0
  36. data/spec/fixtures/list_of_ad_creatives.yml +51 -0
  37. data/spec/fixtures/list_of_ad_groups.yml +49 -0
  38. data/spec/fixtures/list_of_all_ad_creatives_of_account.yml +86 -0
  39. data/spec/fixtures/reach_for_invalid_keyword.yml +95 -0
  40. data/spec/fixtures/reach_for_valid_keywords.yml +93 -0
  41. data/spec/fixtures/reach_for_valid_keywords_male_young.yml +93 -0
  42. data/spec/lib/zuck/facebook/ad_account_spec.rb +26 -0
  43. data/spec/lib/zuck/facebook/ad_campaign_spec.rb +4 -0
  44. data/spec/lib/zuck/facebook/targeting_spec_spec.rb +174 -0
  45. data/spec/lib/zuck/fb_object/helpers_spec.rb +67 -0
  46. data/spec/lib/zuck/koala/koala_methods_spec.rb +30 -0
  47. data/spec/lib/zuck/util/hash_delegator_spec.rb +54 -0
  48. data/spec/lib/zuck_spec.rb +165 -0
  49. data/spec/spec_helper.rb +47 -0
  50. data/spec/vcr_setup.rb +15 -0
  51. data/zuck.gemspec +141 -0
  52. metadata +389 -0
@@ -0,0 +1,75 @@
1
+ module Zuck
2
+ module FbObject
3
+ module Write
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ def save
9
+ self.class.raise_if_read_only
10
+
11
+ # Tell facebook to return
12
+ data = @hash_delegator_hash.merge(redownload: 1)
13
+ data = data.stringify_keys
14
+
15
+ # Don't post ids, because facebook doesn't like it
16
+ data = data.keep_if{ |k,v| k != "id" }
17
+
18
+ # Update on facebook
19
+ result = post(graph, path, data)
20
+
21
+ # The data is nested by name and id, e.g.
22
+ #
23
+ # "campaigns" => { "12345" => "data" }
24
+ #
25
+ # Since we only put one at a time, we'll fetch this like that.
26
+ data = result["data"].values.first.values.first
27
+
28
+ # Update and return
29
+ set_data(data)
30
+ result["result"]
31
+ end
32
+
33
+ def destroy
34
+ self.class.destroy(graph, path)
35
+ end
36
+
37
+
38
+ module ClassMethods
39
+
40
+ def raise_if_read_only
41
+ return unless read_only?
42
+ raise Zuck::Error::ReadOnly.new("#{self} is read_only")
43
+ end
44
+
45
+ def create(graph, data, parent=nil, path=nil)
46
+ raise_if_read_only
47
+ p = path || parent.path
48
+
49
+ # We want facebook to return the data of the created object
50
+ data["redownload"] = 1
51
+
52
+ # Create
53
+ result = create_connection(graph, p, list_path, data)["data"]
54
+
55
+ # The data is nested by name and id, e.g.
56
+ #
57
+ # "campaigns" => { "12345" => "data" }
58
+ #
59
+ # Since we only put one at a time, we'll fetch this like that.
60
+ data = result.values.first.values.first
61
+
62
+ # Return a new instance
63
+ new(graph, data, parent)
64
+ end
65
+
66
+ def destroy(graph, id)
67
+ raise_if_read_only
68
+ delete(graph, id)
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,53 @@
1
+ Dir[File.expand_path("../koala/**/*.rb", __FILE__)].each{ |f| require f}
2
+ Dir[File.expand_path("../fb_object/**/*.rb", __FILE__)].each{ |f| require f}
3
+
4
+ module Zuck
5
+ module FbObject
6
+ # An object that includes {Zuck::HashDelegator} for easy hash
7
+ # access and default keys as methods as well as the `graph`
8
+ # getter and setter from {Zuck::Koala::Methods}.
9
+ #
10
+ # By inheriting from this object, each fb object gets implemented
11
+ # automatically (tm) through calling a couple of DSL methods and
12
+ # defining how an object can obtain its own path.
13
+ #
14
+ # I feel it is example time, here's an imaginary ad campaign:
15
+ #
16
+ # class AdCampaign < FbObject
17
+ #
18
+ # known_keys :title, :budget
19
+ # parent_object :ad_account
20
+ # list_path :adcampaigns
21
+ # connections :ad_groups
22
+ #
23
+ # end
24
+ #
25
+ # These handy things are now provided by {FbObject} to your object:
26
+ #
27
+ # 1. Each `AdCampaign` object has a `title` and `budget` method. In
28
+ # case facebook returned more information than what's documented
29
+ # (there are a lot of these), you can still call
30
+ # `my_campaign[:secret_key]` to get to the juicy bits
31
+ # 2. You can call `AdCampaign.all(graph, my_ad_account)`, because your
32
+ # `AdCampaign` instance knows how to construct the path
33
+ # `act_12345/adcampaigns`. It knows this, because it knows its
34
+ # parent object and its own list path.
35
+ # 3. You can call `#ad_groups` on any `AdCampaign` instance to fetch
36
+ # the ad groups in that campaign. To add an ad_group to a campaign,
37
+ # you can call `AdGroup.create(graph, data, my_campaign)`, or for
38
+ # short: `my_campaign.create_ad_group(data)`
39
+ #
40
+ class RawFbObject
41
+ extend Zuck::FbObject::Helpers
42
+ include Zuck::FbObject::Helpers
43
+ include Zuck::HashDelegator
44
+ include Zuck::KoalaMethods
45
+ include Zuck::FbObject::DSL
46
+ include Zuck::FbObject::Read
47
+ include Zuck::FbObject::Write
48
+ end
49
+ end
50
+ end
51
+
52
+ # See #{Zuck::FbObject::RawFbObject}
53
+ Zuck::RawFbObject = Zuck::FbObject::RawFbObject
@@ -0,0 +1,27 @@
1
+ module Zuck
2
+ module KoalaMethods
3
+
4
+ # You can include this to any object that should have a `graph`
5
+ # getter and setter that checks for {::Koala::Facebook::API}
6
+ # instances with an access token.
7
+
8
+ def graph=(g)
9
+ validate_graph(g)
10
+ @graph = g
11
+ end
12
+
13
+ def graph
14
+ @graph
15
+ end
16
+
17
+ private
18
+
19
+ def validate_graph(g)
20
+ e = "#{g.class} is not a Koala::Facebook::API"
21
+ raise e unless g.is_a? ::Koala::Facebook::API
22
+ e = "#{g} does not work without an access_token"
23
+ raise e unless g.access_token
24
+ end
25
+
26
+ end
27
+ end
data/lib/zuck.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'active_support/all'
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'zuck/koala/koala_methods'
4
+ require 'zuck/fb_object'
5
+ Dir[File.expand_path("../zuck/facebook/**/*.rb", __FILE__)].each{ |f| require f}
6
+
7
+ module Zuck
8
+ extend KoalaMethods
9
+ end
@@ -0,0 +1,75 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://graph.facebook.com/act_10150585630710217?access_token=AAAEvJ5vzhl8BABhTSazJZB2D0B4N0l242VX22Hg9J2WZA7fptcAztfXxfAZB9mhZB6W1nl5dz5tXMlb9DJk9ibs6RqtP7PtO6a3XCiHWVwZDZD
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - ! '*/*'
14
+ User-Agent:
15
+ - Ruby
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: !binary |-
20
+ T0s=
21
+ headers:
22
+ !binary "QWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2lu":
23
+ - !binary |-
24
+ Kg==
25
+ !binary "Q2FjaGUtQ29udHJvbA==":
26
+ - !binary |-
27
+ cHJpdmF0ZSwgbm8tY2FjaGUsIG5vLXN0b3JlLCBtdXN0LXJldmFsaWRhdGU=
28
+ !binary "Q29udGVudC1UeXBl":
29
+ - !binary |-
30
+ dGV4dC9qYXZhc2NyaXB0OyBjaGFyc2V0PVVURi04
31
+ !binary "RXRhZw==":
32
+ - !binary |-
33
+ Ijc1NWUyMTBmYjE2N2NkOTQxNWI3ODIzZTZjOGM4YjYwYTQ1MzUwZTUi
34
+ !binary "RXhwaXJlcw==":
35
+ - !binary |-
36
+ U2F0LCAwMSBKYW4gMjAwMCAwMDowMDowMCBHTVQ=
37
+ !binary "UHJhZ21h":
38
+ - !binary |-
39
+ bm8tY2FjaGU=
40
+ !binary "WC1GYi1SZXY=":
41
+ - !binary |-
42
+ NjEyNTY1
43
+ !binary "Q29udGVudC1FbmNvZGluZw==":
44
+ - !binary |-
45
+ Z3ppcA==
46
+ !binary "WC1GYi1EZWJ1Zw==":
47
+ - !binary |-
48
+ L3l3UTJxVWc2L0toZVhhTnFXY21lVFFsSW0vVkFEV09tUFIvQVkzVWsxTT0=
49
+ !binary "RGF0ZQ==":
50
+ - !binary |-
51
+ VHVlLCAyMSBBdWcgMjAxMiAxNDo1MjoyNCBHTVQ=
52
+ !binary "Q29ubmVjdGlvbg==":
53
+ - !binary |-
54
+ a2VlcC1hbGl2ZQ==
55
+ !binary "Q29udGVudC1MZW5ndGg=":
56
+ - !binary |-
57
+ NTA1
58
+ body:
59
+ encoding: ASCII-8BIT
60
+ string: !binary |-
61
+ H4sIAAAAAAAAA6VSTWvcMBD9K0anFgSV7LU361tDQqHQU8mlTRGyLO8OsSWj
62
+ j8Bm2f/e0W7i2MS3nOz39GbmzceJQEtqIlUQnPGSlTdlVbAtZznfEoq8stEE
63
+ cRGtCYwcND7NpD7IED2pOSUqOqeNOqLg4fcdagIM+sUafcm32c6I1zz30dlR
64
+ P3671a4HM4+wXed1EAcbnRcxKFLnlIAXo3beGtmTmlHSRA9Gey9e093CPvsF
65
+ Tzr73jvYH0L28CP7cpBdiGbvG+3VwT1GxvTGPIWvWG2K98FpHdD3JzPkmOLn
66
+ tcReO9Na12mX/dHQ66zi84oKQprT1PjMigxpxB9fXmBMW8lztltkShtzR6Fs
67
+ m+Lu7vHxWQbxtpiCklZCfxR+1KYVPQyAneYlYzjB6HGepP57IjHtqGR8W+At
68
+ VJTgoAfwHqxJ75zmtKAbWtLtP0qc7bEUZ4yfERkboAMlA2oFLi0ATpvUJzJl
69
+ SwDV7PLFwDNNEP/mEGd3hZTgb8WuomKV3SxDyyWslnC3gHzpgs9d5DOP7y6Q
70
+ nUQL9uL4zSuqVrwiO3ldxE6W39litTqyK9WRXVRHvFI97XjWfPUBzpvfrZa/
71
+ WWHPuBolR9lADwF0uo8c76CRvTQKDwPvarBWtLqTsQ94mOYZryxdR5OOLF3C
72
+ +T90T7SjiQQAAA==
73
+ http_version:
74
+ recorded_at: Tue, 21 Aug 2012 14:52:25 GMT
75
+ recorded_with: VCR 2.2.4
@@ -0,0 +1,48 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://graph.facebook.com/6005950787751?access_token=AAAEvJ5vzhl8BABhTSazJZB2D0B4N0l242VX22Hg9J2WZA7fptcAztfXxfAZB9mhZB6W1nl5dz5tXMlb9DJk9ibs6RqtP7PtO6a3XCiHWVwZDZD
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept-Encoding:
11
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
12
+ Accept:
13
+ - ! '*/*'
14
+ User-Agent:
15
+ - Ruby
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
+ - text/javascript; charset=UTF-8
27
+ Etag:
28
+ - ! '"050868a2c006f558b70017a737ca8f72419e7bc1"'
29
+ Expires:
30
+ - Sat, 01 Jan 2000 00:00:00 GMT
31
+ Pragma:
32
+ - no-cache
33
+ X-Fb-Rev:
34
+ - '612565'
35
+ X-Fb-Debug:
36
+ - o5ZT1cHA5d8AdQpUe+7pBF0kc5dqhirTcSEjdEaM7m8=
37
+ Date:
38
+ - Tue, 21 Aug 2012 14:13:01 GMT
39
+ Connection:
40
+ - keep-alive
41
+ Content-Length:
42
+ - '294'
43
+ body:
44
+ encoding: US-ASCII
45
+ string: ! '{"account_id":"10150585630710217","campaign_id":6005950787751,"name":"bloody","daily_budget":1000,"campaign_status":1,"daily_imps":0,"id":"6005950787751","start_time":"2012-08-21T14:04:13+0000","end_time":null,"updated_time":"2012-08-21T14:08:34+0000","created_time":"2012-08-21T14:04:13+0000"}'
46
+ http_version:
47
+ recorded_at: Tue, 21 Aug 2012 14:13:02 GMT
48
+ recorded_with: VCR 2.2.4
@@ -0,0 +1,49 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://graph.facebook.com/act_10150585630710217/adcampaigns
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: !binary |-
9
+ ZGFpbHlfYnVkZ2V0PTEwMDAmbmFtZT1ibG9vZHkmcmVkb3dubG9hZD0xJmFj
10
+ Y2Vzc190b2tlbj1BQUFFdko1dnpobDhCQUJoVFNhekpaQjJEMEI0TjBsMjQy
11
+ VlgyMkhnOUoyV1pBN2ZwdGNBenRmWHhmQVpCOW1oWkI2VzFubDVkejV0WE1s
12
+ YjlESms5aWJzNlJxdFA3UHRPNmEzWENpSFdWd1pEWkQ=
13
+ headers:
14
+ Content-Type:
15
+ - application/x-www-form-urlencoded
16
+ Accept:
17
+ - ! '*/*'
18
+ User-Agent:
19
+ - Ruby
20
+ response:
21
+ status:
22
+ code: 200
23
+ message: OK
24
+ headers:
25
+ Access-Control-Allow-Origin:
26
+ - ! '*'
27
+ Cache-Control:
28
+ - private, no-cache, no-store, must-revalidate
29
+ Content-Type:
30
+ - text/javascript; charset=UTF-8
31
+ Expires:
32
+ - Sat, 01 Jan 2000 00:00:00 GMT
33
+ Pragma:
34
+ - no-cache
35
+ X-Fb-Rev:
36
+ - '613755'
37
+ X-Fb-Debug:
38
+ - VWQPQsWPxgIjh4Npecy0FD6424cPN2uUCWXr8RfiIVQ=
39
+ Date:
40
+ - Tue, 21 Aug 2012 14:04:16 GMT
41
+ Connection:
42
+ - keep-alive
43
+ Content-Length:
44
+ - '308'
45
+ body:
46
+ encoding: US-ASCII
47
+ string: ! '{"id":"6005950787751","data":{"campaigns":{"6005950787751":{"account_id":"10150585630710217","campaign_id":6005950787751,"name":"bloody","daily_budget":1000,"campaign_status":1,"daily_imps":0,"id":"6005950787751","start_time":1345557853,"end_time":null,"updated_time":1345557855,"created_time":1345557853}}}}'
48
+ http_version:
49
+ recorded_at: Tue, 21 Aug 2012 14:04:16 GMT
@@ -0,0 +1,47 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://graph.facebook.com/act_10150585630710217/adgroups
6
+ body:
7
+ encoding: US-ASCII
8
+ string: bid_type=1&max_bid=1&name=Rap+like+me&targeting=%7B%22countries%22%3A%5B%22US%22%5D%7D&creative=%7B%22type%22%3A25%2C%22action_spec%22%3A%7B%22action.type%22%3A%22like%22%2C+%22post%22%3A10150420410887685%7D%7D&campaign_id=6005950787751&redownload=1&access_token=AAAEvJ5vzhl8BABhTSazJZB2D0B4N0l242VX22Hg9J2WZA7fptcAztfXxfAZB9mhZB6W1nl5dz5tXMlb9DJk9ibs6RqtP7PtO6a3XCiHWVwZDZD
9
+ headers:
10
+ Content-Type:
11
+ - application/x-www-form-urlencoded
12
+ Accept:
13
+ - ! '*/*'
14
+ User-Agent:
15
+ - Ruby
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
+ - text/javascript; charset=UTF-8
27
+ Expires:
28
+ - Sat, 01 Jan 2000 00:00:00 GMT
29
+ Pragma:
30
+ - no-cache
31
+ X-Fb-Rev:
32
+ - '612565'
33
+ X-Fb-Debug:
34
+ - dMRAOMEruKynwNICdc9U+JFPheRyF5jVa8rHgupYgWI=
35
+ Date:
36
+ - Tue, 21 Aug 2012 14:05:56 GMT
37
+ Connection:
38
+ - keep-alive
39
+ Content-Length:
40
+ - '1195'
41
+ body:
42
+ encoding: US-ASCII
43
+ string: ! '{"id":"6005950791151","data":{"adgroups":{"6005950791151":{"adgroup_id":6005950791151,"ad_id":6005950791151,"campaign_id":6005950787751,"name":"Rap
44
+ like me","adgroup_status":4,"bid_type":1,"max_bid":"1","bid_info":{"1":"1"},"ad_status":4,"locations":[3],"impression_control_map":[{"location":3,"control":{"impression_control_type":2,"user_impression_limit":3,"user_impression_limit_period":24,"user_impression_limit_period_unit":0}}],"account_id":"10150585630710217","id":"6005950791151","creative_ids":[6005851371551],"targeting":{"countries":["US"],"friends_of_connections":[{"id":"6005851366351","name":null}]},"conversion_specs":[{"action.type":"like","post":"10150420410887685"}],"last_updated_by_app_id":333322143368799,"start_time":null,"end_time":null,"updated_time":1345557956,"created_time":1345557955}},"creatives":{"6005851371551":{"type":25,"action_spec":{"action.type":"like","post":"10150420410887685"},"related_fan_page":1,"cluster_id":6005851366351,"name":"Sponsored
45
+ story #6005851371551","run_status":1,"preview_url":"http:\/\/www.facebook.com\/ads\/api\/creative_preview.php?cid=6005851371551","count_current_adgroups":11,"id":"6005851371551","creative_id":"6005851371551"}}}}'
46
+ http_version:
47
+ recorded_at: Tue, 21 Aug 2012 14:05:56 GMT
@@ -0,0 +1,50 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://graph.facebook.com/6004497038951
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: !binary |-
9
+ YWNjZXNzX3Rva2VuPUFBQUV2SjV2emhsOEJBQmhUU2F6SlpCMkQwQjROMGwy
10
+ NDJWWDIySGc5SjJXWkE3ZnB0Y0F6dGZYeGZBWkI5bWhaQjZXMW5sNWR6NXRY
11
+ TWxiOURKazlpYnM2UnF0UDdQdE82YTNYQ2lIV1Z3WkRaRCZtZXRob2Q9ZGVs
12
+ ZXRl
13
+ headers:
14
+ Content-Type:
15
+ - application/x-www-form-urlencoded
16
+ Accept:
17
+ - ! '*/*'
18
+ User-Agent:
19
+ - Ruby
20
+ response:
21
+ status:
22
+ code: 200
23
+ message: OK
24
+ headers:
25
+ Access-Control-Allow-Origin:
26
+ - ! '*'
27
+ Cache-Control:
28
+ - private, no-cache, no-store, must-revalidate
29
+ Content-Type:
30
+ - text/javascript; charset=UTF-8
31
+ Expires:
32
+ - Sat, 01 Jan 2000 00:00:00 GMT
33
+ Pragma:
34
+ - no-cache
35
+ X-Fb-Rev:
36
+ - '612565'
37
+ X-Fb-Debug:
38
+ - avl/vpqMd+xVIPXir1VXIWHjUEpg4VLpSSFim8G0OJk=
39
+ Date:
40
+ - Tue, 21 Aug 2012 14:11:08 GMT
41
+ Connection:
42
+ - keep-alive
43
+ Content-Length:
44
+ - '4'
45
+ body:
46
+ encoding: US-ASCII
47
+ string: 'true'
48
+ http_version:
49
+ recorded_at: Tue, 21 Aug 2012 14:11:08 GMT
50
+ recorded_with: VCR 2.2.4