zuck 0.0.4

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 (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