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.
@@ -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
@@ -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
@@ -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