Skip to content
This repository has been archived by the owner on Nov 20, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1 from nikz/master
Browse files Browse the repository at this point in the history
0.1 version of Ruby client
  • Loading branch information
barnaclebarnes committed Apr 18, 2012
2 parents 910d4a6 + d6105ba commit 981c7cf
Show file tree
Hide file tree
Showing 19 changed files with 592 additions and 31 deletions.
7 changes: 7 additions & 0 deletions .travis.yml
@@ -0,0 +1,7 @@
language: ruby
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
# uncomment this line if your project needs to run something other than `rake`:
# script: bundle exec rspec spec
53 changes: 50 additions & 3 deletions README.md
@@ -1,5 +1,52 @@
First cut of Realestate.co.nz API Client
Realestate.co.nz API Client for Ruby
====================================

[![Build Status](https://secure.travis-ci.org/nikz/realestate-ruby.png)](http://travis-ci.org/nikz/realestate-ruby)

*NB:* This API Client is currently under active development, things may change rapidly :)

All methods return the parsed JSON from the API, so this is usually an array of hashes. For documentation on
specific elements, see the PDF provided by Realestate.co.nz.

Creating a new client:

client = Realestate::Client.new(:public_key => YOUR_PUBLIC_KEY, :private_key => YOUR_PRIVATE_KEY)
#=> <Realestate::Client...>

Category methods (note that dashes in API paths are converted to underscores for Ruby method names):

client.suburbs
#=> [ { "id" => 3312, "name" => "Fiji" ... } ]

client.districts
#=> [ { "id" => 1, "name" => "Fiji" ... } ]

client.regions
#=> [ { "id" => 34, "name" => "Northland" ... } ]

client.listing_types
#=> [ { "id" => 1, "name" => "Residential" ... } ]

client.pricing_methods
#=> [ { "id" => 1, "name" => "Fixed Price" ... } ]

Listing searching, will concatenate pages automatically. Search paramters as per Realestate.co.nz documentation.

client.listings(:district_id => 1, :format => "id")
#=> [ { "id" : 1234, ... }]

The `Realestate::Image` class represents an image on the image server. You can create images via ID or URL:

image = Realestate::Image.new(12345)
# => <Realestate::Image... >

image = Realestate::Image.new("http://imageserver.realestate.co.nz/1234")
# => <Realestate::Image... >

You can download images using the download method, and the w/h/bg/mode parameters as per the documentation:

image = Realestate::Image.new(12345)
image.download(:w => 200, :h => 100)
# => <IO ... >

<pre>

</pre>
22 changes: 22 additions & 0 deletions Rakefile
@@ -0,0 +1,22 @@
require 'rake'
require 'rake/testtask'

desc 'Default: run unit tests.'
task :default => "test:units"

# FIXME: I'm sure this can be DRY'd up.
namespace :test do
desc 'Runs unit tests for the Realestate Ruby Client'
Rake::TestTask.new(:units) do |t|
t.libs << '.'
t.pattern = 'test/unit/*_test.rb'
t.verbose = true
end

desc 'Runs live integration tests for the Realestate Ruby Client'
Rake::TestTask.new(:remote) do |t|
t.libs << '.'
t.pattern = 'test/remote/*_test.rb'
t.verbose = true
end
end
19 changes: 19 additions & 0 deletions lib/realestate.rb
@@ -0,0 +1,19 @@
require 'active_support/all'
require 'httparty'
require 'digest/md5'
require 'cgi'

module Realestate

# constants
API_VERSION = 1

# exceptions...
class CredentialsRequired < StandardError; end
class AuthenticationError < StandardError; end
class ApiError < StandardError; end

end

require 'realestate/client'
require 'realestate/image'
110 changes: 110 additions & 0 deletions lib/realestate/client.rb
@@ -0,0 +1,110 @@
module Realestate
class Client

include HTTParty
base_uri "http://api.realestate.co.nz/#{Realestate::API_VERSION}/"

attr_accessor :public_key, :private_key

def initialize(options = {})
self.public_key, self.private_key = options.delete(:public_key), options.delete(:private_key)

unless self.public_key.present? && self.private_key.present?
raise CredentialsRequired.new("You must specify your Public and Private keys")
end
end

%w(suburbs districts regions listing_types pricing_methods).each do |cat_name|
define_method(cat_name) do
request(:get, cat_name.dasherize)
end
end

# List / Search listings
def listings(options = {})
# max results is more like "results per page"
options[:max_results] ||= 100
options[:format] ||= "full"

sort_column = options.delete(:sort)
sort_direction = options.delete(:sort_direction)
# -price_min for price_min, descending etc..
options[:sort_order] = "#{"-" if sort_direction == :desc}#{sort_column}" if sort_column.present?

listings = []
more = true
offset = 0

while more do
result = request(:get, "listings", options)

listings = listings + result["listings"]

offset = offset + options[:max_results]
options[:offset] = offset

more = result["more"]
end

listings
end

def listing(id, options = {})
options[:format] ||= "basic"
request(:get, "listings/#{id}", options)
end

private

def request(method, path, params = {})
query = prepare_params(path, params).map { |x| x.join("=") }.join("&")
process_response(self.class.send(method, "/#{path}/", :query => query))
end

# Adds necessary authentication parameters to the passed request parameters,
# and returns the signed set of parameters
#
# These parameters are api_key and api_sig as per the "Authentication" section
# in the API documentation
#
# @param params Hash - the parameters to sign
# @returns Hash - a hash of signed parameters, plus the original params
def prepare_params(request_path, params)
# use multiple instances of the same param name for Array values
params = params.inject([]) do |array, pair|
Array.wrap(pair.last).each do |val|
array << [ pair.first.to_s, CGI.escape(val.to_s) ]
end
array
end

params << ["api_sig", calculate_api_sig(request_path, params)]
params << ["api_key", public_key ]
end

def calculate_api_sig(request_path, params_array)
# Sort your URL argument list into alphabetical (ASCII) order based on the parameter name and value. e.g. a=B, foo=1, bar=2, baz=P, baz=3 sorts to a=B, bar=2, baz=3, baz=P, foo=1
sorted_params = params_array.sort_by { |k, v| "#{k}-#{v}" }

# Concatenate api secret, request path, sorted params
concatenated_string = [ self.private_key, "#{Realestate::API_VERSION}", request_path, sorted_params.join ].join("/")

Digest::MD5.hexdigest(concatenated_string)
end

def process_response(response)
case response.code
when 200..299
response.parsed_response
when 401
raise AuthenticationError.new(response.body)
when 400
raise ApiError.new(response)
else
raise "Unhandled Response Code: #{response.code}: #{response.body}"
end
end


end
end
28 changes: 28 additions & 0 deletions lib/realestate/image.rb
@@ -0,0 +1,28 @@
module Realestate
require 'open-uri'
require 'tempfile'

class Image

@@base_uri = "http://imageserver.realestate.co.nz/id/"
attr_accessor :id

def initialize(id_or_url)
if id_or_url.to_s =~ /\A[0-9]+\Z/
@id = id_or_url.to_i
else
@id = id_or_url.scan(/\/id\/([0-9]+)/).flatten.first.to_i
end
end

def url(options = {})
options.symbolize_keys!.assert_valid_keys(:w, :h, :mode, :bg)
"#{@@base_uri}#{id}#{"?" + options.to_param if options.present?}"
end

def download(options = {})
open(URI.parse(url(options)))
end

end
end
28 changes: 0 additions & 28 deletions realestate-api-ruby-client.gemspec

This file was deleted.

21 changes: 21 additions & 0 deletions realestate-ruby.gemspec
@@ -0,0 +1,21 @@
Gem::Specification.new do |gem|
gem.add_dependency 'rake'
gem.add_dependency 'activesupport', ['>= 2.3.9', '< 4']
gem.add_dependency 'httparty'

gem.add_development_dependency 'test-unit'
gem.add_development_dependency 'shoulda'
gem.add_development_dependency 'webmock'

gem.authors = ["Nik Wakelin", "Jared Armstrong", "Glen Barnes"]
gem.description = %q{A Ruby wrapper for the Realestate.co.nz API.}
gem.summary = "Ruby Realestate.co.nz API Client"
gem.email = ['nik@200square.co.nz', 'jared@200square.co.nz', 'barnaclebarnes@gmail.com']
gem.files = `git ls-files`.split("\n")
gem.homepage = 'https://github.com/realestateconz/realestate-api-ruby-client'
gem.name = 'realestate-ruby'
gem.require_paths = ['lib']
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.version = '0.1.0'
end
2 changes: 2 additions & 0 deletions test/credentials.example.yml
@@ -0,0 +1,2 @@
public_key: YOUR_PUBLIC_KEY
private_key: YOUR_PRIVATE_KEY
1 change: 1 addition & 0 deletions test/fixtures/listing_detail.json
@@ -0,0 +1 @@
{"id":1759612,"teaser":"Vendors Gone!","is_featured":true,"price":{"price":299000,"description":null},"address":{"text":["5 Laurel Lane","Linwood","Christchurch City","Canterbury"]},"location":[172.6656793,-43.5274119],"open_home":false,"listing_subtype_id":1,"listing_type_id":1,"bathrooms":1,"bedrooms":3,"car_spaces":0,"listing_no":"RN3455","url":"http:\/\/www.realestate.co.nz\/1759612","image":"http:\/\/images16.realestate.co.nz\/id\/10490525\/"}
1 change: 1 addition & 0 deletions test/fixtures/listings_page_1.json
@@ -0,0 +1 @@
{"more":true,"count":null,"listings":[{"id":1222760},{"id":1448771},{"id":1569429},{"id":1589099},{"id":1464217},{"id":1464219},{"id":1470184},{"id":1470183},{"id":1226328},{"id":1226331},{"id":1222761},{"id":1140796},{"id":1138830},{"id":1140810},{"id":1138841},{"id":1165304},{"id":961539},{"id":1219636},{"id":1165313},{"id":1020330}]}
1 change: 1 addition & 0 deletions test/fixtures/listings_page_2.json
@@ -0,0 +1 @@
{"more":false,"count":null,"listings":[{"id":959657},{"id":959659},{"id":1673745},{"id":1673743}]}
Binary file added test/fixtures/nyan-cat.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/fixtures/suburbs.json

Large diffs are not rendered by default.

0 comments on commit 981c7cf

Please sign in to comment.