rentjuicer 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +50 -0
  6. data/VERSION +1 -0
  7. data/lib/rentjuicer/client.rb +15 -0
  8. data/lib/rentjuicer/error.rb +17 -0
  9. data/lib/rentjuicer/lead.rb +21 -0
  10. data/lib/rentjuicer/listing.rb +57 -0
  11. data/lib/rentjuicer/listings.rb +86 -0
  12. data/lib/rentjuicer/neighborhoods.rb +16 -0
  13. data/lib/rentjuicer/response.rb +35 -0
  14. data/lib/rentjuicer.rb +13 -0
  15. data/spec/rentjuicer/client_spec.rb +17 -0
  16. data/spec/rentjuicer/error_spec.rb +17 -0
  17. data/spec/rentjuicer/lead_spec.rb +55 -0
  18. data/spec/rentjuicer/listing_spec.rb +97 -0
  19. data/spec/rentjuicer/listings_spec.rb +124 -0
  20. data/spec/rentjuicer/neighborhoods_spec.rb +30 -0
  21. data/spec/rentjuicer/response_spec.rb +51 -0
  22. data/spec/rentjuicer_api_key.yml.example +1 -0
  23. data/spec/responses/error.json +6 -0
  24. data/spec/responses/featured.json +431 -0
  25. data/spec/responses/find_all_1.json +1211 -0
  26. data/spec/responses/find_all_2.json +1294 -0
  27. data/spec/responses/find_all_3.json +808 -0
  28. data/spec/responses/find_all_by_ids.json +1249 -0
  29. data/spec/responses/find_all_by_ids_2.json +186 -0
  30. data/spec/responses/find_by_id.json +128 -0
  31. data/spec/responses/lead.json +8 -0
  32. data/spec/responses/lead_error.json +6 -0
  33. data/spec/responses/listings.json +1301 -0
  34. data/spec/responses/neighborhoods.json +227 -0
  35. data/spec/spec.opts +1 -0
  36. data/spec/spec_helper.rb +12 -0
  37. data/spec/support/listing_helper.rb +126 -0
  38. data/spec/support/webmock_helper.rb +24 -0
  39. metadata +161 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ spec/rentjuicer_api_key.yml
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tom Cocca
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = rentjuicer
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Tom Cocca. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rentjuicer"
8
+ gem.summary = %Q{ruby api wrapper for rentjuice}
9
+ gem.description = %Q{Ruby API wrapper for rentjuice.com built with httparty}
10
+ gem.email = "tom.cocca@gmail.com"
11
+ gem.homepage = "http://github.com/tcocca/rentjuicer"
12
+ gem.authors = ["tcocca"]
13
+ gem.add_dependency "httparty", ">= 0.6.1"
14
+ gem.add_dependency "hashie", ">= 0.3.1"
15
+ gem.add_dependency "rash", ">= 0.1.1"
16
+ gem.add_dependency "will_paginate", ">= 2.3.4"
17
+ gem.add_development_dependency "rspec", ">= 1.2.9"
18
+ gem.add_development_dependency "webmock", ">= 1.3.4"
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:spec) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.spec_files = FileList['spec/**/*_spec.rb']
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :spec => :check_dependencies
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "rentjuicer #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,15 @@
1
+ module Rentjuicer
2
+ class Client
3
+
4
+ include HTTParty
5
+ format :json
6
+
7
+ attr_accessor :api_key
8
+
9
+ def initialize(api_key)
10
+ self.api_key = api_key
11
+ self.class.base_uri "app.rentjuice.com/api/#{self.api_key}"
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Rentjuicer
2
+ class Error < Exception
3
+
4
+ attr_reader :code, :error
5
+
6
+ def initialize(code, error)
7
+ @code = code
8
+ @error = error
9
+ super(message)
10
+ end
11
+
12
+ def message
13
+ "Rentjuicer Error: #{@error} (code: #{@code})"
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Rentjuicer
2
+ class Lead
3
+
4
+ attr_accessor :client, :resource
5
+
6
+ def initialize(client)
7
+ self.client = client
8
+ self.resource = "/leads.add.json"
9
+ end
10
+
11
+ def create(name, params = {}, raise_error = false)
12
+ params.merge!(:name => name)
13
+ Response.new(self.client.class.get(resource, :query => params), raise_error)
14
+ end
15
+
16
+ def create!(name, params = {})
17
+ create(name, params = {}, true)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ module Rentjuicer
2
+ class Listing
3
+
4
+ def initialize(listing)
5
+ listing.keys.each do |key, value|
6
+ self.instance_variable_set('@'+key, listing.send(key))
7
+ self.class.send(:define_method, key, proc{self.instance_variable_get("@#{key}")})
8
+ end
9
+ end
10
+
11
+ def similar_listings(rj, limit = 6)
12
+ search_params = {
13
+ :limit => limit + 1,
14
+ :min_rent => self.rent * 0.9,
15
+ :max_rent => self.rent * 1.1,
16
+ :min_beds => if (self.bedrooms - 1) <= 0 then 0 else (self.bedrooms - 1) end,
17
+ :max_beds => self.bedrooms + 1,
18
+ :min_baths => if (self.bathrooms - 1) <= 0 then 0 else (self.bathrooms - 1) end,
19
+ :max_baths => self.bathrooms + 1,
20
+ :neighborhoods => self.neighborhood_name
21
+ }
22
+
23
+ similar = []
24
+ listings = Rentjuicer::Listings.new(rj)
25
+ listings.search(search_params).properties.each do |prop|
26
+ similar << prop unless prop.id == self.id
27
+ break if similar.size == limit
28
+ end
29
+ similar
30
+ end
31
+
32
+ def id
33
+ rentjuice_id
34
+ end
35
+
36
+ def thumb_pic
37
+ main_pic[:thumbnail] if sorted_photos
38
+ end
39
+
40
+ def first_pic
41
+ main_pic[:fullsize] if sorted_photos
42
+ end
43
+
44
+ def main_pic
45
+ sorted_photos.detect(lambda {return sorted_photos.first}) { |photo| photo[:main_photo] }
46
+ end
47
+
48
+ def sorted_photos
49
+ @sorted_pictures ||= self.photos.sort_by{|photo| photo[:sort_order]} if photos
50
+ end
51
+
52
+ def neighborhood_name
53
+ self.neighborhoods.first unless neighborhoods.blank?
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,86 @@
1
+ module Rentjuicer
2
+ class Listings
3
+
4
+ attr_accessor :client, :resource
5
+
6
+ def initialize(client)
7
+ self.client = client
8
+ self.resource = "/listings.json"
9
+ end
10
+
11
+ def find_by_id(listing_id)
12
+ SearchResponse.new(self.client.class.get(resource, :query => {:rentjuice_id => listing_id}))
13
+ end
14
+
15
+ def search(params = {})
16
+ limit = params[:limit] || 20
17
+ params[:order_by] ||= "rent"
18
+ params[:order_direction] ||= "asc"
19
+ SearchResponse.new(self.client.class.get(resource, :query => params), limit)
20
+ end
21
+
22
+ def featured(params = {})
23
+ params.merge!(:featured => 1)
24
+ search(params)
25
+ end
26
+
27
+ def find_all(params = {})
28
+ per_page = params[:limit] || 20
29
+ all_listings = []
30
+
31
+ response = search(params)
32
+ if response.success?
33
+ all_listings << response.properties
34
+ total_pages = (response.body.total_count/per_page.to_f).ceil
35
+ if total_pages > 1
36
+ (2..total_pages).each do |page_num|
37
+ resp = search(params.merge(:page => page_num))
38
+ if resp.success?
39
+ all_listings << resp.properties
40
+ end
41
+ end
42
+ end
43
+ end
44
+ all_listings.flatten
45
+ end
46
+
47
+ def find_all_by_ids(listing_ids)
48
+ all_listings = []
49
+ listing_ids.split(',').in_groups_of(500).each do |group|
50
+ group.delete_if{|x| x.nil?}
51
+ all_listings << find_all(:rentjuice_id => group.join(','))
52
+ end
53
+ all_listings.flatten
54
+ end
55
+
56
+ class SearchResponse < Rentjuicer::Response
57
+
58
+ attr_accessor :limit, :paginator_cache
59
+
60
+ def initialize(response, limit = 20)
61
+ super(response)
62
+ @limit = limit
63
+ end
64
+
65
+ def properties
66
+ return [] if @body.listings.blank?
67
+ props = []
68
+ @body.listings.each do |listing|
69
+ props << Rentjuicer::Listing.new(listing)
70
+ end
71
+ props
72
+ end
73
+
74
+ def paginator
75
+ paginator_cache if paginator_cache
76
+ self.paginator_cache = WillPaginate::Collection.create(
77
+ @body.page,
78
+ @limit,
79
+ (@body.total_count ? @body.total_count : properties.size)) do |pager|
80
+ pager.replace properties
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,16 @@
1
+ module Rentjuicer
2
+ class Neighborhoods
3
+
4
+ attr_accessor :client, :resource
5
+
6
+ def initialize(client)
7
+ self.client = client
8
+ self.resource = "/neighborhoods.json"
9
+ end
10
+
11
+ def find_all
12
+ Response.new(self.client.class.get(resource))
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ module Rentjuicer
2
+ class Response
3
+
4
+ attr_accessor :body
5
+
6
+ def initialize(response, raise_error = true)
7
+ @body = rash_response(response)
8
+ raise Error.new(@body.code, @body.message) if !success? && raise_error
9
+ end
10
+
11
+ def success?
12
+ @body.status == "ok"
13
+ end
14
+
15
+ private
16
+
17
+ def rash_response(response)
18
+ if response.is_a?(Array)
19
+ self.body = []
20
+ response.each do |b|
21
+ if b.is_a?(Hash)
22
+ self.body << Hashie::Rash.new(b)
23
+ else
24
+ self.body << b
25
+ end
26
+ end
27
+ elsif response.is_a?(Hash)
28
+ self.body = Hashie::Rash.new(response)
29
+ else
30
+ self.body = response
31
+ end
32
+ end
33
+
34
+ end
35
+ end
data/lib/rentjuicer.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'httparty'
3
+ require 'hashie'
4
+ require 'rash'
5
+ require 'will_paginate'
6
+
7
+ require 'rentjuicer/client'
8
+ require 'rentjuicer/response'
9
+ require 'rentjuicer/error'
10
+ require 'rentjuicer/lead'
11
+ require 'rentjuicer/listings'
12
+ require 'rentjuicer/listing'
13
+ require 'rentjuicer/neighborhoods'
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Rentjuicer::Client do
4
+
5
+ before do
6
+ @rentjuicer = new_rentjuicer
7
+ end
8
+
9
+ it "should set the api_key" do
10
+ @rentjuicer.api_key.should == RENTJUICER_API_KEY
11
+ end
12
+
13
+ it "should set the base uri" do
14
+ @rentjuicer.class.base_uri.should == "http://app.rentjuice.com/api/#{RENTJUICER_API_KEY}"
15
+ end
16
+
17
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Rentjuicer::Error do
4
+
5
+ before do
6
+ @rentjuicer = new_rentjuicer
7
+ @neighborhoods = Rentjuicer::Neighborhoods.new(@rentjuicer)
8
+ mock_get(@neighborhoods.resource, 'error.json')
9
+ end
10
+
11
+ it "should return an error" do
12
+ lambda {
13
+ @neighborhoods.find_all
14
+ }.should raise_exception(Rentjuicer::Error, "Rentjuicer Error: Invalid API key. (code: 1)")
15
+ end
16
+
17
+ end
@@ -0,0 +1,55 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Rentjuicer::Lead do
4
+ before do
5
+ @rentjuicer = new_rentjuicer
6
+ @lead = Rentjuicer::Lead.new(@rentjuicer)
7
+ end
8
+
9
+ context "a successfull creation" do
10
+ before do
11
+ mock_get(@lead.resource, 'lead.json', :name => 'Tom Cocca')
12
+ end
13
+
14
+ it "should not return an error on create" do
15
+ lambda {
16
+ @lead.create('Tom Cocca')
17
+ }.should_not raise_exception
18
+ end
19
+
20
+ it "should not return an error on create!" do
21
+ lambda {
22
+ @lead.create!('Tom Cocca')
23
+ }.should_not raise_exception
24
+ end
25
+
26
+ it "should be a success?" do
27
+ @response = @lead.create('Tom Cocca')
28
+ @response.success?.should be_true
29
+ end
30
+ end
31
+
32
+
33
+ context "unsucessful submission" do
34
+ before do
35
+ mock_get(@lead.resource, 'lead_error.json', :name => '')
36
+ end
37
+
38
+ it "should return an error on create!" do
39
+ lambda {
40
+ @lead.create!('')
41
+ }.should raise_exception(Rentjuicer::Error, "Rentjuicer Error: invalid parameter - `name` is required (code: 3)")
42
+ end
43
+
44
+ it "should not return an error on create" do
45
+ lambda {
46
+ @lead.create('')
47
+ }.should_not raise_exception
48
+ end
49
+
50
+ it "should not be a successful submission on create" do
51
+ @result = @lead.create('')
52
+ @result.success?.should be_false
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Rentjuicer::Listing do
4
+
5
+ before do
6
+ @listing = Rentjuicer::Listing.new(valid_listing_rash)
7
+ end
8
+
9
+ it "should create methods from all hash keys" do
10
+ @listing.should respond_to(
11
+ "address", "agent_phone", "bedrooms", "latitude", "title", "photos", "featured", "url", "date_available",
12
+ "square_footage", "agent_email", "rental_terms", "street", "fee", "property_type", "cross_street", "unit_number",
13
+ "custom_fields", "last_updated", "features", "bathrooms", "rent", "neighborhoods", "street_number", "floor_number",
14
+ "longitude", "description", "agent_name", "rentjuice_id"
15
+ )
16
+ end
17
+
18
+ it "should return rentjuice_id for id" do
19
+ @listing.id.should == 200306
20
+ end
21
+
22
+ it "should return the first neighborhood in the array for neighborhood_name" do
23
+ @listing.neighborhood_name.should == 'South Boston'
24
+ end
25
+
26
+ it "should return the thumbnail url of the first sorted pic for thumb_pic" do
27
+ @listing.thumb_pic.should == "http://static.rentjuice.com/frames/2010/08/25/3008760.jpg"
28
+ end
29
+
30
+ it "should return the fullsize url of the first sorted pic for first_pic" do
31
+ @listing.first_pic.should == "http://static.rentjuice.com/frames/2010/08/25/3008757.jpg"
32
+ end
33
+
34
+ it "should return the first element of the sorted photos for main_pic" do
35
+ @listing.main_pic.should == Hashie::Rash.new({
36
+ "thumbnail" => "http://static.rentjuice.com/frames/2010/08/25/3008760.jpg",
37
+ "sort_order" => 1,
38
+ "main_photo" => true,
39
+ "fullsize" => "http://static.rentjuice.com/frames/2010/08/25/3008757.jpg"
40
+ })
41
+ end
42
+
43
+ it "should return an array of photos sorted by the sort_order key for sorted_photos" do
44
+ @listing.sorted_photos.should == sorted_photos_array
45
+ end
46
+
47
+ context "default similar listings" do
48
+ before do
49
+ @rentjuicer = new_rentjuicer
50
+ mock_get('/listings.json', 'listings.json', {
51
+ :neighborhoods => "South Boston",
52
+ :min_rent => "2250.0",
53
+ :max_rent => "2750.0",
54
+ :min_beds => "2",
55
+ :max_beds => "4",
56
+ :min_baths => "0",
57
+ :max_baths => "2",
58
+ :limit => "7",
59
+ :order_by => "rent",
60
+ :order_direction => "asc"
61
+ })
62
+ @similar_props = @listing.similar_listings(@rentjuicer)
63
+ end
64
+
65
+ it "should return an array of listings" do
66
+ @similar_props.should be_kind_of(Array)
67
+ @similar_props.should have_at_most(6).listings
68
+ @similar_props.collect{|x| x.id}.should_not include(@listing.id)
69
+ end
70
+ end
71
+
72
+ context "similar listings with custom limit" do
73
+ before do
74
+ @rentjuicer = new_rentjuicer
75
+ mock_get('/listings.json', 'listings.json', {
76
+ :neighborhoods => "South Boston",
77
+ :min_rent => "2250.0",
78
+ :max_rent => "2750.0",
79
+ :min_beds => "2",
80
+ :max_beds => "4",
81
+ :min_baths => "0",
82
+ :max_baths => "2",
83
+ :limit => "5",
84
+ :order_by => "rent",
85
+ :order_direction => "asc"
86
+ })
87
+ @similar_props = @listing.similar_listings(@rentjuicer, 4)
88
+ end
89
+
90
+ it "should return an array of listings" do
91
+ @similar_props.should be_kind_of(Array)
92
+ @similar_props.should have_at_most(4).listings
93
+ @similar_props.collect{|x| x.id}.should_not include(@listing.id)
94
+ end
95
+ end
96
+
97
+ end