lib/mls.rb in mls-0.2.2 vs lib/mls.rb in mls-0.2.3

- old
+ new

@@ -4,163 +4,389 @@ require 'net/https' require 'singleton' require 'yajl' require 'bigdecimal' require 'bigdecimal/util' -require 'active_support' -require 'active_resource' +require 'active_support/core_ext' require 'date' require 'time' -class BigDecimal - old_to_s = instance_method :to_s - - define_method :to_s do |param='F'| - old_to_s.bind(self).(param) - end +class Decimal #:nodoc: end -class Decimal +class Boolean #:nodoc: end -class Boolean -end +# _MLS_ is a low-level API. It provides basic HTTP #get, #post, #put, and #delete +# calls to the MLS. It can also provides basic error checking of responses. class MLS include Singleton API_VERSION = '0.1.0' - attr_accessor :url, :api_key, :auth_key, :logger, :asset_host + attr_reader :url + attr_writer :asset_host + attr_accessor :api_key, :auth_key, :logger - def url=(uri) - @url = uri - - uri = URI.parse(uri) - @api_key = CGI.unescape(uri.user) - @host = uri.host - @port = uri.port + # Sets the API Token and Host of the MLS Server + # + # #!ruby + # MLS.url = "https://mls.42floors.com/API_KEY" + def url=(uri) # TODO: testme + @url = URI.parse(uri) + @api_key = CGI.unescape(@url.user) + @host, @port = @url.host, @url.port end - def logger + def logger # TODO: testme @logger ||= default_logger end - def connection + # Returns the current connection to the MLS or if connection has been made + # it returns a new connection + def connection # TODO: testme @connection ||= Net::HTTP.new(@host, @port) end - def asset_host + # provides the asset host, if asset_host is set then it is returned, + # otherwise it queries the MLS for this configuration. + def asset_host # TODO: testme @asset_host ||= get('/asset_host').body end - def add_headers(req) - req['Content-Type'] = 'application/json' - req['X-42Floors-API-Version'] = API_VERSION - req['X-42Floors-API-Key'] = api_key - req['X-42Floors-API-Auth-Key'] = auth_key if auth_key + def headers # TODO: testme + h = { + 'Content-Type' => 'application/json', + 'X-42Floors-API-Version' => API_VERSION, + 'X-42Floors-API-Key' => api_key + } + h['X-42Floors-API-Auth-Key'] = auth_key if auth_key + h end - def put(url, body={}) - req = Net::HTTP::Put.new("/api#{url}") - req.body = Yajl::Encoder.encode(body) - add_headers(req) + def add_headers(req) # TODO: testme + headers.each { |k, v| req[k] = v } + end + + # Gets to +url+ on the MLS Server. Automatically includes any headers returned + # by the MLS#headers function. + # + # Paramaters:: + # + # * +url+ - The +url+ on the server to Get to. This url will automatically + # be prefixed with <tt>"/api"</tt>. To get to <tt>"/api/accounts"</tt> + # pass <tt>"/accounts"</tt> as +url+ + # * +params+ - A Hash or Ruby Object that responds to #to_param. The result + # of this method is appended on the URL as query params + # * +valid_response_codes+ - An Array of HTTP response codes that should be + # considered accepable and not raise exceptions. For example If you don't + # want a MLS::Exception::NotFound to be raised when a GET request returns + # a 404 pass in 404, and the response body will be returned if the status + # code is a 404 as it does if the status code is in the 200..299 rage. Status + # codes in the 200..299 range are *always* considred acceptable + # + # Return Value:: + # + # Returns the return value of the <tt>&block</tt> if given, otherwise the response + # object + # + # Examples: + # + # #!ruby + # MLS.get('/example') # => #<Net::HTTP::Response> + # + # MLS.get('/example', {:body => 'stuff'}) # => #<Net::HTTP::Response> + # + # MLS.get('/404') # => raises MLS::Exception::NotFound + # + # MLS.get('/404', nil, 404, 450..499) # => #<Net::HTTP::Response> + # + # MLS.get('/404', nil, [404, 450..499]) # => #<Net::HTTP::Response> + # + # MLS.get('/404', nil, 404) # => #<Net::HTTP::Response> + # + # # this will still raise an exception if the response_code is not valid + # # and the block will not be called + # MLS.get('/act') do |response, response_code| + # # ... + # end + def get(url, params={}, *valid_response_codes, &block) + params ||= {} + req = Net::HTTP::Get.new("/api#{url}?" + params.to_param) + add_headers(req) + response = connection.request(req) + handle_response(response, valid_response_codes) if block_given? - yield(response.code.to_i, response) + yield(response, response.code.to_i) else - handle_response(response) + response end end - - def post(url, body={}) - req = Net::HTTP::Post.new("/api#{url}") + + # Puts to +url+ on the MLS Server. Automatically includes any headers returned + # by the MLS#headers function. + # + # Paramaters:: + # + # * +url+ - The +url+ on the server to Put to. This url will automatically + # be prefixed with <tt>"/api"</tt>. To put to <tt>"/api/accounts"</tt> + # pass <tt>"/accounts"</tt> as +url+ + # * +body+ - A Ruby object which is converted into JSON and added in the request + # Body. + # * +valid_response_codes+ - An Array of HTTP response codes that should be + # considered accepable and not raise exceptions. For example If you don't + # want a MLS::Exception::NotFound to be raised when a PUT request returns + # a 404 pass in 404, and the response body will be returned if the status + # code is a 404 as it does if the status code is in the 200..299 rage. Status + # codes in the 200..299 range are *always* considred acceptable + # + # Return Value:: + # + # Returns the return value of the <tt>&block</tt> if given, otherwise the response + # object + # + # Examples: + # + # #!ruby + # MLS.put('/example') # => #<Net::HTTP::Response> + # + # MLS.put('/example', {:body => 'stuff'}) # => #<Net::HTTP::Response> + # + # MLS.put('/404') # => raises MLS::Exception::NotFound + # + # MLS.put('/404', nil, 404, 450..499) # => #<Net::HTTP::Response> + # + # MLS.put('/404', nil, [404, 450..499]) # => #<Net::HTTP::Response> + # + # MLS.put('/404', nil, 404) # => #<Net::HTTP::Response> + # + # # this will still raise an exception if the response_code is not valid + # # and the block will not be called + # MLS.put('/act') do |response, response_code| + # # ... + # end + def put(url, body={}, *valid_response_codes, &block) + body ||= {} + + req = Net::HTTP::Put.new("/api#{url}") req.body = Yajl::Encoder.encode(body) add_headers(req) response = connection.request(req) + handle_response(response, valid_response_codes) + if block_given? - yield(response.code.to_i, response) + yield(response, response.code.to_i) else - handle_response(response) + response end - end - - def delete(url, body={}) - req = Net::HTTP::Delete.new("/api#{url}") + end + + # Posts to +url+ on the MLS Server. Automatically includes any headers returned + # by the MLS#headers function. + # + # Paramaters:: + # + # * +url+ - The +url+ on the server to Post to. This url will automatically + # be prefixed with <tt>"/api"</tt>. To post to <tt>"/api/accounts"</tt> + # pass <tt>"/accounts"</tt> as +url+ + # * +body+ - A Ruby object which is converted into JSON and added in the request + # Body. + # * +valid_response_codes+ - An Array of HTTP response codes that should be + # considered accepable and not raise exceptions. For example If you don't + # want a MLS::Exception::NotFound to be raised when a POST request returns + # a 404 pass in 404, and the response body will be returned if the status + # code is a 404 as it does if the status code is in the 200..299 rage. Status + # codes in the 200..299 range are *always* considred acceptable + # + # Return Value:: + # + # Returns the return value of the <tt>&block</tt> if given, otherwise the response + # object + # + # Examples: + # + # #!ruby + # MLS.post('/example') # => #<Net::HTTP::Response> + # + # MLS.post('/example', {:body => 'stuff'}) # => #<Net::HTTP::Response> + # + # MLS.post('/404') # => raises MLS::Exception::NotFound + # + # MLS.post('/404', nil, 404, 450..499) # => #<Net::HTTP::Response> + # + # MLS.post('/404', nil, [404, 450..499]) # => #<Net::HTTP::Response> + # + # MLS.post('/404', nil, 404) # => #<Net::HTTP::Response> + # + # # this will still raise an exception if the response_code is not valid + # # and the block will not be called + # MLS.post('/act') do |response, response_code| + # # ... + # end + def post(url, body={}, *valid_response_codes, &block) + body ||= {} + + req = Net::HTTP::Post.new("/api#{url}") req.body = Yajl::Encoder.encode(body) add_headers(req) response = connection.request(req) + handle_response(response, valid_response_codes) + if block_given? - yield(response.code.to_i, response) + yield(response, response.code.to_i) else - handle_response(response) + response end end - def get(url, params={}) - url = "/api#{url}?" + params.to_param - req = Net::HTTP::Get.new(url) + # Deletes to +url+ on the MLS Server. Automatically includes any headers returned + # by the MLS#headers function. + # + # Paramaters:: + # + # * +url+ - The +url+ on the server to Post to. This url will automatically + # be prefixed with <tt>"/api"</tt>. To delete to <tt>"/api/accounts"</tt> + # pass <tt>"/accounts"</tt> as +url+ + # * +body+ - A Ruby object which is converted into JSON and added in the request + # Body. + # * +valid_response_codes+ - An Array of HTTP response codes that should be + # considered accepable and not raise exceptions. For example If you don't + # want a MLS::Exception::NotFound to be raised when a POST request returns + # a 404 pass in 404, and the response body will be returned if the status + # code is a 404 as it does if the status code is in the 200..299 rage. Status + # codes in the 200..299 range are *always* considred acceptable + # + # Return Value:: + # + # Returns the return value of the <tt>&block</tt> if given, otherwise the + # response object + # + # Examples: + # + # #!ruby + # MLS.delete('/example') # => #<Net::HTTP::Response> + # + # MLS.delete('/example', {:body => 'stuff'}) # => #<Net::HTTP::Response> + # + # MLS.delete('/404') # => raises MLS::Exception::NotFound + # + # MLS.delete('/404', nil, 404, 450..499) # => #<Net::HTTP::Response> + # + # MLS.delete('/404', nil, [404, 450..499]) # => #<Net::HTTP::Response> + # + # MLS.delete('/404', nil, 404) # => #<Net::HTTP::Response> + # + # # this will still raise an exception if the response_code is not valid + # # and the block will not be called + # MLS.delete('/act') do |response, response_code| + # # ... + # end + def delete(url, body={}, *valid_response_codes, &block) + body ||= {} + + req = Net::HTTP::Delete.new("/api#{url}") + req.body = Yajl::Encoder.encode(body) add_headers(req) + response = connection.request(req) + handle_response(response, valid_response_codes) if block_given? - yield(response.code.to_i, response) + yield(response, response.code.to_i) else - handle_response(response) + response end - - response end - def handle_response(response) + # Raise an MLS::Exception based on the response_code, unless the response_code + # is include in the valid_response_codes Array + # + # Paramaters:: + # + # * +response+ - The Net::HTTP::Response object + # * +valid_response_codes+ - An Array, Integer, or Range. If it's Array the + # Array can include both Integers or Ranges. + # + # Return Value:: + # + # If an exception is not raised the +response+ is returned + # + # Examples: + # + # #!ruby + # MLS.handle_response(<Net::HTTP::Response @code=200>) # => <Net::HTTP::Response @code=200> + # + # MLS.handle_response(<Net::HTTP::Response @code=404>) # => raises MLS::Exception::NotFound + # + # MLS.handle_response(<Net::HTTP::Response @code=500>) # => raises MLS::Exception + # + # MLS.handle_response(<Net::HTTP::Response @code=404>, 404) # => <Net::HTTP::Response @code=404> + # + # MLS.handle_response(<Net::HTTP::Response @code=500>, 404, 500) # => <Net::HTTP::Response @code=500> + # + # MLS.handle_response(<Net::HTTP::Response @code=405>, 300, 400..499) # => <Net::HTTP::Response @code=405> + # + # MLS.handle_response(<Net::HTTP::Response @code=405>, [300, 400..499]) # => <Net::HTTP::Response @code=405> + def handle_response(response, *valid_response_codes) if response['X-42Floors-API-Version-Deprecated'] logger.warn("DEPRECATION WARNING: API v#{API_VERSION} is being phased out") end - raise(response.code, response.body) - response.body - end - - def raise(error_code, message=nil) - case error_code.to_i - when 401 - super Unauthorized, message - when 404, 410 - super NotFound, message - when 422 - super ApiVersionUnsupported, message - when 300...400 - super MLS::Exception, error_code - when 400 - super MLS::BadRequest, message - when 401...500 - super MLS::Exception, error_code - when 500...600 - super MLS::Exception, error_code + code = response.code.to_i + valid_response_codes.flatten! + valid_response_codes << (200..299) + + if !valid_response_codes.detect{|i| i.is_a?(Range) ? i.include?(code) : i == code} + case code + when 400 + raise MLS::Exception::BadRequest, response.body + when 401 + raise MLS::Exception::Unauthorized, response.body + when 404, 410 + raise MLS::Exception::NotFound + when 422 + raise MLS::Exception::ApiVersionUnsupported, response.body + when 503 + raise MLS::Exception::ServiceUnavailable, response.body + when 300..599 + raise MLS::Exception, code + end end + + response end - - def ping + + # Ping the MLS. If everything is configured and operating correctly <tt>"pong"</tt> + # will be returned. Otherwise and MLS::Exception should be thrown. + # + # #!ruby + # MLS.ping # => "pong" + # + # MLS.ping # raises MLS::Exception::ServiceUnavailable if a 503 is returned + def ping # TODO: testme get('/ping').body end - def auth_ping + def auth_ping # TODO: testme post('/ping').body end - def default_logger + def default_logger # TODO: testme logger = Logger.new(STDOUT) logger.level = Logger::INFO logger end - def self.method_missing(method, *args, &block) + # Delegates all uncauge class method calls to the singleton + def self.method_missing(method, *args, &block) #:nodoc: # TODO: testme instance.__send__(method, *args, &block) end - def self.parse(json) + def self.parse(json) # TODO: testme Yajl::Parser.new(:symbolize_keys => true).parse(json) end end @@ -173,13 +399,16 @@ require 'mls/properties/fixnum' require 'mls/properties/boolean' require 'mls/properties/decimal' require 'mls/properties/datetime' require 'mls/properties/string' +require 'mls/properties/hash' # Models require 'mls/model' require 'mls/models/account' require 'mls/models/listing' require 'mls/models/address' require 'mls/models/photo' require 'mls/models/tour_request' +require 'mls/models/flyer' +require 'mls/models/area'