hungrytable-pjc 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.sw*
6
+ .open_table
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2@hungrytable --create
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in hungrytable.gemspec
4
+ gemspec
5
+
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ notification :libnotify
5
+
6
+ guard 'minitest' do
7
+ # with Minitest::Unit
8
+ watch(%r|^test/(.*)_test\.rb|)
9
+ watch(%r|^lib/(.*)/([^/]+)\.rb|) { |m| "test/unit/#{m[1]}/#{m[2]}_test.rb" }
10
+ watch(%r|^test/test_helper\.rb|) { "test" }
11
+
12
+ # with Minitest::Spec
13
+ # watch(%r|^spec/(.*)_spec\.rb|)
14
+ # watch(%r|^lib/(.*)\.rb|) { |m| "spec/#{m[1]}_spec.rb" }
15
+ # watch(%r|^spec/spec_helper\.rb|) { "spec" }
16
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 David Chapmap
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Hungrytable
2
+
3
+ The purpose of this gem is to interact with the [OpenTable](http://www.opentable.com) REST API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'hungrytable'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install hungrytable
18
+
19
+ ## Usage
20
+
21
+ You need to set some environment variable for this gem to work properly:
22
+
23
+ Observe:
24
+
25
+ # In ~/.bashrc
26
+ export OT_PARTNER_ID=<YOUR OPENTABLE PARTNER ID>
27
+ export OT_PARTNER_AUTH=<YOUR OPENTABLE PARTNER AUTH> # For XML feed only...
28
+ export OT_OAUTH_KEY=<YOUR OPENTABLE OAUTH KEY>
29
+ export OT_OAUTH_SECRET=<YOUR OPENTABLE OAUTH SECRET KEY>
30
+
31
+ ## Example Run
32
+
33
+ $ restaurant = Hungrytable::Restaurant.new(82591)
34
+
35
+ > #<Hungrytable::Restaurant:0x0000000032e4098 ... >
36
+
37
+ $ search = Hungrytable::RestaurantSearch.new(restaurant, {date_time: 5.days.from_now, party_size: 3})
38
+
39
+ > #<Hungrytable::RestaurantSearch:0x00000003143388 ... >
40
+
41
+ $ slotlock = Hungrytable::RestaurantSlotlock.new(search)
42
+
43
+ > #<Hungrytable::RestaurantSlotlock:0x00000002973a68 ... >
44
+
45
+ $ reservation = Hungrytable::ReservationMake.new(slotlock, {email_address: 'foo@bar.com', firstname: 'Mike', lastname: 'Jones', phone: '2813308004'})
46
+
47
+
48
+ ## Contributing
49
+
50
+ 1. Fork it
51
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
52
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
53
+ 4. Push to the branch (`git push origin my-new-feature`)
54
+ 5. Create new Pull Request
data/RELEASE_NOTES.md ADDED
@@ -0,0 +1,11 @@
1
+ # Hungrytable - Ruby API Client for the OpenTable REST API
2
+
3
+ ## v0.0.1 (05/11/2012)
4
+
5
+ * Get relevant details about an individual restaurant.
6
+ * Search for availability at an individual restaurant.
7
+
8
+ ### Known Issues
9
+
10
+ OpenTable's API currently returns search results as an unsupported transaction,
11
+ but it is clearly marked as a thing in their API document.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'lib'
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "hungrytable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "hungrytable-pjc"
7
+ s.version = Hungrytable::VERSION
8
+ s.authors = ["David Chapman", "Nicholas Fine"]
9
+ s.email = ["david@isotope11.com", 'nicholas.fine@gmail.com']
10
+ s.homepage = "http://www.isotope11.com/"
11
+ s.summary = %q{Gem to interact with OpenTable's REST API}
12
+ s.description = %q{Gem to interact with OpenTable's REST API}
13
+
14
+ s.rubyforge_project = "hungrytable-pjc"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+
25
+ s.add_runtime_dependency 'json', '~> 1.7.1'
26
+ s.add_runtime_dependency 'activesupport'
27
+ s.add_runtime_dependency 'i18n'
28
+ s.add_runtime_dependency 'curb'
29
+
30
+ s.add_development_dependency 'rake'
31
+ s.add_development_dependency 'minitest'
32
+ s.add_development_dependency 'minitest-reporters'
33
+ s.add_development_dependency 'mocha'
34
+ s.add_development_dependency 'pry'
35
+ s.add_development_dependency 'webmock'
36
+ end
37
+
@@ -0,0 +1,26 @@
1
+ module Hungrytable
2
+ module Config
3
+ extend self
4
+
5
+ def partner_id
6
+ ENV['OT_PARTNER_ID'] || config_error('OT_PARTNER_ID')
7
+ end
8
+
9
+ def oauth_key
10
+ ENV['OT_OAUTH_KEY'] || config_error('OT_OAUTH_KEY')
11
+ end
12
+
13
+ def oauth_secret
14
+ ENV['OT_OAUTH_SECRET'] || config_error('OT_OAUTH_SECRET')
15
+ end
16
+
17
+ def base_url
18
+ 'https://secure.opentable.com/api/otapi_v2.ashx'
19
+ end
20
+
21
+ private
22
+ def config_error var
23
+ raise HungrytableError, "ENV variable #{var} must be set."
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Hungrytable
2
+ class GetRequest < Request
3
+ private
4
+ def auth_header
5
+ Hungrytable::RequestHeader.new(:get, @uri, {}, {})
6
+ end
7
+
8
+ def make_request
9
+ Curl::Easy.perform(@uri) do |curl|
10
+ curl.headers['Authorization'] = auth_header
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Hungrytable
2
+ class PostRequest < Request
3
+ private
4
+ def auth_header
5
+ Hungrytable::RequestHeader.new(:post, @uri, {}, {})
6
+ end
7
+
8
+ def make_request
9
+ Curl::Easy.http_post(@uri, @params.to_query) do |curl|
10
+ curl.headers['Authorization'] = auth_header
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Hungrytable
2
+ class Request
3
+ def initialize uri, params={}
4
+ @uri = Hungrytable::Config.base_url << uri
5
+ @params = params
6
+ end
7
+
8
+ def parsed_response
9
+ JSON.parse(response)
10
+ end
11
+
12
+ private
13
+ def response
14
+ @response ||= make_request.body_str
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Hungrytable
2
+ module RequestExtensions
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+ def request
7
+ @request ||= @requester.new(request_uri, params)
8
+ end
9
+
10
+ def ensure_required_opts
11
+ required_opts.each do |key|
12
+ raise ArgumentError, "options must include a value for #{key}" unless opts.has_key?(key)
13
+ end
14
+ end
15
+
16
+ # Will be overwritten in objects that send post requests
17
+ def params
18
+ {}
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ # Modified from simple_oauth (https://github.com/laserlemon/simple_oauth)
2
+ module Hungrytable
3
+ class RequestHeader
4
+
5
+ ATTRIBUTE_KEYS = %w(consumer_key nonce signature_method timestamp token version).map(&:to_sym)
6
+
7
+ def self.default_options
8
+ {
9
+ :nonce => OpenSSL::Random.random_bytes(16).unpack('H*')[0],
10
+ :signature_method => 'HMAC-SHA1',
11
+ :timestamp => Time.now.to_i.to_s,
12
+ :version => '1.0',
13
+ :consumer_key => Hungrytable::Config.oauth_key,
14
+ :consumer_secret => Hungrytable::Config.oauth_secret,
15
+ :token => ''
16
+ }
17
+ end
18
+
19
+ def self.encode(value)
20
+ URI.encode(value.to_s, /[^a-z0-9\-\.\_\~]/i)
21
+ end
22
+
23
+ def self.decode(value)
24
+ URI.decode(value.to_s)
25
+ end
26
+
27
+ attr_reader :method, :params, :options
28
+
29
+ def initialize(method, url, params, oauth = {})
30
+ @method = method.to_s.upcase
31
+ @uri = URI.parse(url.to_s)
32
+ @uri.scheme = @uri.scheme.downcase
33
+ @uri.normalize!
34
+ @uri.fragment = nil
35
+ @params = params
36
+ @options = self.class.default_options.merge(oauth)
37
+ end
38
+
39
+ def url
40
+ uri = @uri.dup
41
+ uri.query = nil
42
+ uri.to_s
43
+ end
44
+
45
+ def to_s
46
+ %Q(OAuth realm="http://www.opentable.com/", #{normalized_attributes})
47
+ end
48
+
49
+ def valid?(secrets = {})
50
+ original_options = options.dup
51
+ options.merge!(secrets)
52
+ valid = options[:signature] == signature
53
+ options.replace(original_options)
54
+ valid
55
+ end
56
+
57
+ def signed_attributes
58
+ attributes.merge(:oauth_signature => signature)
59
+ end
60
+
61
+ private
62
+
63
+ def normalized_attributes
64
+ signed_attributes.sort_by{|k,v| k.to_s }.map{|k,v| %(#{k}="#{self.class.encode(v)}") }.join(', ')
65
+ end
66
+
67
+ def attributes
68
+ ATTRIBUTE_KEYS.inject({}){|a,k| options.key?(k) ? a.merge(:"oauth_#{k}" => options[k]) : a }
69
+ end
70
+
71
+ def signature
72
+ send(options[:signature_method].downcase.tr('-', '_') + '_signature')
73
+ end
74
+
75
+ def hmac_sha1_signature
76
+ Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, secret, signature_base)).chomp.gsub(/\n/, '')
77
+ end
78
+
79
+ def secret
80
+ options.values_at(:consumer_secret, :token_secret).map{|v| self.class.encode(v) }.join('&')
81
+ end
82
+ alias_method :plaintext_signature, :secret
83
+
84
+ def signature_base
85
+ [method, url, normalized_params].map{|v| self.class.encode(v) }.join('&')
86
+ end
87
+
88
+ def normalized_params
89
+ signature_params.map{|p| p.map{|v| self.class.encode(v) } }.sort.map{|p| p.join('=') }.join('&')
90
+ end
91
+
92
+ def signature_params
93
+ attributes.to_a + params.to_a + url_params
94
+ end
95
+
96
+ def url_params
97
+ CGI.parse(@uri.query || '').inject([]){|p,(k,vs)| p + vs.sort.map{|v| [k, v] } }
98
+ end
99
+
100
+ def rsa_sha1_signature
101
+ Base64.encode64(private_key.sign(OpenSSL::Digest::SHA1.new, signature_base)).chomp.gsub(/\n/, '')
102
+ end
103
+
104
+ def private_key
105
+ OpenSSL::PKey::RSA.new(options[:consumer_secret])
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,30 @@
1
+ module Hungrytable
2
+ class ReservationCancel
3
+ include RequestExtensions
4
+
5
+ attr_reader :opts
6
+
7
+ def initialize opts={}
8
+ @opts = opts
9
+ ensure_required_opts
10
+ @requester = opts[:requester] || GetRequest
11
+ end
12
+
13
+ def successful?
14
+ details["ns:ErrorID"] == "0"
15
+ end
16
+
17
+ private
18
+ def request_uri
19
+ "/reservation/?pid=#{Config.partner_id}&rid=#{opts[:restaurant_id]}&conf=#{opts[:confirmation_number]}&email=#{CGI.escape(opts[:email_address])}"
20
+ end
21
+
22
+ def details
23
+ request.parsed_response["Results"]
24
+ end
25
+
26
+ def required_opts
27
+ %w(email_address confirmation_number restaurant_id).map(&:to_sym)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ module Hungrytable
2
+ class ReservationMake
3
+ include RequestExtensions
4
+
5
+ attr_reader :restaurant_slotlock, :opts
6
+
7
+ def initialize restaurant_slotlock, opts={}
8
+ @opts = opts
9
+ ensure_required_opts
10
+ @requester = opts[:requester] || PostRequest
11
+ @restaurant_slotlock = restaurant_slotlock
12
+ end
13
+
14
+ def successful?
15
+ details["ns:ErrorID"] == "0"
16
+ end
17
+
18
+ def confirmation_number
19
+ return nil unless successful?
20
+ details["ns:ConfirmationNumber"]
21
+ end
22
+
23
+ private
24
+ def required_opts
25
+ %w(email_address firstname lastname phone).map(&:to_sym)
26
+ end
27
+
28
+ def default_options
29
+ {
30
+ 'OTannouncementOption' => '0',
31
+ 'RestaurantEmailOption' => '0',
32
+ 'firsttimediner' => '0',
33
+ 'specialinstructions' => 'Have a great time',
34
+ 'slotlockid' => restaurant_slotlock.slotlock_id
35
+ }.merge(restaurant_slotlock.params)
36
+ end
37
+
38
+ def params
39
+ default_options.merge(opts)
40
+ end
41
+
42
+ def request_uri
43
+ "/reservation/?pid=#{Config.partner_id}&st=0"
44
+ end
45
+
46
+ def details
47
+ request.parsed_response["MakeResults"]
48
+ end
49
+
50
+ end
51
+ end
File without changes
@@ -0,0 +1,58 @@
1
+ module Hungrytable
2
+ class Restaurant
3
+ include RequestExtensions
4
+
5
+ def initialize restaurant_id, opts={}
6
+ @requester = opts[:requester] || GetRequest
7
+ @restaurant_id = restaurant_id
8
+ end
9
+
10
+ def id
11
+ @restaurant_id
12
+ end
13
+
14
+ def valid?
15
+ error_ID == "0"
16
+ end
17
+
18
+ def method_missing meth, *args, &blk
19
+ if %w(
20
+ address
21
+ city
22
+ error_ID
23
+ error_message
24
+ image_link
25
+ latitude
26
+ longitude
27
+ metro_name
28
+ neighborhood_name
29
+ parking
30
+ parking_details
31
+ phone
32
+ postal_code
33
+ price_range
34
+ primary_food_type
35
+ restaurant_description
36
+ restaurant_ID
37
+ restaurant_name
38
+ state
39
+ url
40
+ ).map(&:to_sym).include?(meth)
41
+ return details["ns:#{meth.to_s.camelize.gsub("Id","ID")}"]
42
+ end
43
+ super
44
+ end
45
+
46
+ private
47
+
48
+ def request_uri
49
+ "/restaurant/?pid=#{Config.partner_id}&rid=#{id}"
50
+ end
51
+
52
+ # @return [Hash]
53
+ def details
54
+ request.parsed_response["RestaurantDetailsResults"]
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,78 @@
1
+ module Hungrytable
2
+ class RestaurantSearch
3
+ include RequestExtensions
4
+
5
+ attr_reader :restaurant, :opts
6
+
7
+ def initialize restaurant, opts={}
8
+ @opts = opts
9
+ ensure_required_opts
10
+ @requester = opts[:requester] || GetRequest
11
+ @restaurant = restaurant
12
+ end
13
+
14
+ def valid?
15
+ error_ID == "0"
16
+ end
17
+
18
+ def method_missing meth, *args, &blk
19
+ if %w(
20
+ cuisine_type
21
+ early_security_ID
22
+ early_time
23
+ error_ID
24
+ error_message
25
+ exact_security_ID
26
+ exact_time
27
+ later_security_ID
28
+ later_time
29
+ latitude
30
+ longitude
31
+ neighborhood_name
32
+ restaurant_name
33
+ results_key
34
+ no_times_message
35
+ ).map(&:to_sym).include?(meth)
36
+ return details["ns:#{meth.to_s.camelize.gsub("Id","ID")}"]
37
+ end
38
+ super
39
+ end
40
+
41
+ def ideal_security_id
42
+ return exact_security_ID unless exact_security_ID.nil?
43
+ return early_security_ID unless early_security_ID.nil?
44
+ return later_security_ID unless later_security_ID.nil?
45
+ nil
46
+ end
47
+
48
+ def ideal_time
49
+ return exact_time unless exact_time.nil?
50
+ return early_time unless early_time.nil?
51
+ return later_time unless later_time.nil?
52
+ nil
53
+ end
54
+
55
+ def party_size
56
+ opts[:party_size]
57
+ end
58
+
59
+ private
60
+ def required_opts
61
+ %w(date_time party_size).map(&:to_sym)
62
+ end
63
+
64
+ def encoded_date_time
65
+ #URI.encode(opts[:date_time].strftime("%m/%d/%Y %I:%M %p"), /[^a-z0-9\-\.\_\~]/i)
66
+ URI.encode(opts[:date_time].strftime("%Y-%m-%dT%H:%M"), /[^a-z0-9\-\.\_\~]/i)
67
+ end
68
+
69
+ def request_uri
70
+ "/table/?pid=#{Config.partner_id}&rid=#{restaurant.id}&dt=#{encoded_date_time}&ps=#{party_size}"
71
+ end
72
+
73
+ def details
74
+ request.parsed_response["SearchResults"]
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,49 @@
1
+ module Hungrytable
2
+ class RestaurantSlotlock
3
+ include RequestExtensions
4
+
5
+ attr_reader :restaurant_search
6
+
7
+ def initialize restaurant_search, requester=PostRequest
8
+ @restaurant_search = restaurant_search
9
+ @requester = requester
10
+ end
11
+
12
+ def successful?
13
+ details["ns:ErrorID"] == "0"
14
+ end
15
+
16
+ def errors
17
+ details["ns:ErrorMessage"]
18
+ end
19
+
20
+ def slotlock_id
21
+ return nil unless successful?
22
+ details["ns:SlotLockID"]
23
+ end
24
+
25
+ def params
26
+ {
27
+ 'RID' => restaurant.id,
28
+ 'datetime' => restaurant_search.ideal_time,
29
+ 'partysize' => restaurant_search.party_size,
30
+ 'timesecurityID' => restaurant_search.ideal_security_id,
31
+ 'resultskey' => restaurant_search.results_key
32
+ }
33
+ end
34
+
35
+ private
36
+ def request_uri
37
+ "/slotlock/?pid=#{Config.partner_id}&st=0"
38
+ end
39
+
40
+ def restaurant
41
+ restaurant_search.restaurant
42
+ end
43
+
44
+ def details
45
+ request.parsed_response["SlotLockResults"]
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module Hungrytable
2
+ VERSION = "0.0.7"
3
+ end
@@ -0,0 +1,73 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'cgi'
6
+ require 'curb'
7
+ require 'active_support/core_ext'
8
+ require 'hungrytable/version'
9
+ require 'hungrytable/config'
10
+ require 'hungrytable/request_extensions'
11
+ require 'hungrytable/request_header'
12
+ require 'hungrytable/request'
13
+ require 'hungrytable/get_request'
14
+ require 'hungrytable/post_request'
15
+ require 'hungrytable/restaurant'
16
+ require 'hungrytable/restaurant_search'
17
+ require 'hungrytable/restaurant_slotlock'
18
+ require 'hungrytable/reservation_make'
19
+ require 'hungrytable/reservation_status'
20
+ require 'hungrytable/reservation_cancel'
21
+
22
+ module Hungrytable
23
+ class HungrytableError < StandardError;end
24
+
25
+ def error_codes
26
+ {
27
+ 152 => { message: 'The email address is not valid. Please try again.', name: 'VALIDEMAIL' },
28
+ 160 => { message: "We're sorry but we were unable to complete you request. Our technical team has been notified of the problem and will resolve it shortly. Thank you.", name: "GENERALERROR" },
29
+ 197 => { message: "You must select at least one restaurant to search.", name: "PROVIDERESTAURANT" },
30
+ 202 => { message: "The time you selected has already passed. You may wish to check that your computer clock is correct.", name: "PASSEDTIME" },
31
+ 274 => { message: "Your phone number must be numeric.", name: "NUMERICPHONE" },
32
+ 279 => { message: "Your IP address is not listed as an OpenTable Partner IP.", name: "INVALIDIP" },
33
+ 280 => { message: "Key authentication failure", name: "AUTHENTICATEFAIL" },
34
+ 281 => { message: "The phone length for the number provided was not a valid length for the country code.", name: "INVALIDPHONELENGTH" },
35
+ 282 => { message: "We are currently unable to connect to the restaurant to complete this action. Please try again later.", name: "ERBERROR" },
36
+ 283 => { message: "The time you have chosen for your reservation is no longer available.", name: "RESERVATIONNOTAVAIL" },
37
+ 285 => { message: "Credit Card transactions are not allowed via OT Web Services.", name: "CCNOTALLOWED" },
38
+ 286 => { message: "Large parties are not allowed via OT Web Services.", name: "LARGEPARTYNOTALLOWED" },
39
+ 287 => { message: "The restaurant is currently offline.", name: "RESTOFFLINE" },
40
+ 288 => { message: "The restaurant is currently unreachable.", name: "RESTUNREACHABLE" },
41
+ 289 => { message: "Cancel transaction failed.", name: "CANCELFAIL" },
42
+ 294 => { message: "You have not provided enough information to perform the search.", name: "INSUFFICIENTINFORMATION" },
43
+ 295 => { message: "No restaurants were found in your search. Please try again.", name: "NORESTAURANTSRETURNED" },
44
+ 296 => { message: "Your search produced no times.", name: "NOTIMESMESSAGE" },
45
+ 298 => { message: "The confirmation number is invalid.", name: "VALIDCONFIRMNUMBER" },
46
+ 299 => { message: "The reservation status is not available on reservations older than 30 days.", name: "VALIDSTATUSDATE" },
47
+ 301 => { message: "The user for the reservation request already has a reservation within 2 hours of the requested time.", name: "VALIDDUPRESERVATION" },
48
+ 302 => { message: "No Reservation Activity found.", name: "NORESOHISTORYAVAILABLE" },
49
+ 303 => { message: "Invalid User Email. Please re-enter your UserEmail.", name: "INVALIDUSEREMAIL" },
50
+ 304 => { message: "Invalid User Password. Please re-enter your password.", name: "INVALIDUSERPASSWORD" },
51
+ 305 => { message: "Unable to authenticate user to login. Please try again.", name: "USERAUTHENTICATIONFAIL" },
52
+ 306 => { message: "The entered area to search is too large please limit your search to 20 miles.", name: "SEARCHAREATOOLARGE" },
53
+ 307 => { message: "The email address(es) you have provided are not in the correct format. Please try again.", name: "INVALIDEMAILADDRESSES" },
54
+ 308 => { message: "Your reservation has already been cancelled, please hit the Reload button on your profile to refresh your page", name: "ALREADYCANCELLED" },
55
+ 309 => { message: "You must use SSL for this request.", name: "SSLCONNECTIONREQUIRED" },
56
+ 310 => { message: "The points requested for this time slot are not available.", name: "INVALIDPOINTREQUEST" },
57
+ 311 => { message: "The user account is deactivated.", name: "ACCTDEACTIVATED" },
58
+ 313 => { message: "The requested Restaurant was not found.", name: "INVALIDRESTAURANTID" },
59
+ 314 => { message: "We already have an account registered to <email address>", name: "MATCHACCOUNT" },
60
+ 315 => { message: "A default metro must be provided for a use", name: "PROVIDEMETRO" },
61
+ 316 => { message: "The password must be a minimum of 6 characters.", name: "MALFORMEDPASSWORD" },
62
+ 317 => { message: "The phone length for the number provided was not a valid length for the country code.", name: "INVALIDPHONE" },
63
+ 318 => { message: "Please enter a valid country id for the phone number.", name: "PROVIDEPHONECOUNTRY" },
64
+ 319 => { message: "Please enter a valid country id for the mobile phone number.", name: "PROVIDEMOBILEPHONECOUNTRY" },
65
+ 321 => { message: "We're sorry, but we could not complete your reservation request because an account cannot have more than two confirmed reservations into the same restaurant for the same day.", name: "VALIDTOOMANYSAMEREST" },
66
+ 322 => { message: "The time slot for this reservation is no longer available.", name: "SLOTLOCKUNAVAILABLE" },
67
+ 323 => { message: "The service you are trying to access is currently unavailable, please try again later.", name: "SERVICEUNAVAILABLE" },
68
+ 324 => { message: "The service experienced a timeout. Please try again later.", name: "SERVICETIMEOUT" },
69
+ 325 => { message: "Invalid number of lookback days, the most you can look back is 8 days.", name: "INVALIDLOOKBACKDAYS" },
70
+ 326 => { message: "This service is currently disabled. Please try again later.", name: "CURRENTLYDISABLED" }
71
+ }
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ HTTP/1.1 200 OK
2
+ Status: 200
3
+ Vary: Accept-Encoding
4
+ Content-Type: application/json
5
+
6
+ { "RestaurantDetailsResults": {"ns:Address": null, "ns:City": "799 Market Street", "ns:ErrorID": "0", "ns:ErrorMessage": null, "ns:ImageLink": null, "ns:Latitude": null, "ns:Longitude": null, "ns:MenuURL": null, "ns:MetroID": "1", "ns:MetroName": "Demoland", "ns:NeighborhoodName": "Devland", "ns:Parking": "No", "ns:ParkingDetails": null, "ns:Phone": "4157897485", "ns:PostalCode": "94103", "ns:PriceRange": "$30 and under", "ns:PrimaryFoodType": "American", "ns:ReserveUrl": "1759B23C", "ns:RestaurantDescription": "Coming Soon!", "ns:RestaurantID": "82591", "ns:RestaurantName": "OTConnect-Partner-Rest1", "ns:State": "CA", "ns:Url": null, "xmlns:ns": "com.opentable.webservices.v2" }}
@@ -0,0 +1,7 @@
1
+ HTTP/1.1 200 OK
2
+ Status: 200
3
+ Vary: Accept-Encoding
4
+ Content-Type: application/json
5
+
6
+ {"SearchResults": {"ns:CuisineType": "American", "ns:EarlyPoints": "100", "ns:EarlySecurityID": "1113043844", "ns:EarlyTime": "5/16/2012 1:45:00 PM", "ns:ErrorID": "0", "ns:ErrorMessage": null, "ns:ExactPoints": "100", "ns:ExactSecurityID": "1113043836", "ns:ExactTime": "5/16/2012 2:00:00 PM", "ns:ImageExists": "0", "ns:Latitude": null, "ns:Longitude": null, "ns:NeighborhoodName": "Devland", "ns:Price": "$$", "ns:RestaurantID": "82591", "ns:RestaurantName": "OTConnect-Partner-Rest1", "ns:ResultsKey": "z0OlOTkH2NbY%2bq7bjf7Lxw%3d%3d", "xmlns:ns": "com.opentable.webservices.v2" }}
7
+
@@ -0,0 +1,18 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/reporters'
3
+
4
+ require 'ostruct'
5
+ require 'pry'
6
+ require 'webmock'
7
+ include WebMock::API
8
+
9
+
10
+ # Load support files
11
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
12
+
13
+ # Set up minitest
14
+ MiniTest::Unit.runner = MiniTest::SuiteRunner.new
15
+ MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
16
+
17
+ require 'mocha'
18
+ require File.expand_path("../../lib/hungrytable.rb", __FILE__)
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+ describe Hungrytable::Config do
4
+ describe 'when the correct ENV vars are not set' do
5
+ before do
6
+ ENV['OT_PARTNER_ID'] = nil
7
+ ENV['OT_OAUTH_KEY'] = nil
8
+ ENV['OT_OAUTH_SECRET'] = nil
9
+ end
10
+
11
+ it 'raises a HungrytableError' do
12
+ [:partner_id, :oauth_key, :oauth_secret].each do |meth|
13
+ -> {Hungrytable::Config.send meth}.must_raise Hungrytable::HungrytableError
14
+ end
15
+ end
16
+ end
17
+
18
+ describe 'when the ENV vars are set' do
19
+ before do
20
+ ENV['OT_PARTNER_ID'] = 'foo'
21
+ ENV['OT_OAUTH_KEY'] = 'bar'
22
+ ENV['OT_OAUTH_SECRET'] = 'baz'
23
+ end
24
+
25
+ describe '.partner_id' do
26
+ it 'returns the value for OT_PARTNER_ID' do
27
+ Hungrytable::Config.partner_id.must_equal 'foo'
28
+ end
29
+ end
30
+
31
+ describe '.oauth_key' do
32
+ it 'returns the value for OT_OAUTH_KEY' do
33
+ Hungrytable::Config.oauth_key.must_equal 'bar'
34
+ end
35
+ end
36
+
37
+ describe '.oauth_secret' do
38
+ it 'returns the value for OT_OAUTH_SECRET' do
39
+ Hungrytable::Config.oauth_secret.must_equal 'baz'
40
+ end
41
+ end
42
+ end
43
+ end
File without changes
@@ -0,0 +1,28 @@
1
+ =begin
2
+ require 'test_helper'
3
+
4
+ describe Hungrytable::User do
5
+ subject { Hungrytable::User }
6
+
7
+ it 'should be a class' do
8
+ subject.class.must_be_kind_of Class
9
+ end
10
+
11
+ %w{ login }.each do |meth|
12
+ it "should respond to #{meth}" do
13
+ assert subject.new.respond_to?(meth.to_sym), true
14
+ end
15
+ end
16
+
17
+ describe 'users' do
18
+ before do
19
+ stub_request(:get, /.*secure.opentable.com\/api\/otapi_v2.ashx\/user.*/).to_return(File.new('./test/user_login_result.json'), :status => 200)
20
+ @user = subject.new
21
+ end
22
+ it 'should be able to login' do
23
+ response = @user.login({ email: 'bob@webservices.com', password: 'password' })
24
+ response.must_be_kind_of Hash
25
+ end
26
+ end
27
+ end
28
+ =end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,39 @@
1
+ require 'test_helper'
2
+ require 'pry'
3
+
4
+ describe Hungrytable::Restaurant do
5
+ before do
6
+ @mock_requester = mock('GetRequest')
7
+ @mock_request = mock('get')
8
+ @mock_requester.expects(:new).returns(@mock_request)
9
+ @mock_request.stubs(:parsed_response).returns({"RestaurantDetailsResults" => {}})
10
+ ENV['OT_PARTNER_ID'] = 'foo'
11
+ @restaurant = Hungrytable::Restaurant.new(1, requester: @mock_requester)
12
+ end
13
+ %w(
14
+ address
15
+ city
16
+ error_ID
17
+ error_message
18
+ image_link
19
+ latitude
20
+ longitude
21
+ metro_name
22
+ neighborhood_name
23
+ parking
24
+ parking_details
25
+ phone
26
+ postal_code
27
+ price_range
28
+ primary_food_type
29
+ restaurant_description
30
+ restaurant_ID
31
+ restaurant_name
32
+ state
33
+ url
34
+ ).map(&:to_sym).each do |meth|
35
+ it "responds to #{meth.to_s} via method_missing" do
36
+ @restaurant.send(meth).must_equal nil
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ HTTP/1.1 200 OK
2
+ Status: 200
3
+ Vary: Accept-Encoding
4
+ Content-Type: application/json
5
+
6
+ { "LoginResults": {"ns:ErrorID": "303", "ns:ErrorMessage": "Invalid User Email. Please re-enter your UserEmail.", "ns:IsAdmin": "0", "xmlns:ns": "com.opentable.webservices.v2" }}
metadata ADDED
@@ -0,0 +1,258 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hungrytable-pjc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Chapman
9
+ - Nicholas Fine
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-12-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 1.7.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 1.7.1
31
+ - !ruby/object:Gem::Dependency
32
+ name: activesupport
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: i18n
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: curb
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: rake
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: minitest
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest-reporters
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: mocha
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: pry
145
+ requirement: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ! '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: webmock
161
+ requirement: !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ type: :development
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ description: Gem to interact with OpenTable's REST API
176
+ email:
177
+ - david@isotope11.com
178
+ - nicholas.fine@gmail.com
179
+ executables: []
180
+ extensions: []
181
+ extra_rdoc_files: []
182
+ files:
183
+ - .gitignore
184
+ - .rvmrc
185
+ - Gemfile
186
+ - Guardfile
187
+ - LICENSE
188
+ - README.md
189
+ - RELEASE_NOTES.md
190
+ - Rakefile
191
+ - hungrytable.gemspec
192
+ - lib/hungrytable.rb
193
+ - lib/hungrytable/config.rb
194
+ - lib/hungrytable/get_request.rb
195
+ - lib/hungrytable/post_request.rb
196
+ - lib/hungrytable/request.rb
197
+ - lib/hungrytable/request_extensions.rb
198
+ - lib/hungrytable/request_header.rb
199
+ - lib/hungrytable/reservation_cancel.rb
200
+ - lib/hungrytable/reservation_make.rb
201
+ - lib/hungrytable/reservation_status.rb
202
+ - lib/hungrytable/restaurant.rb
203
+ - lib/hungrytable/restaurant_search.rb
204
+ - lib/hungrytable/restaurant_slotlock.rb
205
+ - lib/hungrytable/version.rb
206
+ - test/restaurant_get_details_result.json
207
+ - test/restaurant_search_result.json
208
+ - test/test_helper.rb
209
+ - test/unit/config_test.rb
210
+ - test/unit/get_request_test.rb
211
+ - test/unit/hungrytable/user_test.rb
212
+ - test/unit/post_request_test.rb
213
+ - test/unit/request_test.rb
214
+ - test/unit/reservation_cancel_test.rb
215
+ - test/unit/reservation_make_test.rb
216
+ - test/unit/restaurant_search_test.rb
217
+ - test/unit/restaurant_slotlock_test.rb
218
+ - test/unit/restaurant_test.rb
219
+ - test/user_login_result.json
220
+ homepage: http://www.isotope11.com/
221
+ licenses: []
222
+ post_install_message:
223
+ rdoc_options: []
224
+ require_paths:
225
+ - lib
226
+ required_ruby_version: !ruby/object:Gem::Requirement
227
+ none: false
228
+ requirements:
229
+ - - ! '>='
230
+ - !ruby/object:Gem::Version
231
+ version: '0'
232
+ required_rubygems_version: !ruby/object:Gem::Requirement
233
+ none: false
234
+ requirements:
235
+ - - ! '>='
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
238
+ requirements: []
239
+ rubyforge_project: hungrytable-pjc
240
+ rubygems_version: 1.8.24
241
+ signing_key:
242
+ specification_version: 3
243
+ summary: Gem to interact with OpenTable's REST API
244
+ test_files:
245
+ - test/restaurant_get_details_result.json
246
+ - test/restaurant_search_result.json
247
+ - test/test_helper.rb
248
+ - test/unit/config_test.rb
249
+ - test/unit/get_request_test.rb
250
+ - test/unit/hungrytable/user_test.rb
251
+ - test/unit/post_request_test.rb
252
+ - test/unit/request_test.rb
253
+ - test/unit/reservation_cancel_test.rb
254
+ - test/unit/reservation_make_test.rb
255
+ - test/unit/restaurant_search_test.rb
256
+ - test/unit/restaurant_slotlock_test.rb
257
+ - test/unit/restaurant_test.rb
258
+ - test/user_login_result.json