fourmer 1.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.
- data/.autotest +27 -0
- data/.document +5 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/README.md +124 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/fourmer.rb +20 -0
- data/lib/foursquare/base.rb +32 -0
- data/lib/foursquare/campaign.rb +49 -0
- data/lib/foursquare/campaigns.rb +25 -0
- data/lib/foursquare/consumer.rb +62 -0
- data/lib/foursquare/errors.rb +24 -0
- data/lib/foursquare/model.rb +32 -0
- data/lib/foursquare/requests.rb +49 -0
- data/lib/foursquare/special.rb +39 -0
- data/lib/foursquare/specials.rb +33 -0
- data/lib/foursquare/timeseries.rb +13 -0
- data/lib/foursquare/venue.rb +52 -0
- data/lib/foursquare/venue_group.rb +31 -0
- data/lib/foursquare/venue_groups.rb +26 -0
- data/lib/foursquare/venue_stats.rb +19 -0
- data/lib/foursquare/venues.rb +38 -0
- data/test/helper.rb +19 -0
- data/test/test_base.rb +62 -0
- data/test/test_campaign.rb +36 -0
- data/test/test_campaigns.rb +46 -0
- data/test/test_consumer.rb +107 -0
- data/test/test_errors.rb +17 -0
- data/test/test_fourmer.rb +7 -0
- data/test/test_model.rb +31 -0
- data/test/test_special.rb +28 -0
- data/test/test_specials.rb +71 -0
- data/test/test_timeseries.rb +13 -0
- data/test/test_venue.rb +39 -0
- data/test/test_venue_group.rb +38 -0
- data/test/test_venue_groups.rb +47 -0
- data/test/test_venue_stats.rb +11 -0
- data/test/test_venues.rb +66 -0
- metadata +166 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
module Errors
|
4
|
+
class APIError < StandardError; end
|
5
|
+
class OAuthError < APIError; end
|
6
|
+
|
7
|
+
# Dynamically create errors from responses we receive from the
|
8
|
+
# Foursquare Merchant API
|
9
|
+
def self.new(type, message=nil)
|
10
|
+
unless self.const_defined?(type)
|
11
|
+
self.const_set type.intern, Class.new(APIError) do
|
12
|
+
attr_reader :message
|
13
|
+
|
14
|
+
def initialize(message=nil)
|
15
|
+
@message = message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.const_get(type).new(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class Model < Hashie::Trash
|
5
|
+
include HTTParty
|
6
|
+
include Merchant::Requests
|
7
|
+
|
8
|
+
base_uri Merchant::Requests::API
|
9
|
+
format :json
|
10
|
+
|
11
|
+
attr_reader :consumer
|
12
|
+
def initialize(hash, consumer)
|
13
|
+
@consumer = consumer
|
14
|
+
super(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def listify(venues)
|
19
|
+
case venues
|
20
|
+
when Array
|
21
|
+
venues.join(',')
|
22
|
+
when String
|
23
|
+
venues
|
24
|
+
else
|
25
|
+
raise ArgumentError, "Please ensure you're attempting to use either an " +
|
26
|
+
"array of venue ids, or a comma delimited list of ids"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
module Requests
|
5
|
+
API = 'https://api.foursquare.com/v2'
|
6
|
+
OAUTH = 'https://foursquare.com/oauth2'
|
7
|
+
|
8
|
+
def get(path, params)
|
9
|
+
request = self.class.get(path, {:query => camelize(params).merge(auth_params)})
|
10
|
+
handle_response(request)
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(path, params)
|
14
|
+
request = self.class.post(path, {:body => camelize(params).merge(auth_params)})
|
15
|
+
handle_response(request)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def camel_case(string, capital=true)
|
20
|
+
elem = capital == true ? string.capitalize : string
|
21
|
+
elem.gsub(/(_[a-z])/) { |match| match[1..1].upcase }
|
22
|
+
end
|
23
|
+
|
24
|
+
def camelize(params)
|
25
|
+
params.inject({}) { |o, (k, v)| o[camel_case(k.to_s, false)] = v; o}
|
26
|
+
end
|
27
|
+
|
28
|
+
def auth_params
|
29
|
+
if @consumer.oauth_token
|
30
|
+
{:oauth_token => @consumer.oauth_token }
|
31
|
+
else
|
32
|
+
{:client_id => @consumer.client_id, :client_secret => @consumer.client_secret }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle_response(request)
|
37
|
+
response = request.parsed_response
|
38
|
+
meta = response['meta']
|
39
|
+
|
40
|
+
if meta['code'] != 200
|
41
|
+
type, message = camel_case(meta['errorType']), meta['errorDetail']
|
42
|
+
raise Merchant::Errors.new(type, message)
|
43
|
+
end
|
44
|
+
|
45
|
+
response['response']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class Special < Model
|
5
|
+
property :count1
|
6
|
+
property :count2
|
7
|
+
property :count3
|
8
|
+
property :description
|
9
|
+
property :detail
|
10
|
+
property :fine_print, :from => :finePrint
|
11
|
+
property :friends_here, :from => :friendsHere
|
12
|
+
property :icon
|
13
|
+
property :id
|
14
|
+
property :interaction
|
15
|
+
property :message
|
16
|
+
property :name
|
17
|
+
property :progress
|
18
|
+
property :progress_description, :from => :progressDescription
|
19
|
+
property :provider
|
20
|
+
property :redemption
|
21
|
+
property :state
|
22
|
+
property :status
|
23
|
+
property :target
|
24
|
+
property :text
|
25
|
+
property :title
|
26
|
+
property :type
|
27
|
+
property :unlocked
|
28
|
+
|
29
|
+
def retire
|
30
|
+
self.post("/specials/#{id}/retire", {})
|
31
|
+
end
|
32
|
+
|
33
|
+
def configuration
|
34
|
+
self.class.new(self.get("/specials/#{id}/configuration", {})['special'], @consumer)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class Specials < Base
|
5
|
+
base_uri "#{API}/specials"
|
6
|
+
|
7
|
+
def find(special_id, params={})
|
8
|
+
response = self.get("/#{special_id}", params)
|
9
|
+
Foursquare::Merchant::Special.new(response['special'], @consumer)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(params)
|
13
|
+
response = self.post("/add", params)
|
14
|
+
Foursquare::Merchant::Special.new(response['special'], @consumer)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list(params={})
|
18
|
+
response = self.get("/list", params)
|
19
|
+
specials = response['specials']['items']
|
20
|
+
specials.map { |item| Foursquare::Merchant::Special.new(item, @consumer) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def search(params)
|
24
|
+
raise "Must include lat/lng string" unless params.has_key? :ll
|
25
|
+
response = self.get("/search", params)
|
26
|
+
specials = response['specials']['items']
|
27
|
+
specials.map { |item| Foursquare::Merchant::Special.new(item, @consumer) }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class TimeSeries < Hashie::Trash
|
5
|
+
property :venue_id, :from => :venueId, :required => true
|
6
|
+
property :total_checkins, :from => :totalCheckins
|
7
|
+
property :new_checkins, :from => :newCheckins
|
8
|
+
property :viewing_users, :from => :viewingUsers
|
9
|
+
property :unlocking_users, :from => :unlockingUsers
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class Venue < Model
|
5
|
+
property :been_here, :from => :beenHere
|
6
|
+
property :canonical_url, :from => :canonicalUrl
|
7
|
+
property :categories
|
8
|
+
property :contact
|
9
|
+
property :created_at, :from => :createdAt
|
10
|
+
property :description
|
11
|
+
property :here_now, :from => :hereNow
|
12
|
+
property :id
|
13
|
+
property :listed
|
14
|
+
property :location
|
15
|
+
property :mayor
|
16
|
+
property :name
|
17
|
+
property :photos
|
18
|
+
property :short_url, :from => :shortUrl
|
19
|
+
property :specials
|
20
|
+
property :stats
|
21
|
+
property :tags
|
22
|
+
property :time_zone, :from => :timeZone
|
23
|
+
property :tips
|
24
|
+
property :todos
|
25
|
+
property :url
|
26
|
+
property :verified
|
27
|
+
|
28
|
+
def fetch
|
29
|
+
response = self.get("/venues/#{id}", {})['venue']
|
30
|
+
self.class.new(response, @consumer)
|
31
|
+
end
|
32
|
+
|
33
|
+
def stats(start_at=nil, end_at=nil)
|
34
|
+
params = {}
|
35
|
+
params[:start_at] = start_at if start_at
|
36
|
+
params[:end_at] = end_at if end_at
|
37
|
+
|
38
|
+
response = self.get("/venues/#{id}/stats", params)['stats']
|
39
|
+
VenueStats.new(response)
|
40
|
+
end
|
41
|
+
|
42
|
+
def edit(params)
|
43
|
+
response = self.post("/venues/#{id}/edit", params)['venue']
|
44
|
+
temp_venue = self.fetch
|
45
|
+
temp_venue.keys.each { |k| self.send("#{k}=", temp_venue.send("#{k}")) }
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class VenueGroup < Model
|
5
|
+
property :id
|
6
|
+
property :name
|
7
|
+
property :venues
|
8
|
+
|
9
|
+
def initialize(hash, consumer)
|
10
|
+
super
|
11
|
+
self.venues = self.venues['items'].map { |item| Venue.new(item, @consumer) } if self.venues
|
12
|
+
end
|
13
|
+
|
14
|
+
def delete
|
15
|
+
self.post("venuegroups/#{id}/delete", {})
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_venue(venue_ids)
|
19
|
+
params = {:venue_ids => listify(venue_ids)}
|
20
|
+
self.post("venuegroups/#{id}/addvenue", params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_venue(venue_ids)
|
24
|
+
params = {:venue_ids => listify(venue_ids)}
|
25
|
+
self.post("venuegroups/#{id}/removevenue", params)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class VenueGroups < Base
|
5
|
+
base_uri "#{API}/venuegroups"
|
6
|
+
|
7
|
+
def find(venue_group_id, params={})
|
8
|
+
response = self.get("/#{venue_group_id}", params)
|
9
|
+
Foursquare::Merchant::VenueGroup.new(response['venueGroup'], @consumer)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(params)
|
13
|
+
response = self.post("/add", params)
|
14
|
+
Foursquare::Merchant::VenueGroup.new(response['venueGroup'], @consumer)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list(params={})
|
18
|
+
response = self.get("/list", params)
|
19
|
+
venue_groups = response['venueGroups']['items']
|
20
|
+
venue_groups.map { |item| Foursquare::Merchant::VenueGroup.new(item, @consumer) }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class VenueStats < Hashie::Trash
|
5
|
+
property :sharing
|
6
|
+
property :total_checkins, :from => :totalCheckins
|
7
|
+
property :new_checkins, :from => :newCheckins
|
8
|
+
property :unique_visitors, :from => :uniqueVisitors
|
9
|
+
property :gender_breakdown, :from => :genderBreakdown
|
10
|
+
property :age_breakdown, :from => :ageBreakdown
|
11
|
+
property :hour_breakdown, :from => :hourBreakdown
|
12
|
+
property :visit_count_histogram, :from => :visitCountHistogram
|
13
|
+
property :top_visitors, :from => :topVisitors
|
14
|
+
property :recent_visitors, :from => :recentVisitors
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Foursquare
|
2
|
+
module Merchant
|
3
|
+
|
4
|
+
class Venues < Base
|
5
|
+
base_uri "#{API}/venues"
|
6
|
+
|
7
|
+
def find(venue_id, params={})
|
8
|
+
response = self.get("/#{venue_id}", params)
|
9
|
+
Foursquare::Merchant::Venue.new(response['venue'], @consumer)
|
10
|
+
end
|
11
|
+
|
12
|
+
def managed(params={})
|
13
|
+
response = self.get("/managed", params)
|
14
|
+
venues = response['venues']
|
15
|
+
venues.map { |item| Foursquare::Merchant::Venue.new(item, @consumer) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def timeseries(venue_ids, start_at=nil, end_at=nil)
|
19
|
+
params = {}
|
20
|
+
params[:venue_id] = listify(venue_ids)
|
21
|
+
params[:start_at] = start_at if start_at
|
22
|
+
params[:end_at] = end_at if end_at
|
23
|
+
|
24
|
+
response = self.get("/timeseries", params)['timeseries']
|
25
|
+
response.map { |ts| TimeSeries.new(ts) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def search(params)
|
29
|
+
raise "Must include lat/lng string" unless params.has_key? :ll
|
30
|
+
response = self.get("/search", params)
|
31
|
+
venues = response['groups'].map { |res| res['items'] }.flatten
|
32
|
+
venues.map { |item| Foursquare::Merchant::Venue.new(item, @consumer) }
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'mocha'
|
12
|
+
require 'shoulda'
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
+
require 'fourmer'
|
17
|
+
|
18
|
+
class Test::Unit::TestCase
|
19
|
+
end
|
data/test/test_base.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
module Foursquare
|
4
|
+
module Merchant
|
5
|
+
module Errors
|
6
|
+
class TestError < APIError; end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class TestBase < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
@consumer = Foursquare::Merchant::Consumer.new('ACCESS_TOKEN')
|
14
|
+
@base = Foursquare::Merchant::Base.new(@consumer)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_api_path
|
18
|
+
assert_equal('https://api.foursquare.com/v2', Foursquare::Merchant::Base::API)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_initialize_with_consumer
|
22
|
+
Foursquare::Merchant::Base.expects(:new).with(@consumer).returns(Foursquare::Merchant::Base)
|
23
|
+
Foursquare::Merchant::Base.new(@consumer)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_get_success
|
27
|
+
response = {'meta' => {'code' => 200}, 'response' => true}
|
28
|
+
Foursquare::Merchant::Base.stubs(:get).returns(stub(:parsed_response => response))
|
29
|
+
assert @base.get('/', {})
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_post_success
|
33
|
+
response = {'meta' => {'code' => 200}, 'response' => true}
|
34
|
+
Foursquare::Merchant::Base.stubs(:post).returns(stub(:parsed_response => response))
|
35
|
+
assert @base.post('/', {})
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_get_failure
|
39
|
+
response = {
|
40
|
+
'meta' => {
|
41
|
+
'code' => 400,
|
42
|
+
'errorType' => 'test_error',
|
43
|
+
'errorDetail' => 'This is a test error'
|
44
|
+
}
|
45
|
+
}
|
46
|
+
Foursquare::Merchant::Base.stubs(:get).returns(stub(:parsed_response => response))
|
47
|
+
assert_raise(Foursquare::Merchant::Errors::TestError) { @base.get('/', {}) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_post_failure
|
51
|
+
response = {
|
52
|
+
'meta' => {
|
53
|
+
'code' => 400,
|
54
|
+
'errorType' => 'test_error',
|
55
|
+
'errorDetail' => 'This is a test error'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
Foursquare::Merchant::Base.stubs(:post).returns(stub(:parsed_response => response))
|
59
|
+
assert_raise(Foursquare::Merchant::Errors::TestError) { @base.post('/', {}) }
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|