hungrytable 0.0.8 → 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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +299 -22
  3. data/RELEASE_NOTES.md +16 -0
  4. data/lib/hungrytable/api_health_monitor.rb +106 -0
  5. data/lib/hungrytable/circuit_breaker.rb +177 -0
  6. data/lib/hungrytable/config.rb +38 -16
  7. data/lib/hungrytable/enhanced_errors.rb +127 -0
  8. data/lib/hungrytable/errors.rb +36 -0
  9. data/lib/hungrytable/get_request.rb +17 -6
  10. data/lib/hungrytable/post_request.rb +17 -6
  11. data/lib/hungrytable/request.rb +42 -5
  12. data/lib/hungrytable/request_extensions.rb +17 -4
  13. data/lib/hungrytable/request_header.rb +25 -25
  14. data/lib/hungrytable/reservation_cancel.rb +19 -5
  15. data/lib/hungrytable/reservation_make.rb +30 -9
  16. data/lib/hungrytable/reservation_status.rb +56 -0
  17. data/lib/hungrytable/restaurant.rb +43 -31
  18. data/lib/hungrytable/restaurant_search.rb +63 -34
  19. data/lib/hungrytable/restaurant_slotlock.rb +32 -10
  20. data/lib/hungrytable/version.rb +3 -1
  21. data/lib/hungrytable.rb +117 -65
  22. metadata +43 -179
  23. data/.gitignore +0 -6
  24. data/.rvmrc +0 -1
  25. data/Gemfile +0 -5
  26. data/Guardfile +0 -16
  27. data/Rakefile +0 -8
  28. data/hungrytable.gemspec +0 -37
  29. data/test/restaurant_get_details_result.json +0 -6
  30. data/test/restaurant_search_result.json +0 -7
  31. data/test/test_helper.rb +0 -18
  32. data/test/unit/config_test.rb +0 -43
  33. data/test/unit/get_request_test.rb +0 -0
  34. data/test/unit/hungrytable/user_test.rb +0 -28
  35. data/test/unit/post_request_test.rb +0 -0
  36. data/test/unit/request_test.rb +0 -0
  37. data/test/unit/reservation_cancel_test.rb +0 -0
  38. data/test/unit/reservation_make_test.rb +0 -0
  39. data/test/unit/restaurant_search_test.rb +0 -0
  40. data/test/unit/restaurant_slotlock_test.rb +0 -0
  41. data/test/unit/restaurant_test.rb +0 -39
  42. data/test/user_login_result.json +0 -6
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hungrytable
4
+ # Checks the status of an existing reservation
5
+ class ReservationStatus
6
+ include RequestExtensions
7
+
8
+ attr_reader :opts
9
+
10
+ def initialize(opts = {})
11
+ @opts = opts
12
+ ensure_required_opts
13
+ @requester = opts[:requester] || GetRequest
14
+ end
15
+
16
+ # @return [Boolean] true if the status request was successful
17
+ def successful?
18
+ details['ns:ErrorID'].to_s == '0'
19
+ end
20
+
21
+ # @return [String] the current status of the reservation
22
+ def status
23
+ return nil unless successful?
24
+
25
+ details['ns:ReservationStatus']
26
+ end
27
+
28
+ # @return [Hash] all reservation details
29
+ def reservation_details
30
+ return nil unless successful?
31
+
32
+ {
33
+ status: details['ns:ReservationStatus'],
34
+ restaurant_name: details['ns:RestaurantName'],
35
+ date_time: details['ns:DateTime'],
36
+ party_size: details['ns:PartySize'],
37
+ confirmation_number: opts[:confirmation_number]
38
+ }
39
+ end
40
+
41
+ private
42
+
43
+ def request_uri
44
+ "/reservationstatus/?pid=#{Config.partner_id}&rid=#{opts[:restaurant_id]}&" \
45
+ "conf=#{CGI.escape(opts[:confirmation_number].to_s)}"
46
+ end
47
+
48
+ def details
49
+ @details ||= request.parsed_response['StatusResults'] || {}
50
+ end
51
+
52
+ def required_opts
53
+ %i[confirmation_number restaurant_id]
54
+ end
55
+ end
56
+ end
@@ -1,46 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hungrytable
4
+ # Represents a restaurant and its details from the OpenTable API
2
5
  class Restaurant
3
6
  include RequestExtensions
4
7
 
5
- def initialize restaurant_id, opts={}
6
- @requester = opts[:requester] || GetRequest
8
+ # Attributes that map directly to API response fields
9
+ ATTRIBUTES = %i[
10
+ address
11
+ city
12
+ error_ID
13
+ error_message
14
+ image_link
15
+ latitude
16
+ longitude
17
+ metro_name
18
+ neighborhood_name
19
+ parking
20
+ parking_details
21
+ phone
22
+ postal_code
23
+ price_range
24
+ primary_food_type
25
+ restaurant_description
26
+ restaurant_ID
27
+ restaurant_name
28
+ state
29
+ url
30
+ ].freeze
31
+
32
+ attr_reader :restaurant_id
33
+
34
+ def initialize(restaurant_id, opts = {})
35
+ @requester = opts[:requester] || GetRequest
7
36
  @restaurant_id = restaurant_id
8
37
  end
9
38
 
39
+ # Alias for consistency
10
40
  def id
11
41
  @restaurant_id
12
42
  end
13
43
 
44
+ # Check if the restaurant query was valid
45
+ # @return [Boolean] true if no errors
14
46
  def valid?
15
- error_ID == "0"
47
+ error_ID.to_s == '0'
16
48
  end
17
49
 
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")}"]
50
+ # Dynamically define getter methods for all attributes
51
+ ATTRIBUTES.each do |attr|
52
+ define_method(attr) do
53
+ # Convert Ruby snake_case to API camelCase, handling special case of ID
54
+ api_key = "ns:#{attr.to_s.camelize.gsub('Id', 'ID')}"
55
+ details[api_key]
42
56
  end
43
- super
44
57
  end
45
58
 
46
59
  private
@@ -49,10 +62,9 @@ module Hungrytable
49
62
  "/restaurant/?pid=#{Config.partner_id}&rid=#{id}"
50
63
  end
51
64
 
52
- # @return [Hash]
65
+ # @return [Hash] restaurant details from API response
53
66
  def details
54
- request.parsed_response["RestaurantDetailsResults"]
67
+ @details ||= request.parsed_response['RestaurantDetailsResults'] || {}
55
68
  end
56
-
57
69
  end
58
70
  end
@@ -1,77 +1,106 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hungrytable
4
+ # Searches for available reservation times at a restaurant
2
5
  class RestaurantSearch
3
6
  include RequestExtensions
4
7
 
8
+ # Attributes that map directly to API response fields
9
+ ATTRIBUTES = %i[
10
+ cuisine_type
11
+ early_security_ID
12
+ early_time
13
+ error_ID
14
+ error_message
15
+ exact_security_ID
16
+ exact_time
17
+ later_security_ID
18
+ later_time
19
+ latitude
20
+ longitude
21
+ neighborhood_name
22
+ restaurant_name
23
+ results_key
24
+ no_times_message
25
+ ].freeze
26
+
5
27
  attr_reader :restaurant, :opts
6
28
 
7
- def initialize restaurant, opts={}
29
+ def initialize(restaurant, opts = {})
8
30
  @opts = opts
9
31
  ensure_required_opts
10
- @requester = opts[:requester] || GetRequest
32
+ validate_date_time_type
33
+ validate_party_size
34
+ @requester = opts[:requester] || GetRequest
11
35
  @restaurant = restaurant
12
36
  end
13
37
 
38
+ # Check if the search was valid
39
+ # @return [Boolean] true if no errors
14
40
  def valid?
15
- error_ID == "0"
41
+ error_ID.to_s == '0'
16
42
  end
17
43
 
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")}"]
44
+ # Dynamically define getter methods for all attributes
45
+ ATTRIBUTES.each do |attr|
46
+ define_method(attr) do
47
+ # Convert Ruby snake_case to API camelCase, handling special case of ID
48
+ api_key = "ns:#{attr.to_s.camelize.gsub('Id', 'ID')}"
49
+ details[api_key]
37
50
  end
38
- super
39
51
  end
40
52
 
53
+ # Get the best available security ID
54
+ # @return [String, nil] the ideal security ID for slotlock
41
55
  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
56
+ exact_security_ID || early_security_ID || later_security_ID
46
57
  end
47
58
 
59
+ # Get the best available time
60
+ # @return [String, nil] the ideal time for reservation
48
61
  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
62
+ exact_time || early_time || later_time
53
63
  end
54
64
 
65
+ # Get the party size for this search
66
+ # @return [Integer] number of people
55
67
  def party_size
56
68
  opts[:party_size]
57
69
  end
58
70
 
59
71
  private
72
+
60
73
  def required_opts
61
- %w(date_time party_size).map(&:to_sym)
74
+ %i[date_time party_size]
62
75
  end
63
76
 
64
77
  def encoded_date_time
65
- URI.encode(opts[:date_time].strftime("%m/%d/%Y %I:%M %p"), /[^a-z0-9\-\.\_\~]/i)
78
+ # Use CGI.escape instead of deprecated URI.encode
79
+ CGI.escape(opts[:date_time].strftime('%m/%d/%Y %I:%M %p'))
66
80
  end
67
81
 
68
82
  def request_uri
69
83
  "/table/?pid=#{Config.partner_id}&rid=#{restaurant.id}&dt=#{encoded_date_time}&ps=#{party_size}"
70
84
  end
71
85
 
86
+ # @return [Hash] search results from API response
72
87
  def details
73
- request.parsed_response["SearchResults"]
88
+ @details ||= request.parsed_response['SearchResults'] || {}
89
+ end
90
+
91
+ def validate_date_time_type
92
+ return if opts[:date_time].respond_to?(:strftime)
93
+
94
+ raise ValidationError,
95
+ "date_time must be a Time or DateTime object, got #{opts[:date_time].class}"
74
96
  end
75
97
 
98
+ def validate_party_size
99
+ ps = opts[:party_size]
100
+ return if ps.is_a?(Integer) && ps.positive? && ps <= 20
101
+
102
+ raise ValidationError,
103
+ "party_size must be a positive integer between 1 and 20, got #{ps.inspect}"
104
+ end
76
105
  end
77
106
  end
@@ -1,38 +1,52 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hungrytable
4
+ # Locks a reservation time slot before making a reservation
2
5
  class RestaurantSlotlock
3
6
  include RequestExtensions
4
7
 
5
8
  attr_reader :restaurant_search
6
9
 
7
- def initialize restaurant_search, requester=PostRequest
10
+ def initialize(restaurant_search, requester = PostRequest)
8
11
  @restaurant_search = restaurant_search
9
- @requester = requester
12
+ @requester = requester
13
+ validate_search_has_results
10
14
  end
11
15
 
16
+ # Check if the slotlock was successful
17
+ # @return [Boolean] true if no errors
12
18
  def successful?
13
- details["ns:ErrorID"] == "0"
19
+ details['ns:ErrorID'].to_s == '0'
14
20
  end
15
21
 
22
+ # Get error messages if slotlock failed
23
+ # @return [String, nil] error message or nil
16
24
  def errors
17
- details["ns:ErrorMessage"]
25
+ details['ns:ErrorMessage']
18
26
  end
19
27
 
28
+ # Get the slotlock ID needed for making a reservation
29
+ # @return [String, nil] slotlock ID or nil if unsuccessful
20
30
  def slotlock_id
21
31
  return nil unless successful?
22
- details["ns:SlotLockID"]
32
+
33
+ details['ns:SlotLockID']
23
34
  end
24
35
 
36
+ # Parameters to send with the slotlock request
37
+ # @return [Hash] request parameters
25
38
  def params
26
39
  {
27
- 'RID' => restaurant.id,
28
- 'datetime' => restaurant_search.ideal_time,
29
- 'partysize' => restaurant_search.party_size,
40
+ 'RID' => restaurant.id,
41
+ 'datetime' => restaurant_search.ideal_time,
42
+ 'partysize' => restaurant_search.party_size,
30
43
  'timesecurityID' => restaurant_search.ideal_security_id,
31
- 'resultskey' => restaurant_search.results_key
44
+ 'resultskey' => restaurant_search.results_key
32
45
  }
33
46
  end
34
47
 
35
48
  private
49
+
36
50
  def request_uri
37
51
  "/slotlock/?pid=#{Config.partner_id}&st=0"
38
52
  end
@@ -41,9 +55,17 @@ module Hungrytable
41
55
  restaurant_search.restaurant
42
56
  end
43
57
 
58
+ # @return [Hash] slotlock results from API response
44
59
  def details
45
- request.parsed_response["SlotLockResults"]
60
+ @details ||= request.parsed_response['SlotLockResults'] || {}
46
61
  end
47
62
 
63
+ def validate_search_has_results
64
+ if restaurant_search.ideal_security_id.nil? ||
65
+ restaurant_search.ideal_time.nil? ||
66
+ restaurant_search.results_key.nil?
67
+ raise ValidationError, 'Cannot create slotlock: no available times found in restaurant search'
68
+ end
69
+ end
48
70
  end
49
71
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hungrytable
2
- VERSION = "0.0.8"
4
+ VERSION = '1.0.0'
3
5
  end
data/lib/hungrytable.rb CHANGED
@@ -1,73 +1,125 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'json'
3
- require 'openssl'
4
- require 'base64'
5
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'
6
+ require 'http'
7
+ require 'oauth'
8
+ require 'active_support'
9
+ require 'active_support/core_ext/string/inflections'
10
+ require 'active_support/concern'
11
+
12
+ require_relative 'hungrytable/version'
13
+ require_relative 'hungrytable/errors'
14
+ require_relative 'hungrytable/config'
15
+
16
+ # Safety features
17
+ require_relative 'hungrytable/circuit_breaker'
18
+ require_relative 'hungrytable/api_health_monitor'
19
+ require_relative 'hungrytable/enhanced_errors'
20
+
21
+ # Core functionality
22
+ require_relative 'hungrytable/request_extensions'
23
+ require_relative 'hungrytable/request_header'
24
+ require_relative 'hungrytable/request'
25
+ require_relative 'hungrytable/get_request'
26
+ require_relative 'hungrytable/post_request'
27
+ require_relative 'hungrytable/restaurant'
28
+ require_relative 'hungrytable/restaurant_search'
29
+ require_relative 'hungrytable/restaurant_slotlock'
30
+ require_relative 'hungrytable/reservation_make'
31
+ require_relative 'hungrytable/reservation_status'
32
+ require_relative 'hungrytable/reservation_cancel'
21
33
 
22
34
  module Hungrytable
23
- class HungrytableError < StandardError;end
35
+ # OpenTable API error codes
36
+ # @return [Hash] mapping of error codes to error details
37
+ ERROR_CODES = {
38
+ 152 => { message: 'The email address is not valid. Please try again.', name: 'VALIDEMAIL' },
39
+ 160 => {
40
+ message: "We're sorry but we were unable to complete you request. Our technical team has been notified " \
41
+ 'of the problem and will resolve it shortly. Thank you.',
42
+ name: 'GENERALERROR'
43
+ },
44
+ 197 => { message: 'You must select at least one restaurant to search.', name: 'PROVIDERESTAURANT' },
45
+ 202 => {
46
+ message: 'The time you selected has already passed. You may wish to check that your computer clock ' \
47
+ 'is correct.',
48
+ name: 'PASSEDTIME'
49
+ },
50
+ 274 => { message: 'Your phone number must be numeric.', name: 'NUMERICPHONE' },
51
+ 279 => { message: 'Your IP address is not listed as an OpenTable Partner IP.', name: 'INVALIDIP' },
52
+ 280 => { message: 'Key authentication failure', name: 'AUTHENTICATEFAIL' },
53
+ 281 => { message: 'The phone length for the number provided was not a valid length for the country code.',
54
+ name: 'INVALIDPHONELENGTH' },
55
+ 282 => {
56
+ message: 'We are currently unable to connect to the restaurant to complete this action. ' \
57
+ 'Please try again later.',
58
+ name: 'ERBERROR'
59
+ },
60
+ 283 => { message: 'The time you have chosen for your reservation is no longer available.',
61
+ name: 'RESERVATIONNOTAVAIL' },
62
+ 285 => { message: 'Credit Card transactions are not allowed via OT Web Services.', name: 'CCNOTALLOWED' },
63
+ 286 => { message: 'Large parties are not allowed via OT Web Services.', name: 'LARGEPARTYNOTALLOWED' },
64
+ 287 => { message: 'The restaurant is currently offline.', name: 'RESTOFFLINE' },
65
+ 288 => { message: 'The restaurant is currently unreachable.', name: 'RESTUNREACHABLE' },
66
+ 289 => { message: 'Cancel transaction failed.', name: 'CANCELFAIL' },
67
+ 294 => { message: 'You have not provided enough information to perform the search.',
68
+ name: 'INSUFFICIENTINFORMATION' },
69
+ 295 => { message: 'No restaurants were found in your search. Please try again.', name: 'NORESTAURANTSRETURNED' },
70
+ 296 => { message: 'Your search produced no times.', name: 'NOTIMESMESSAGE' },
71
+ 298 => { message: 'The confirmation number is invalid.', name: 'VALIDCONFIRMNUMBER' },
72
+ 299 => { message: 'The reservation status is not available on reservations older than 30 days.',
73
+ name: 'VALIDSTATUSDATE' },
74
+ 301 => {
75
+ message: 'The user for the reservation request already has a reservation within 2 hours ' \
76
+ 'of the requested time.',
77
+ name: 'VALIDDUPRESERVATION'
78
+ },
79
+ 302 => { message: 'No Reservation Activity found.', name: 'NORESOHISTORYAVAILABLE' },
80
+ 303 => { message: 'Invalid User Email. Please re-enter your UserEmail.', name: 'INVALIDUSEREMAIL' },
81
+ 304 => { message: 'Invalid User Password. Please re-enter your password.', name: 'INVALIDUSERPASSWORD' },
82
+ 305 => { message: 'Unable to authenticate user to login. Please try again.', name: 'USERAUTHENTICATIONFAIL' },
83
+ 306 => { message: 'The entered area to search is too large please limit your search to 20 miles.',
84
+ name: 'SEARCHAREATOOLARGE' },
85
+ 307 => { message: 'The email address(es) you have provided are not in the correct format. Please try again.',
86
+ name: 'INVALIDEMAILADDRESSES' },
87
+ 308 => {
88
+ message: 'Your reservation has already been cancelled, please hit the Reload button on your profile ' \
89
+ 'to refresh your page',
90
+ name: 'ALREADYCANCELLED'
91
+ },
92
+ 309 => { message: 'You must use SSL for this request.', name: 'SSLCONNECTIONREQUIRED' },
93
+ 310 => { message: 'The points requested for this time slot are not available.', name: 'INVALIDPOINTREQUEST' },
94
+ 311 => { message: 'The user account is deactivated.', name: 'ACCTDEACTIVATED' },
95
+ 313 => { message: 'The requested Restaurant was not found.', name: 'INVALIDRESTAURANTID' },
96
+ 314 => { message: 'We already have an account registered to <email address>', name: 'MATCHACCOUNT' },
97
+ 315 => { message: 'A default metro must be provided for a use', name: 'PROVIDEMETRO' },
98
+ 316 => { message: 'The password must be a minimum of 6 characters.', name: 'MALFORMEDPASSWORD' },
99
+ 317 => { message: 'The phone length for the number provided was not a valid length for the country code.',
100
+ name: 'INVALIDPHONE' },
101
+ 318 => { message: 'Please enter a valid country id for the phone number.', name: 'PROVIDEPHONECOUNTRY' },
102
+ 319 => { message: 'Please enter a valid country id for the mobile phone number.',
103
+ name: 'PROVIDEMOBILEPHONECOUNTRY' },
104
+ 321 => {
105
+ message: "We're sorry, but we could not complete your reservation request because an account cannot " \
106
+ 'have more than two confirmed reservations into the same restaurant for the same day.',
107
+ name: 'VALIDTOOMANYSAMEREST'
108
+ },
109
+ 322 => { message: 'The time slot for this reservation is no longer available.', name: 'SLOTLOCKUNAVAILABLE' },
110
+ 323 => { message: 'The service you are trying to access is currently unavailable, please try again later.',
111
+ name: 'SERVICEUNAVAILABLE' },
112
+ 324 => { message: 'The service experienced a timeout. Please try again later.', name: 'SERVICETIMEOUT' },
113
+ 325 => { message: 'Invalid number of lookback days, the most you can look back is 8 days.',
114
+ name: 'INVALIDLOOKBACKDAYS' },
115
+ 326 => { message: 'This service is currently disabled. Please try again later.', name: 'CURRENTLYDISABLED' }
116
+ }.freeze
24
117
 
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
- }
118
+ class << self
119
+ # Configure the gem programmatically
120
+ # @yield [Config] configuration object
121
+ def configure
122
+ yield Config if block_given?
123
+ end
72
124
  end
73
125
  end