├── .rspec ├── Gemfile ├── .yardopts ├── .gitignore ├── lib ├── rubillow │ ├── version.rb │ ├── models │ │ ├── search_result.rb │ │ ├── property_chart.rb │ │ ├── images.rb │ │ ├── zpidable.rb │ │ ├── region_chart.rb │ │ ├── demographic_value.rb │ │ ├── region_children.rb │ │ ├── chart.rb │ │ ├── comps.rb │ │ ├── posting.rb │ │ ├── deep_comps.rb │ │ ├── linkable.rb │ │ ├── region.rb │ │ ├── property_basics.rb │ │ ├── addressable.rb │ │ ├── rate_summary.rb │ │ ├── deep_search_result.rb │ │ ├── postings.rb │ │ ├── monthly_payments.rb │ │ ├── updated_property_details.rb │ │ ├── zestimateable.rb │ │ └── demographics.rb │ ├── helpers │ │ └── xml_parsing_helper.rb │ ├── configuration.rb │ ├── request.rb │ ├── postings.rb │ ├── models.rb │ ├── mortgage.rb │ ├── property_details.rb │ ├── neighborhood.rb │ └── home_valuation.rb └── rubillow.rb ├── .travis.yml ├── spec ├── rubillow │ ├── models │ │ ├── region_children_spec.rb │ │ ├── postings_spec.rb │ │ ├── rate_summary_spec.rb │ │ ├── monthly_payments_spec.rb │ │ ├── property_chart_spec.rb │ │ ├── posting_spec.rb │ │ ├── region_chart_spec.rb │ │ ├── demographics_spec.rb │ │ ├── updated_property_details_spec.rb │ │ ├── comps_spec.rb │ │ ├── deep_search_results_spec.rb │ │ ├── deep_comps_spec.rb │ │ └── search_result_spec.rb │ ├── rubillow_spec.rb │ ├── configuration_spec.rb │ ├── postings_spec.rb │ ├── models_spec.rb │ ├── mortgage_spec.rb │ ├── neighborhood_spec.rb │ ├── request_spec.rb │ ├── property_details_spec.rb │ └── home_valuation_spec.rb ├── spec_helper.rb ├── xml │ ├── general_failure.xml │ ├── near_limit.xml │ ├── get_chart.xml │ ├── get_rate_summary.xml │ ├── get_region_chart.xml │ ├── get_monthly_payments.xml │ ├── get_zestimate_missing_region.xml │ ├── get_deep_search_results_missing_data.xml │ ├── get_search_results.xml │ ├── get_zestimate_missing_value_duration.xml │ ├── get_zestimate.xml │ ├── get_deep_search_results.xml │ ├── get_updated_property_details.xml │ ├── get_deep_search_results_duplicated.xml │ ├── get_comps.xml │ ├── get_deep_comps.xml │ └── get_demographics.xml └── support │ └── have_configuration_matcher.rb ├── Rakefile ├── LICENSE ├── CHANGELOG.md ├── Gemfile.lock ├── rubillow.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format progress 2 | --color 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --no-private 2 | lib/**/*.rb 3 | - 4 | LICENSE 5 | CHANGELOG.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | .rvmrc 4 | pkg/* 5 | coverage 6 | doc 7 | .yardoc 8 | -------------------------------------------------------------------------------- /lib/rubillow/version.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | # @private 3 | VERSION = "0.0.8" 4 | end 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.8.7 3 | - 1.9.2 4 | - 1.9.3 5 | script: "bundle exec rake spec" 6 | 7 | branches: 8 | only: 9 | - master -------------------------------------------------------------------------------- /spec/rubillow/models/region_children_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::RegionChildren do 4 | it "populates the data" do 5 | data = Rubillow::Models::RegionChildren.new(get_xml('get_region_children.xml')) 6 | 7 | data.region.id.should == "16037" 8 | data.regions.count.should == 107 9 | data.regions[0].id.should == "343997" 10 | end 11 | end -------------------------------------------------------------------------------- /lib/rubillow/models/search_result.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Property search results 4 | class SearchResult < Base 5 | include Zestimateable 6 | 7 | protected 8 | 9 | # @private 10 | def parse 11 | super 12 | 13 | return if !success? 14 | 15 | extract_zestimate(@parser) 16 | end 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /spec/rubillow/rubillow_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow do 4 | it "allows setting configuration as a block" do 5 | Rubillow.configure do |config| 6 | config.http_open_timeout = 10 7 | config.http_read_timeout = 30 8 | end 9 | 10 | Rubillow.configuration.http_open_timeout.should == 10 11 | Rubillow.configuration.http_read_timeout.should == 30 12 | end 13 | end -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | require "yard" 4 | 5 | desc "Run specs" 6 | RSpec::Core::RakeTask.new do |t| 7 | t.pattern = "spec/**/*_spec.rb" 8 | end 9 | 10 | desc "Run specs with coverage" 11 | task :coverage do 12 | ENV['COVERAGE'] = 'true' 13 | Rake::Task["spec"].execute 14 | end 15 | 16 | YARD::Rake::YardocTask.new do |t| 17 | end 18 | 19 | task :default => :spec -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'coveralls' 3 | 4 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 5 | SimpleCov::Formatter::HTMLFormatter, 6 | Coveralls::SimpleCov::Formatter 7 | ] 8 | SimpleCov.start 9 | 10 | require File.expand_path("../../lib/rubillow", __FILE__) 11 | require 'rspec' 12 | 13 | Dir[File.expand_path("../support/**/*.rb", __FILE__)].each do |file| 14 | require file 15 | end 16 | 17 | def get_xml(file) 18 | File.open(File.expand_path("../xml/" + file, __FILE__)).read 19 | end -------------------------------------------------------------------------------- /spec/rubillow/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Configuration do 4 | it { should have_configuration_option(:host).default("www.zillow.com") } 5 | it { should have_configuration_option(:port).default(80) } 6 | it { should have_configuration_option(:path).default("webservice/") } 7 | it { should have_configuration_option(:zwsid).default(nil) } 8 | it { should have_configuration_option(:http_open_timeout).default(2) } 9 | it { should have_configuration_option(:http_read_timeout).default(2) } 10 | end -------------------------------------------------------------------------------- /spec/rubillow/models/postings_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::Postings do 4 | it "populates the data" do 5 | data = Rubillow::Models::Postings.new(get_xml('get_region_postings.xml')) 6 | 7 | data.region_id.should == "99562" 8 | data.links.count.should == 3 9 | data.make_me_move.count.should == 90 10 | data.for_sale_by_owner.count.should == 1 11 | data.for_sale_by_agent.count.should == 8 12 | data.report_for_sale.count.should == 1 13 | data.for_rent.count.should == 1 14 | end 15 | end -------------------------------------------------------------------------------- /spec/rubillow/models/rate_summary_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::RateSummary do 4 | it "populates the results" do 5 | data = Rubillow::Models::RateSummary.new(get_xml('get_rate_summary.xml')) 6 | 7 | data.today[:thiry_year_fixed].should == "4.01" 8 | data.today[:fifteen_year_fixed].should == "3.27" 9 | data.today[:five_one_arm].should == "2.73" 10 | data.last_week[:thiry_year_fixed].should == "4.02" 11 | data.last_week[:fifteen_year_fixed].should == "3.27" 12 | data.last_week[:five_one_arm].should == "2.84" 13 | end 14 | end -------------------------------------------------------------------------------- /lib/rubillow/models/property_chart.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Chart for a property 4 | class PropertyChart < Chart 5 | # @return [String] url for chart 6 | attr_accessor :graphs_and_data 7 | 8 | # Returns HTML for the chart. 9 | # @return [String] chart HTML. 10 | def to_html 11 | "" + super + "" 12 | end 13 | 14 | protected 15 | 16 | # @private 17 | def parse 18 | super 19 | 20 | return if !success? 21 | 22 | @graphs_and_data = @parser.xpath('//response/graphsanddata').text 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/rubillow/models/images.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Common data for responses containing images 4 | module Images 5 | # @return [Array] List of image urls 6 | attr_accessor :images 7 | 8 | # @return [Integer] number of images available (doesn't always match @images.count) 9 | attr_accessor :images_count 10 | 11 | protected 12 | 13 | # @private 14 | def extract_images(xml) 15 | @images_count = xml.xpath('//images/count').text 16 | 17 | @images = [] 18 | xml.xpath('//images/image').children.each do |elm| 19 | @images << elm.text 20 | end 21 | end 22 | end 23 | end 24 | end -------------------------------------------------------------------------------- /lib/rubillow/models/zpidable.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Common data for responses containing zpid's 4 | module Zpidable 5 | # @return [String] ZPID of property 6 | attr_accessor :zpid 7 | 8 | protected 9 | 10 | # @private 11 | def extract_zpid(xml) 12 | # TODO: clean up this logic 13 | if !xml.xpath('//response/zpid').empty? 14 | selector = '//response/zpid' 15 | elsif !xml.xpath('//result/zpid').empty? 16 | selector = '//result/zpid' 17 | else 18 | selector = '//zpid' 19 | end 20 | 21 | @zpid = xml.xpath(selector).first.text 22 | end 23 | end 24 | end 25 | end -------------------------------------------------------------------------------- /spec/xml/general_failure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | Error: invalid or missing ZWSID parameter 9 | 2 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/rubillow/models/region_chart.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Chart for a region 4 | class RegionChart < Chart 5 | include Linkable 6 | 7 | # @return [String] url for chart 8 | attr_accessor :link 9 | 10 | # Returns HTML for the chart. 11 | # @return [String] chart HTML. 12 | def to_html 13 | "" + super + "" 14 | end 15 | 16 | protected 17 | 18 | # @private 19 | def parse 20 | super 21 | 22 | return if !success? 23 | 24 | extract_links(@parser) 25 | 26 | @link = @parser.xpath('//response/link').text 27 | end 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /spec/xml/near_limit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | Error: invalid or missing ZWSID parameter 9 | 2 10 | true 11 | 12 | 13 | -------------------------------------------------------------------------------- /spec/rubillow/postings_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Postings, ".region_postings" do 4 | let(:request) { stub("Request", :get => response) } 5 | let(:response) { stub("Response", :body => "", :code => 200) } 6 | 7 | it "requires either the zipcode or citystatezip option" do 8 | lambda { 9 | Rubillow::Postings.region_postings() 10 | }.should raise_error(ArgumentError, "Either the zipcode or citystatezip option is required") 11 | end 12 | 13 | it "fetches the XML" do 14 | Rubillow::Request.stub(:request).and_return(request) 15 | 16 | response = Rubillow::Postings.region_postings({ :zipcode => "98102", :rental => true }) 17 | response.should be_an_instance_of(Rubillow::Models::Postings) 18 | end 19 | end -------------------------------------------------------------------------------- /lib/rubillow/models/demographic_value.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Demographic data point. 4 | class DemographicValue 5 | # @return [String] data point value. 6 | attr_accessor :value 7 | 8 | # @return [String] data point type. 9 | attr_accessor :type 10 | 11 | # create a new data point. 12 | # @param [String] xml for point. 13 | def initialize(xml) 14 | if !xml.empty? 15 | @value = xml.text 16 | @type = xml.attribute('type').value if !xml.attribute('type').nil? 17 | end 18 | end 19 | 20 | # Prints value 21 | # 22 | # @return [String] attribute value 23 | def to_s 24 | @value 25 | end 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /lib/rubillow/helpers/xml_parsing_helper.rb: -------------------------------------------------------------------------------- 1 | # Helper methods for parsing XML. 2 | module XmlParsingHelper 3 | # @param path [String] Xpath query to find the node in XML. 4 | # @param attribute [Symbol] Attribute on the node to call and return as the value. 5 | # @param xml [Nokogiri::XML] A nokogiri parser object to call #xpath. 6 | # @param nil_value (optional) A value to return if the attribute on the node 7 | # is nil or if the node is not present. 8 | # 9 | # @return [String] Value from an attribute of an xpath node, if present. 10 | # If the node is not present, return nil or nil_value if specified. 11 | def xpath_if_present(path, attribute, xml, nil_value = nil) 12 | text = xml.xpath(path).first.send(attribute) unless xml.xpath(path).empty? 13 | text ||= nil_value 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rubillow/models/region_children.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # List of sub-regions for a region 4 | class RegionChildren < Base 5 | # @return [Models::Region] top-level region 6 | attr_accessor :region 7 | 8 | # @return [Array] sub-level regions ({Models::Region}) 9 | attr_accessor :regions 10 | 11 | protected 12 | 13 | # @private 14 | def parse 15 | super 16 | 17 | return if !success? 18 | 19 | @region = Region.new(@parser.xpath('//response/region').to_xml) 20 | 21 | @regions = [] 22 | @parser.xpath('//response/list').children.each do |region| 23 | if region.name == "region" 24 | @regions << Region.new(region.to_xml) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /lib/rubillow.rb: -------------------------------------------------------------------------------- 1 | require "rubillow/version" 2 | require "rubillow/configuration" 3 | require "rubillow/request" 4 | require "rubillow/home_valuation" 5 | require "rubillow/mortgage" 6 | require "rubillow/postings" 7 | require "rubillow/property_details" 8 | require "rubillow/neighborhood" 9 | require "rubillow/models" 10 | 11 | require 'date' 12 | 13 | # Top-level interface to Rubillow 14 | module Rubillow 15 | # Call this method to modify defaults in your initializers. 16 | # 17 | # @example 18 | # Rubillow.configure do |configuration| 19 | # configuration.zwsid = "abcd1234" 20 | # end 21 | # 22 | # @yield [Configuration] The current configuration. 23 | def self.configure 24 | yield(configuration) 25 | end 26 | 27 | # @return [Configuration] Current configuration. 28 | def self.configuration 29 | @@configuration ||= Configuration.new 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rubillow/models/chart.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Base chart class. 4 | class Chart < Base 5 | # @return [String] image height. 6 | attr_accessor :height 7 | 8 | # @return [String] image width. 9 | attr_accessor :width 10 | 11 | # @return [String] URL to image. 12 | attr_accessor :url 13 | 14 | # Returns HTML for the chart. 15 | # @return [String] chart HTML. 16 | def to_html 17 | "" 18 | end 19 | 20 | protected 21 | 22 | # @private 23 | def parse 24 | super 25 | 26 | return if !success? 27 | 28 | @height = @parser.xpath('//request/height').first.text.to_i 29 | @width = @parser.xpath('//request/width').first.text.to_i 30 | @url = @parser.xpath('//response/url').first.text 31 | end 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /spec/rubillow/models/monthly_payments_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::MonthlyPayments do 4 | it "populates the results" do 5 | data = Rubillow::Models::MonthlyPayments.new(get_xml('get_monthly_payments.xml')) 6 | 7 | data.thirty_year_fixed[:rate].should == "4.01" 8 | data.thirty_year_fixed[:principal_and_interest].should == "1219" 9 | data.thirty_year_fixed[:mortgage_insurance].should == "93" 10 | data.fifteen_year_fixed[:rate].should == "3.27" 11 | data.fifteen_year_fixed[:principal_and_interest].should == "1795" 12 | data.fifteen_year_fixed[:mortgage_insurance].should == "93" 13 | data.five_one_arm[:rate].should == "3.27" 14 | data.five_one_arm[:principal_and_interest].should == "1039" 15 | data.five_one_arm[:mortgage_insurance].should == "93" 16 | data.down_payment.should == "45000" 17 | data.monthly_property_taxes.should == "193" 18 | data.monthly_hazard_insurance == "50" 19 | end 20 | end -------------------------------------------------------------------------------- /lib/rubillow/models/comps.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # List of comps for a property. 4 | class Comps < Base 5 | # @return [Models::SearchResult] principal property. 6 | attr_accessor :principal 7 | 8 | # @return [Hash] comparables list (key => comparable's score, value => {Models::SearchResult}). 9 | # 10 | # @example 11 | # comparables.each do |score, comp| 12 | # puts score 13 | # puts comp.price 14 | # end 15 | attr_accessor :comparables 16 | 17 | protected 18 | 19 | # @private 20 | def parse 21 | super 22 | 23 | return if !success? 24 | 25 | @principal = SearchResult.new(@parser.xpath('//principal').to_xml) 26 | 27 | @comparables = {} 28 | @parser.xpath('//comparables/comp').each do |elm| 29 | key = elm.attribute('score').value 30 | @comparables[key] = SearchResult.new(elm.to_xml) 31 | end 32 | end 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /lib/rubillow/models/posting.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # A posting 4 | class Posting < Base 5 | include Zpidable 6 | include Addressable 7 | include Linkable 8 | include PropertyBasics 9 | include Images 10 | 11 | # @return [Date] last date data was refreshed. 12 | attr_accessor :last_refreshed_date 13 | 14 | # @return [String] price 15 | attr_accessor :price 16 | 17 | protected 18 | 19 | # @private 20 | def parse 21 | super 22 | 23 | return if !success? 24 | 25 | property = @parser.xpath('//property').first 26 | extract_zpid(property) 27 | extract_links(property) 28 | extract_address(property) 29 | extract_property_basics(property) 30 | extract_images(property) 31 | 32 | @last_refreshed_date = Date.strptime(@parser.xpath('//lastRefreshedDate').text, "%Y-%m-%d") 33 | @price = @parser.xpath('//price').text 34 | end 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /lib/rubillow/configuration.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | # Configuration options 3 | class Configuration 4 | # @return [String] HTTP host (defaults to +www.zillow.com+) 5 | attr_accessor :host 6 | 7 | # @return [Integer] HTTP port (defaults to +80+) 8 | attr_accessor :port 9 | 10 | # @return [String] relative service path (defaults to +webservice/+) 11 | attr_accessor :path 12 | 13 | # @return [String] Zillow API key. See {http://www.zillow.com/howto/api/APIOverview.htm}. 14 | attr_accessor :zwsid 15 | 16 | # @return [Integer] HTTP connection timeout seconds (defaults to +2+) 17 | attr_accessor :http_open_timeout 18 | 19 | # @return [Integer] HTTP read timeout seconds (defaults to +2+) 20 | attr_accessor :http_read_timeout 21 | 22 | # @private 23 | def initialize 24 | self.host = "www.zillow.com" 25 | self.port = 80 26 | self.path = "webservice/" 27 | self.http_open_timeout = 2 28 | self.http_read_timeout = 2 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /lib/rubillow/models/deep_comps.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # List of comps for a given property with deep data. 4 | class DeepComps < Base 5 | # @return [Models::DeepSearchResult] principal property. 6 | attr_accessor :principal 7 | 8 | # @return [Hash] comparables list (key => comparable's score, value => {Models::DeepSearchResult}). 9 | # 10 | # @example 11 | # comparables.each do |score, comp| 12 | # puts score 13 | # puts comp.price 14 | # end 15 | attr_accessor :comparables 16 | 17 | protected 18 | 19 | # @private 20 | def parse 21 | super 22 | 23 | return if !success? 24 | 25 | @principal = DeepSearchResult.new(@parser.xpath('//principal').to_xml) 26 | 27 | @comparables = {} 28 | @parser.xpath('//comparables/comp').each do |elm| 29 | key = elm.attribute('score').value 30 | @comparables[key] = DeepSearchResult.new(elm.to_xml) 31 | end 32 | end 33 | end 34 | end 35 | end -------------------------------------------------------------------------------- /spec/xml/get_chart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 48749425 5 | percent 6 | 300 7 | 150 8 | 9 | 10 | Request successfully processed 11 | 0 12 | 13 | 14 | http://www.zillow.com/app?chartDuration=1year&chartType=partner&height=150&page=webservice%2FGetChart&service=chart&showPercent=true&width=300&zpid=48749425 15 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /spec/support/have_configuration_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_configuration_option do |option| 2 | match do |configuration| 3 | configuration.should respond_to(option) 4 | 5 | if instance_variables.include?("@default") 6 | configuration.send(option).should == @default 7 | end 8 | 9 | configuration.__send__(:"#{option}=", "value") 10 | configuration.__send__(option).should == "value" 11 | end 12 | 13 | chain :default do |default| 14 | @default = default 15 | end 16 | 17 | failure_message_for_should do 18 | description = "expected #{subject} to have" 19 | description << " configuration option #{option.inspect}" 20 | description << " with a default of #{@default.inspect}" if instance_variables.include?("@default") 21 | description 22 | end 23 | 24 | failure_message_for_should_not do 25 | description = "expected #{subject} to not have" 26 | description << " configuration option #{option.inspect}" 27 | description << " with a default of #{@default.inspect}" if instance_variables.include?("@default") 28 | description 29 | end 30 | end -------------------------------------------------------------------------------- /spec/rubillow/models/property_chart_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::PropertyChart do 4 | it "populates the results" do 5 | data = Rubillow::Models::PropertyChart.new(get_xml('get_chart.xml')) 6 | 7 | data.width.should == 300 8 | data.height.should == 150 9 | data.url.should == "http://www.zillow.com/app?chartDuration=1year&chartType=partner&height=150&page=webservice%2FGetChart&service=chart&showPercent=true&width=300&zpid=48749425" 10 | data.graphs_and_data.should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data" 11 | end 12 | 13 | it "should format correctly for HTML" do 14 | data = Rubillow::Models::PropertyChart.new(get_xml('get_chart.xml')) 15 | 16 | data.to_html.should == "" 17 | end 18 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011 Matthew Vince 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /spec/xml/get_rate_summary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Request successfully processed 6 | 0 7 | 8 | 9 | 10 | 4.01 11 | 3.27 12 | 2.73 13 | 14 | 15 | 4.02 16 | 3.27 17 | 2.84 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.8 / 2013-06-16 2 | 3 | * Fix nokogiri gem requirement to maintain 1.8.7 compatibilty. 4 | 5 | ## 0.0.7 / 2013-06-16 6 | 7 | * Remove unnecessary chomp call from value duration 8 | 9 | ## 0.0.6 / 2013-04-10 10 | 11 | * Fix case where Zillow returns multiple results for an address -- assume first address for result 12 | * Fix RentZestimate bug when valueChange doesn't have a duration. 13 | * Fix common scenarios where xml nodes are not present 14 | 15 | ## 0.0.5 / 2013-01-12 16 | 17 | * Include RentZestimate if available 18 | * Check if region info exists before populating 19 | 20 | ## 0.0.4 / 2012-01-31 21 | 22 | * Check if lastSoldDate exists before parsing 23 | 24 | ## 0.0.3 / 2012-01-14 25 | 26 | * Fix GetUpdatedPropertyDetails date requirement 27 | * Fix typo on method name 28 | * Updated documentation 29 | * Added Travis CI and dependency tracking 30 | * Remove unnecessary testing dependencies 31 | 32 | ## 0.0.2 / 2011-09-27 33 | 34 | * Remove unnecessary dependencies of activesupport and i18n 35 | 36 | ## 0.0.1 / 2011-08-27 37 | 38 | * Covers all Zillow API endpoints 39 | * Code documentation 40 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rubillow (0.0.8) 5 | nokogiri (~> 1.5.0) 6 | 7 | GEM 8 | remote: http://rubygems.org/ 9 | specs: 10 | bluecloth (2.2.0) 11 | colorize (0.5.8) 12 | coveralls (0.6.0) 13 | colorize 14 | multi_json (~> 1.3) 15 | rest-client 16 | simplecov (>= 0.7) 17 | thor 18 | diff-lcs (1.2.1) 19 | mime-types (1.21) 20 | multi_json (1.6.1) 21 | nokogiri (1.5.9) 22 | rake (10.0.3) 23 | rest-client (1.6.7) 24 | mime-types (>= 1.16) 25 | rspec (2.13.0) 26 | rspec-core (~> 2.13.0) 27 | rspec-expectations (~> 2.13.0) 28 | rspec-mocks (~> 2.13.0) 29 | rspec-core (2.13.0) 30 | rspec-expectations (2.13.0) 31 | diff-lcs (>= 1.1.3, < 2.0) 32 | rspec-mocks (2.13.0) 33 | simplecov (0.7.1) 34 | multi_json (~> 1.0) 35 | simplecov-html (~> 0.7.1) 36 | simplecov-html (0.7.1) 37 | thor (0.17.0) 38 | yard (0.8.5.2) 39 | 40 | PLATFORMS 41 | ruby 42 | 43 | DEPENDENCIES 44 | bluecloth (~> 2.2) 45 | coveralls (~> 0.6) 46 | rake (~> 10.0) 47 | rspec (~> 2.12) 48 | rubillow! 49 | simplecov (~> 0.7) 50 | yard (~> 0.8) 51 | -------------------------------------------------------------------------------- /lib/rubillow/models/linkable.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Common data for responses containing links 4 | module Linkable 5 | # @return [Hash] Links (format: :name => 'url') 6 | # 7 | # @example 8 | # links.each do |name, url 9 | # end 10 | # 11 | attr_accessor :links 12 | 13 | protected 14 | 15 | # @private 16 | def extract_links(xml) 17 | @links = {} 18 | 19 | # TODO: clean up this logic 20 | if !xml.xpath('//result/links').empty? 21 | selector = '//result/links' 22 | elsif !xml.xpath('//response/links').empty? 23 | selector = '//response/links' 24 | elsif !xml.xpath('//principal/links').empty? 25 | selector = '//principal/links' 26 | elsif !xml.xpath('//comp/links').empty? 27 | selector = '//comp/links' 28 | else 29 | selector = '//links' 30 | end 31 | 32 | xml.xpath(selector).children.each do |link| 33 | next if link.name == "myzestimator" # deprecated 34 | @links[link.name.to_sym] = link.text 35 | end 36 | end 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /rubillow.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "rubillow/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "rubillow" 7 | s.version = Rubillow::VERSION 8 | s.authors = ["Matthew Vince"] 9 | s.email = ["rubillow@matthewvince.com"] 10 | s.homepage = "https://github.com/synewaves/rubillow" 11 | s.summary = "Ruby library to access the Zillow API" 12 | s.description = "Ruby library to access the Zillow API" 13 | s.license = "MIT" 14 | 15 | s.rubyforge_project = "rubillow" 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.require_paths = ["lib"] 21 | 22 | s.add_dependency "nokogiri", "~> 1.5.0" 23 | 24 | s.add_development_dependency "rake", "~> 10.0" 25 | s.add_development_dependency "rspec", "~> 2.12" 26 | s.add_development_dependency "yard", "~> 0.8" 27 | s.add_development_dependency "bluecloth", "~> 2.2" 28 | s.add_development_dependency "simplecov", "~> 0.7" 29 | s.add_development_dependency "coveralls", "~> 0.6" 30 | end 31 | -------------------------------------------------------------------------------- /spec/rubillow/models/posting_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::Posting do 4 | it "populates the data" do 5 | data = Rubillow::Models::Postings.new(get_xml('get_region_postings.xml')) 6 | data = data.make_me_move[0] 7 | 8 | data.should be_a(Rubillow::Models::Posting) 9 | data.zpid.should == "48708109" 10 | data.last_refreshed_date.strftime("%m/%d/%Y").should == "08/21/2011" 11 | data.links.count.should == 1 12 | data.links[:homedetails].should == "http://www.zillow.com/homedetails/1658-Federal-Ave-E-Seattle-WA-98102/48708109_zpid/" 13 | data.address[:street].should == "1658 Federal Ave E" 14 | data.address[:city].should == "Seattle" 15 | data.address[:state].should == "WA" 16 | data.address[:zipcode].should == "98102" 17 | data.address[:latitude].should == "47.634532" 18 | data.address[:longitude].should == "-122.318519" 19 | data.use_code.should == "Single Family" 20 | data.lot_size_square_feet.should == "20000" 21 | data.finished_square_feet.should == "7380" 22 | data.bathrooms.should == "5.0" 23 | data.bedrooms.should == "6" 24 | data.total_rooms.should == "" 25 | data.images_count.should == "1" 26 | data.price.should == "4500000" 27 | end 28 | end -------------------------------------------------------------------------------- /spec/rubillow/models/region_chart_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::RegionChart do 4 | it "populates the results" do 5 | data = Rubillow::Models::RegionChart.new(get_xml('get_region_chart.xml')) 6 | 7 | data.width.should == 300 8 | data.height.should == 150 9 | data.url.should == "http://www.zillow.com/app?chartDuration=1year&chartType=partner&cityRegionId=16037&countyRegionId=0&height=150&nationRegionId=0&neighborhoodRegionId=0&page=webservice%2FGetRegionChart&service=chart&showCity=true&showPercent=true&stateRegionId=0&width=300&zipRegionId=0" 10 | data.link.should == "http://www.zillow.com/homes/Seattle-WA/" 11 | data.links.count.should == 3 12 | end 13 | 14 | it "should format correctly for HTML" do 15 | data = Rubillow::Models::RegionChart.new(get_xml('get_region_chart.xml')) 16 | 17 | data.to_html.should == "" 18 | end 19 | end -------------------------------------------------------------------------------- /lib/rubillow/models/region.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Region information. 4 | class Region < Base 5 | # @return [String] region id. 6 | attr_accessor :id 7 | 8 | # @return [String] state. 9 | attr_accessor :state 10 | 11 | # @return [String] city. 12 | attr_accessor :city 13 | 14 | # @return [String] neighborhood. 15 | attr_accessor :neighborhood 16 | 17 | # @return [String] latitude. 18 | attr_accessor :latitude 19 | 20 | # @return [String] longitude. 21 | attr_accessor :longitude 22 | 23 | # @return [String] ZMM rate URL. 24 | attr_accessor :zmmrateurl 25 | 26 | protected 27 | 28 | # @private 29 | def parse 30 | super 31 | 32 | return if !success? 33 | 34 | @id = @parser.xpath('//id').first.text 35 | @state = @parser.xpath('//state').text 36 | @city = @parser.xpath('//city').text 37 | @neighborhood = @parser.xpath('//neighborhood').text 38 | @latitude = @parser.xpath('//latitude').text 39 | @longitude = @parser.xpath('//longitude').text 40 | @zmmrateurl = @parser.xpath('//zmmrateurl').text 41 | end 42 | end 43 | end 44 | end -------------------------------------------------------------------------------- /lib/rubillow/models/property_basics.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Common data for responses containing property information 4 | module PropertyBasics 5 | # @return [String] property type 6 | attr_accessor :use_code 7 | 8 | # @return [String] Size of lot in square feet 9 | attr_accessor :lot_size_square_feet 10 | 11 | # @return [String] Size of property in square feet 12 | attr_accessor :finished_square_feet 13 | 14 | # @return [String] number of bathrooms 15 | attr_accessor :bathrooms 16 | 17 | # @return [String] number of bedrooms 18 | attr_accessor :bedrooms 19 | 20 | # @return [String] total number of rooms 21 | attr_accessor :total_rooms 22 | 23 | protected 24 | 25 | # @private 26 | def extract_property_basics(xml) 27 | @use_code = xpath_if_present('//useCode', :text, xml, "") 28 | @lot_size_square_feet = xpath_if_present('//lotSizeSqFt', :text, xml, "") 29 | @finished_square_feet = xpath_if_present('//finishedSqFt', :text, xml, "") 30 | @bathrooms = xpath_if_present('//bathrooms', :text, xml, "") 31 | @bedrooms = xpath_if_present('//bedrooms', :text, xml, "") 32 | @total_rooms = xpath_if_present('//totalRooms', :text, xml, "") 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rubillow/models/addressable.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Common data for responses containing address information. 4 | module Addressable 5 | # @return [Hash] Address information (all are strings, keys are: :street, :city, :state, :zipcode, :latitude, :longitude). 6 | # 7 | # @example 8 | # puts address[:street] 9 | # puts address[:city] 10 | # 11 | attr_accessor :address 12 | 13 | # get the full, formatted address 14 | # 15 | # @return [String] formatted address 16 | def full_address 17 | @address[:street] + ', ' + @address[:city] + ', ' + @address[:state] + ' ' + @address[:zipcode] 18 | end 19 | 20 | protected 21 | 22 | # @private 23 | def extract_address(xml) 24 | address = xml.xpath('//address') 25 | if !address.empty? 26 | @address = { 27 | :street => address.xpath('//street').first.text, 28 | :city => address.xpath('//city').first.text, 29 | :state => address.xpath('//state').first.text, 30 | :zipcode => address.xpath('//zipcode').first.text, 31 | :latitude => address.xpath('//latitude').first.text, 32 | :longitude => address.xpath('//longitude').first.text, 33 | } 34 | end 35 | end 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /spec/rubillow/models_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::Base do 4 | it "catches error responses" do 5 | data = Rubillow::Models::Base.new(get_xml('general_failure.xml')) 6 | 7 | data.code.should == 2 8 | data.message.should == "Error: invalid or missing ZWSID parameter" 9 | end 10 | 11 | it "catches near limit warning" do 12 | data = Rubillow::Models::Base.new(get_xml('near_limit.xml')) 13 | 14 | data.near_limit.should be_true 15 | end 16 | 17 | it "fails if empty XML" do 18 | data = Rubillow::Models::Base.new("") 19 | 20 | data.success?.should be_false 21 | data.code.should == -1 22 | data.message.should == "Error connecting to remote service." 23 | end 24 | 25 | it "fails if nil XML" do 26 | data = Rubillow::Models::Base.new(nil) 27 | 28 | data.success?.should be_false 29 | data.code.should == -1 30 | data.message.should == "Error connecting to remote service." 31 | end 32 | 33 | it "fails if passed false" do 34 | data = Rubillow::Models::Base.new(false) 35 | 36 | data.success?.should be_false 37 | data.code.should == -1 38 | data.message.should == "Error connecting to remote service." 39 | end 40 | 41 | it "saves raw XML" do 42 | data = Rubillow::Models::Base.new(get_xml('near_limit.xml')) 43 | 44 | data.xml.should == get_xml('near_limit.xml') 45 | end 46 | end -------------------------------------------------------------------------------- /spec/rubillow/mortgage_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Mortgage, ".rate_summary" do 4 | let(:request) { stub("Request", :get => response) } 5 | let(:response) { stub("Response", :body => "", :code => 200) } 6 | 7 | it "fetches the XML" do 8 | Rubillow::Request.stub(:request).and_return(request) 9 | 10 | response = Rubillow::Mortgage.rate_summary() 11 | response.should be_an_instance_of(Rubillow::Models::RateSummary) 12 | end 13 | end 14 | 15 | describe Rubillow::Mortgage, ".monthly_payments" do 16 | let(:request) { stub("Request", :get => response) } 17 | let(:response) { stub("Response", :body => "", :code => 200) } 18 | 19 | it "requires the price option" do 20 | lambda { 21 | Rubillow::Mortgage.monthly_payments() 22 | }.should raise_error(ArgumentError, "The price option is required") 23 | end 24 | 25 | it "requires either the down or dollars down option" do 26 | lambda { 27 | Rubillow::Mortgage.monthly_payments({ :price => "300000" }) 28 | }.should raise_error(ArgumentError, "Either the down or dollarsdown option is required") 29 | end 30 | 31 | it "fetches the XML" do 32 | Rubillow::Request.stub(:request).and_return(request) 33 | 34 | response = Rubillow::Mortgage.monthly_payments({ :price => "300000", :down => "15", :zip => "98104" }) 35 | response.should be_an_instance_of(Rubillow::Models::MonthlyPayments) 36 | end 37 | end -------------------------------------------------------------------------------- /lib/rubillow/models/rate_summary.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Rate summary information 4 | class RateSummary < Base 5 | # @return [Hash] today's rates (:thirty_year_fixed, :fifteen_year_fixed, :five_one_arm). 6 | # 7 | # @example 8 | # today[:thirty_year_fixed] 9 | # 10 | attr_accessor :today 11 | 12 | # @return [Hash] last week's rates (:thirty_year_fixed, :fifteen_year_fixed, :five_one_arm). 13 | # 14 | # @example 15 | # last_week[:thirty_year_fixed] 16 | # 17 | attr_accessor :last_week 18 | 19 | protected 20 | 21 | # @private 22 | def parse 23 | super 24 | 25 | return if !success? 26 | 27 | @today = {} 28 | @last_week = {} 29 | @today[:thiry_year_fixed] = @parser.xpath('//today/rate[@loanType="thirtyYearFixed"]').text 30 | @today[:fifteen_year_fixed] = @parser.xpath('//today/rate[@loanType="fifteenYearFixed"]').text 31 | @today[:five_one_arm] = @parser.xpath('//today/rate[@loanType="fiveOneARM"]').text 32 | @last_week[:thiry_year_fixed] = @parser.xpath('//lastWeek/rate[@loanType="thirtyYearFixed"]').text 33 | @last_week[:fifteen_year_fixed] = @parser.xpath('//lastWeek/rate[@loanType="fifteenYearFixed"]').text 34 | @last_week[:five_one_arm] = @parser.xpath('//lastWeek/rate[@loanType="fiveOneARM"]').text 35 | end 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /spec/xml/get_region_chart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | seattle 5 | WA 6 | percent 7 | 300 8 | 150 9 | 10 | 11 | Request successfully processed 12 | 0 13 | 14 | 15 | http://www.zillow.com/app?chartDuration=1year&chartType=partner&cityRegionId=16037&countyRegionId=0&height=150&nationRegionId=0&neighborhoodRegionId=0&page=webservice%2FGetRegionChart&service=chart&showCity=true&showPercent=true&stateRegionId=0&width=300&zipRegionId=0 16 | http://www.zillow.com/homes/Seattle-WA/ 17 | 18 | http://www.zillow.com/local-info/WA-Seattle-home-value/r_16037/ 19 | http://www.zillow.com/homes/for_sale/Seattle-WA/ 20 | http://www.zillow.com/homes/fsbo/Seattle-WA/ 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/xml/get_monthly_payments.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 300000 5 | 15 6 | 98104 7 | 8 | 9 | Request successfully processed 10 | 0 11 | 12 | 13 | 14 | 4.01 15 | 1219 16 | 93 17 | 18 | 19 | 3.27 20 | 1795 21 | 93 22 | 23 | 24 | 3.27 25 | 1039 26 | 93 27 | 28 | 45000 29 | 193 30 | 50 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/rubillow/models/deep_search_result.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Get a property's information with deeper data. 4 | class DeepSearchResult < SearchResult 5 | include PropertyBasics 6 | include XmlParsingHelper 7 | 8 | # @return [String] FIPS county code. See {http://www.itl.nist.gov/fipspubs/fip6-4.htm}. 9 | attr_accessor :fips_county 10 | 11 | # @return [String] year of the last tax assessment 12 | attr_accessor :tax_assessment_year 13 | 14 | # @return [String] value of the last tax assessment 15 | attr_accessor :tax_assessment 16 | 17 | # @return [String] year home was built 18 | attr_accessor :year_built 19 | 20 | # @return [Date] last date property was sold 21 | attr_accessor :last_sold_date 22 | 23 | # @return [String] price property was last sold for 24 | attr_accessor :last_sold_price 25 | 26 | protected 27 | 28 | # @private 29 | def parse 30 | super 31 | 32 | return if !success? 33 | 34 | extract_property_basics(@parser) 35 | @fips_county = xpath_if_present('//FIPScounty', :text, @parser, "") 36 | @tax_assessment_year = xpath_if_present('//taxAssessmentYear', :text, @parser) 37 | @tax_assessment = xpath_if_present('//taxAssessment', :text, @parser) 38 | @year_built = xpath_if_present('//yearBuilt', :text, @parser) 39 | if tmp = xpath_if_present('//lastSoldDate', :text, @parser) and tmp.strip.length > 0 40 | @last_sold_date = Date.strptime(tmp, "%m/%d/%Y") 41 | end 42 | @last_sold_price = xpath_if_present('//lastSoldPrice', :text, @parser) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/rubillow/request.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | require 'net/http' 3 | require 'uri' 4 | 5 | module Rubillow 6 | # @private 7 | # HTTP request manager 8 | class Request 9 | # Makes the request to the web service. 10 | # 11 | # @param [String] path Web service name. 12 | # @param [Hash] options Request options. 13 | # @return [String, Boolean] XML on success, false if not. 14 | def self.get(path, options = {}) 15 | zwsid = Rubillow.configuration.zwsid 16 | 17 | unless zwsid.nil? 18 | options[:zws_id] ||= zwsid 19 | end 20 | 21 | response = request.get(uri(path, options)) 22 | 23 | case response.code.to_i 24 | when 200 25 | response.body 26 | else 27 | false 28 | end 29 | end 30 | 31 | # gets the request object. 32 | # 33 | # @return [Net::HTTP] HTTP object. 34 | def self.request 35 | http = Net::HTTP.new(Rubillow.configuration.host, Rubillow.configuration.port) 36 | 37 | http.open_timeout = Rubillow.configuration.http_open_timeout 38 | http.read_timeout = Rubillow.configuration.http_read_timeout 39 | http 40 | end 41 | 42 | # Generate the url for the request. 43 | # 44 | # @param [String] path Web service name. 45 | # @param [Hash] options Request options. 46 | def self.uri(path, options = {}) 47 | path = Rubillow.configuration.path + path 48 | "/#{path}.htm?#{hash_to_query_string(options)}" 49 | end 50 | 51 | # Turns request options into query string. 52 | # 53 | # @param [Hash] hash Request options. 54 | # @return [String] Formatted query string. 55 | def self.hash_to_query_string(hash) 56 | hash = hash.sort_by { |key, value| 57 | key.to_s 58 | }.delete_if { |key, value| 59 | value.to_s.empty? 60 | }.collect { |key, value| 61 | "#{CGI.escape(key.to_s).gsub(/\_/, '-')}=#{CGI.escape(value.to_s)}" 62 | } 63 | 64 | hash.join("&") 65 | end 66 | end 67 | end -------------------------------------------------------------------------------- /lib/rubillow/models/postings.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # List of postings 4 | class Postings < Base 5 | include Linkable 6 | 7 | # @return [String] region id. 8 | attr_accessor :region_id 9 | 10 | # @return [Array] postings with MakeMeMove status ({Models::Posting}). 11 | attr_accessor :make_me_move 12 | 13 | # @return [Array] postings with FSBA status ({Models::Posting}). 14 | attr_accessor :for_sale_by_owner 15 | 16 | # @return [Array] postings with FSBO status ({Models::Posting}). 17 | attr_accessor :for_sale_by_agent 18 | 19 | # @return [Array] postings with reporting status ({Models::Posting}). 20 | attr_accessor :report_for_sale 21 | 22 | # @return [Array] postings with for rent status ({Models::Posting}). 23 | attr_accessor :for_rent 24 | 25 | protected 26 | 27 | # @private 28 | def parse 29 | super 30 | 31 | return if !success? 32 | 33 | @region_id = @parser.xpath('//regionId').text 34 | 35 | extract_links(@parser) 36 | 37 | @make_me_move = [] 38 | @parser.xpath('//response/makeMeMove/result').each do |elm| 39 | @make_me_move << Posting.new(elm.to_xml) 40 | end 41 | 42 | @for_sale_by_owner = [] 43 | @parser.xpath('//response/forSaleByOwner/result').each do |elm| 44 | @for_sale_by_owner << Posting.new(elm.to_xml) 45 | end 46 | 47 | @for_sale_by_agent = [] 48 | @parser.xpath('//response/forSaleByAgent/result').each do |elm| 49 | @for_sale_by_agent << Posting.new(elm.to_xml) 50 | end 51 | 52 | @report_for_sale = [] 53 | @parser.xpath('//response/reportForSale/result').each do |elm| 54 | @report_for_sale << Posting.new(elm.to_xml) 55 | end 56 | 57 | @for_rent = [] 58 | @parser.xpath('//response/forRent/result').each do |elm| 59 | @for_rent << Posting.new(elm.to_xml) 60 | end 61 | end 62 | end 63 | end 64 | end -------------------------------------------------------------------------------- /spec/xml/get_zestimate_missing_region.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 78264249 5 | 6 | 7 | Request successfully processed 8 | 0 9 | 10 | 11 | 78264249 12 | 13 | http://www.zillow.com/homedetails/8663-Orchard-Loop-Rd-NE-Leland-NC-28451/78264249_zpid/ 14 | http://www.zillow.com/homedetails/8663-Orchard-Loop-Rd-NE-Leland-NC-28451/78264249_zpid/#charts-and-data 15 | http://www.zillow.com/homes/78264249_zpid/ 16 | http://www.zillow.com/homes/comps/78264249_zpid/ 17 | 18 |
19 | 8663 Orchard Loop Rd NE 20 | 28451 21 | Leland 22 | NC 23 | 34.217408 24 | -78.054412 25 |
26 | 27 | 136518 28 | 12/27/2012 29 | 30 | -1299 31 | 32 | 23208 33 | 203412 34 | 35 | 58 36 | 37 | 38 | 39 | 69961 40 | 41 | 2640 42 | 36 43 | 44 |
45 |
46 | 47 | -------------------------------------------------------------------------------- /lib/rubillow/postings.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | # Interface for the Postings API. 3 | # 4 | # Read the more about this API at: {http://www.zillow.com/howto/api/GetRegionPostings.htm} 5 | class Postings 6 | # Retrieve postings for a given region. 7 | # 8 | # Read more at: {http://www.zillow.com/howto/api/GetRegionPostings.htm}. 9 | # 10 | # \* Either the zipcode or citystatezip option is required. 11 | # 12 | # @example 13 | # data = Rubillow::Postings.region_postings({ :zipcode => "98102", :rental => true }) 14 | # 15 | # if data.success? 16 | # puts data.region_id # "99562" 17 | # data.make_me_move.each do |posting| 18 | # puts posting.price 19 | # puts posting.address[:street] 20 | # end 21 | # end 22 | # 23 | # @param [Hash] options The options for the API request. 24 | # @option options [String] :zipcode The zipcode of the region (required *). 25 | # @option options [String] :citystatezip The city+state combination and/or ZIP code in which to search. Note that giving both city and state is required. Using just one will not work. (required *). 26 | # @option options [Boolean] :rental Return rental properties (defaults to +false+). 27 | # @option options [String] :postingType The type of for sale listings to return. The default is +all+. To return only for sale by owner, set +fsbo+. To return only for sale by agent, set +fsba+. To return only Make Me Move, set +mmm+. Set +none+ and the rental parameter to +true+ to return only rentals. 28 | # @return [Models::Postings] Region postings list. 29 | def self.region_postings(options = {}) 30 | options = { 31 | :zws_id => Rubillow.configuration.zwsid, 32 | :zipcode => nil, 33 | :citystatezip => nil, 34 | :rental => false, 35 | :postingType => 'all', 36 | }.merge!(options) 37 | options[:output] = 'xml' 38 | 39 | if options[:zipcode].nil? && options[:citystatezip].nil? 40 | raise ArgumentError, "Either the zipcode or citystatezip option is required" 41 | end 42 | 43 | Models::Postings.new(Rubillow::Request.get("GetRegionPostings", options)) 44 | end 45 | end 46 | end -------------------------------------------------------------------------------- /spec/rubillow/neighborhood_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Neighborhood, ".demographics" do 4 | let(:request) { stub("Request", :get => response) } 5 | let(:response) { stub("Response", :body => "", :code => 200) } 6 | 7 | it "requires either the regionid, state and city, city and neighborhood, or zip option" do 8 | lambda { 9 | Rubillow::Neighborhood.demographics() 10 | }.should raise_error(ArgumentError, "The regionid, state and city, city and neighborhood or zip option is required") 11 | end 12 | 13 | it "fetches the XML" do 14 | Rubillow::Request.stub(:request).and_return(request) 15 | 16 | response = Rubillow::Neighborhood.demographics({ :state => 'WA', :city => 'Seattle', :neighborhood => 'Ballard' }) 17 | response.should be_an_instance_of(Rubillow::Models::Demographics) 18 | end 19 | end 20 | 21 | describe Rubillow::Neighborhood, ".region_children" do 22 | let(:request) { stub("Request", :get => response) } 23 | let(:response) { stub("Response", :body => "", :code => 200) } 24 | 25 | it "requires either the regionid or state option" do 26 | lambda { 27 | Rubillow::Neighborhood.region_children() 28 | }.should raise_error(ArgumentError, "The regionid or state option is required") 29 | end 30 | 31 | it "fetches the XML" do 32 | Rubillow::Request.stub(:request).and_return(request) 33 | 34 | response = Rubillow::Neighborhood.region_children({ :state => 'WA', :city => 'Seattle', :childtype => 'neighborhood' }) 35 | response.should be_an_instance_of(Rubillow::Models::RegionChildren) 36 | end 37 | end 38 | 39 | describe Rubillow::Neighborhood, ".region_chart" do 40 | let(:request) { stub("Request", :get => response) } 41 | let(:response) { stub("Response", :body => "", :code => 200) } 42 | 43 | it "requires the unit_type option" do 44 | lambda { 45 | Rubillow::Neighborhood.region_chart() 46 | }.should raise_error(ArgumentError, "The unit_type option is required") 47 | end 48 | 49 | it "fetches the XML" do 50 | Rubillow::Request.stub(:request).and_return(request) 51 | 52 | response = Rubillow::Neighborhood.region_chart({ :city => 'Seattle', :state => 'WA', :unit_type => 'percent', :width => 300, :height => 150 }) 53 | response.should be_an_instance_of(Rubillow::Models::RegionChart) 54 | end 55 | end -------------------------------------------------------------------------------- /spec/rubillow/request_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Request, ".get" do 4 | let(:request) { stub("Request", :get => response) } 5 | let(:response) { stub("Response", :body => '', :code => 200) } 6 | let(:zwsid) { "xyz123-oo" } 7 | 8 | before do 9 | Rubillow::Request.stub(:uri).and_return("/") 10 | Rubillow::Request.stub(:request).and_return(request) 11 | end 12 | 13 | it "constructs the request URI" do 14 | Rubillow::Request.should_receive(:uri).with("/", :option => 1) 15 | Rubillow::Request.get("/", :option => 1) 16 | end 17 | 18 | it "includes access key when configured" do 19 | Rubillow::Request.should_receive(:uri).with("/", :zws_id => zwsid) 20 | 21 | Rubillow.configuration.zwsid = zwsid 22 | Rubillow::Request.get("/") 23 | end 24 | 25 | it "allows overriding of configured access key" do 26 | Rubillow::Request.should_receive(:uri).with("/", :zws_id => "abc890_11") 27 | 28 | Rubillow.configuration.zwsid = zwsid 29 | Rubillow::Request.get("/", :zws_id => "abc890_11") 30 | end 31 | 32 | it "makes an API request" do 33 | request.should_receive(:get).with("/") 34 | 35 | Rubillow::Request.get("/") 36 | end 37 | 38 | it "returns the XML for a successful response" do 39 | Rubillow::Request.get("/").should == '' 40 | end 41 | 42 | it "returns false for any response code other than 200" do 43 | response.stub(:code => "401") 44 | Rubillow::Request.get("/").should == false 45 | end 46 | end 47 | 48 | describe Rubillow::Request, ".request" do 49 | it "creates a new HTTP client" do 50 | Rubillow::Request.request.should be_a(Net::HTTP) 51 | end 52 | 53 | it "connects to configured host and port" do 54 | Rubillow::Request.request.address.should == Rubillow.configuration.host 55 | Rubillow::Request.request.port.should == Rubillow.configuration.port 56 | end 57 | 58 | it "sets configured open and read timeouts" do 59 | Rubillow::Request.request.open_timeout.should == Rubillow.configuration.http_open_timeout 60 | Rubillow::Request.request.read_timeout.should == Rubillow.configuration.http_read_timeout 61 | end 62 | end 63 | 64 | describe Rubillow::Request, ".uri" do 65 | it "generates a request path and query string based on parameters" do 66 | Rubillow::Request.uri("GetRegionChart", { :key_with_spaces => 12, :normal => "maybe", :camelCased => 1340}).should == "/webservice/GetRegionChart.htm?camelCased=1340&key-with-spaces=12&normal=maybe" 67 | end 68 | end -------------------------------------------------------------------------------- /spec/rubillow/property_details_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::PropertyDetails, ".deep_search_results" do 4 | let(:request) { stub("Request", :get => response) } 5 | let(:response) { stub("Response", :body => "", :code => 200) } 6 | 7 | it "requires the address option" do 8 | lambda { 9 | Rubillow::PropertyDetails.deep_search_results({ :citystatezip => 'Baton Rouge, LA, 70802' }) 10 | }.should raise_error(ArgumentError, "The address option is required") 11 | end 12 | 13 | it "requires the citystatezip option" do 14 | lambda { 15 | Rubillow::PropertyDetails.deep_search_results({ :address => '100 North Blvd' }) 16 | }.should raise_error(ArgumentError, "The citystatezip option is required") 17 | end 18 | 19 | it "fetches the XML" do 20 | Rubillow::Request.stub(:request).and_return(request) 21 | 22 | response = Rubillow::PropertyDetails.deep_search_results({ :address => '2114 Bigelow Ave', :citystatezip => 'Seattle, WA' }) 23 | response.should be_an_instance_of(Rubillow::Models::DeepSearchResult) 24 | end 25 | end 26 | 27 | describe Rubillow::PropertyDetails, ".deep_comps" do 28 | let(:request) { stub("Request", :get => response) } 29 | let(:response) { stub("Response", :body => "", :code => 200) } 30 | 31 | it "requires the zpid option" do 32 | lambda { 33 | Rubillow::PropertyDetails.deep_comps() 34 | }.should raise_error(ArgumentError, "The zpid option is required") 35 | end 36 | 37 | it "requires the count option" do 38 | lambda { 39 | Rubillow::PropertyDetails.deep_comps({ :zpid => '48749425' }) 40 | }.should raise_error(ArgumentError, "The count option is required") 41 | end 42 | 43 | it "fetches the XML" do 44 | Rubillow::Request.stub(:request).and_return(request) 45 | 46 | response = Rubillow::PropertyDetails.deep_comps({ :zpid => '48749425', :count => 5 }) 47 | response.should be_an_instance_of(Rubillow::Models::DeepComps) 48 | end 49 | end 50 | 51 | describe Rubillow::PropertyDetails, ".updated_property_details" do 52 | let(:request) { stub("Request", :get => response) } 53 | let(:response) { stub("Response", :body => "", :code => 200) } 54 | 55 | it "requires the zpid option" do 56 | lambda { 57 | Rubillow::PropertyDetails.updated_property_details() 58 | }.should raise_error(ArgumentError, "The zpid option is required") 59 | end 60 | 61 | it "fetches the XML" do 62 | Rubillow::Request.stub(:request).and_return(request) 63 | 64 | response = Rubillow::PropertyDetails.updated_property_details({ :zpid => '48749425' }) 65 | response.should be_an_instance_of(Rubillow::Models::UpdatedPropertyDetails) 66 | end 67 | end -------------------------------------------------------------------------------- /lib/rubillow/models/monthly_payments.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Monthly payment information 4 | class MonthlyPayments < Base 5 | # @return [Hash] 30 year fixed rate data (:rate, :principal_and_interest, :mortgage_insurance). 6 | # 7 | # @example 8 | # puts thirty_year_fixed[:rate] 9 | # 10 | attr_accessor :thirty_year_fixed 11 | 12 | # @return [Hash] 15 year fixed rate data (:rate, :principal_and_interest, :mortgage_insurance). 13 | # 14 | # @example 15 | # puts fifteen_year_fixed[:rate] 16 | # 17 | attr_accessor :fifteen_year_fixed 18 | 19 | # @return [Hash] 5/1 fixed rate data (:rate, :principal_and_interest, :mortgage_insurance). 20 | # 21 | # @example 22 | # puts five_one_arm[:rate] 23 | # 24 | attr_accessor :five_one_arm 25 | 26 | # @return [String] down payment amount. 27 | attr_accessor :down_payment 28 | 29 | # @return [String] monthly property taxes (estimated). 30 | attr_accessor :monthly_property_taxes 31 | 32 | # @return [String] monthyly hazard insurance (estimated). 33 | attr_accessor :monthly_hazard_insurance 34 | 35 | protected 36 | 37 | # @private 38 | def parse 39 | super 40 | 41 | return if !success? 42 | 43 | @thirty_year_fixed = { 44 | :rate => @parser.xpath('//payment[@loanType="thirtyYearFixed"]/rate').text, 45 | :principal_and_interest => @parser.xpath('//payment[@loanType="thirtyYearFixed"]/monthlyPrincipalAndInterest').text, 46 | :mortgage_insurance => @parser.xpath('//payment[@loanType="thirtyYearFixed"]/monthlyMortgageInsurance').text, 47 | } 48 | @fifteen_year_fixed = { 49 | :rate => @parser.xpath('//payment[@loanType="fifteenYearFixed"]/rate').text, 50 | :principal_and_interest => @parser.xpath('//payment[@loanType="fifteenYearFixed"]/monthlyPrincipalAndInterest').text, 51 | :mortgage_insurance => @parser.xpath('//payment[@loanType="fifteenYearFixed"]/monthlyMortgageInsurance').text, 52 | } 53 | @five_one_arm = { 54 | :rate => @parser.xpath('//payment[@loanType="fiveOneARM"]/rate').text, 55 | :principal_and_interest => @parser.xpath('//payment[@loanType="fiveOneARM"]/monthlyPrincipalAndInterest').text, 56 | :mortgage_insurance => @parser.xpath('//payment[@loanType="fiveOneARM"]/monthlyMortgageInsurance').text, 57 | } 58 | @down_payment = @parser.xpath('//downPayment').text 59 | @monthly_property_taxes = @parser.xpath('//monthlyPropertyTaxes').text 60 | @monthly_hazard_insurance = @parser.xpath('//monthlyHazardInsurance').text 61 | end 62 | end 63 | end 64 | end -------------------------------------------------------------------------------- /lib/rubillow/models.rb: -------------------------------------------------------------------------------- 1 | require "nokogiri" 2 | require "rubillow/helpers/xml_parsing_helper" 3 | 4 | module Rubillow 5 | # Sub module for returned and parsed web service data 6 | module Models 7 | # Base class for data models 8 | class Base 9 | include XmlParsingHelper 10 | 11 | # @return [String] the raw XML content from the service 12 | attr_accessor :xml 13 | 14 | # @private 15 | # @return [Nokogiri::XML::Reader] xml parser 16 | attr_accessor :parser 17 | 18 | # @return [String] response message 19 | attr_accessor :message 20 | 21 | # @return [Integer] response code 22 | attr_accessor :code 23 | 24 | # @return [Boolean] nearing API's daily request limit 25 | attr_accessor :near_limit 26 | 27 | # @private 28 | # Initialize the model 29 | # 30 | # @param [String] xml the raw XML from the service 31 | def initialize(xml) 32 | if !empty?(xml) 33 | @xml = xml 34 | @parser = Nokogiri::XML(xml) { |cfg| cfg.noblanks } 35 | parse 36 | else 37 | @code = -1 38 | @message = 'Error connecting to remote service.' 39 | end 40 | end 41 | 42 | # Was the request successful? 43 | # @return [Boolean] +true+ on successful request 44 | def success? 45 | @code.to_i == 0 46 | end 47 | 48 | protected 49 | 50 | # @private 51 | # Parses the xml content 52 | def parse 53 | @message = @parser.xpath('//message/text').text 54 | @code = @parser.xpath('//message/code').text.to_i 55 | 56 | limit = @parser.xpath('//message/limit-warning') 57 | @near_limit = !limit.empty? && limit.text.downcase == "true" 58 | end 59 | 60 | def empty?(elm) 61 | elm.respond_to?(:empty?) ? elm.empty? : !elm 62 | end 63 | end 64 | end 65 | end 66 | 67 | require "rubillow/models/zpidable" 68 | require "rubillow/models/linkable" 69 | require "rubillow/models/addressable" 70 | require "rubillow/models/zestimateable" 71 | require "rubillow/models/property_basics" 72 | require "rubillow/models/images" 73 | require "rubillow/models/search_result" 74 | require "rubillow/models/chart" 75 | require "rubillow/models/property_chart" 76 | require "rubillow/models/comps" 77 | require "rubillow/models/rate_summary" 78 | require "rubillow/models/monthly_payments" 79 | require "rubillow/models/postings" 80 | require "rubillow/models/posting" 81 | require "rubillow/models/deep_search_result" 82 | require "rubillow/models/deep_comps" 83 | require "rubillow/models/updated_property_details" 84 | require "rubillow/models/demographics" 85 | require "rubillow/models/demographic_value" 86 | require "rubillow/models/region_children" 87 | require "rubillow/models/region_chart" 88 | require "rubillow/models/region" 89 | -------------------------------------------------------------------------------- /spec/xml/get_deep_search_results_missing_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
23366-Torrey-St
5 | Armada MI 48005 6 |
7 | 8 | Request successfully processed 9 | 0 10 | 11 | 12 | 13 | 14 | 83624854 15 | 16 | http://www.zillow.com/homedetails/23366-Torrey-St-Armada-MI-48005/83624854_zpid/ 17 | http://www.zillow.com/homedetails/23366-Torrey-St-Armada-MI-48005/83624854_zpid/#charts-and-data 18 | http://www.zillow.com/homes/83624854_zpid/ 19 | http://www.zillow.com/homes/comps/83624854_zpid/ 20 | 21 |
22 | 23366 Torrey St 23 | 48005 24 | Armada 25 | MI 26 | 42.842281 27 | -82.88019 28 |
29 | 26099 30 | SingleFamily 31 | 2011 32 | 100376.0 33 | 14810 34 | 35 | 89301 36 | 04/09/2013 37 | 38 | -1566 39 | 40 | 74120 41 | 108947 42 | 43 | 0 44 | 45 | 46 | 47 | 48 | http://www.zillow.com/local-info/MI-Armada/r_50765/ 49 | http://www.zillow.com/armada-mi/fsbo/ 50 | http://www.zillow.com/armada-mi/ 51 | 52 | 53 | 54 |
55 |
56 |
57 |
58 | 59 | -------------------------------------------------------------------------------- /spec/xml/get_search_results.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
2114 Bigelow Ave
5 | Seattle, WA 6 |
7 | 8 | Request successfully processed 9 | 0 10 | 11 | 12 | 13 | 14 | 48749425 15 | 16 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/ 17 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data 18 | http://www.zillow.com/homes/48749425_zpid/ 19 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 20 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 21 | http://www.zillow.com/homes/comps/48749425_zpid/ 22 | 23 |
24 | 2114 Bigelow Ave N 25 | 98109 26 | Seattle 27 | WA 28 | 47.637933 29 | -122.347938 30 |
31 | 32 | 1032000 33 | 08/24/2011 34 | 35 | 5900 36 | 37 | 866880 38 | 1259040 39 | 40 | 0 41 | 42 | 43 | 44 | 45 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 46 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 47 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 48 | 49 | 50 | 51 |
52 |
53 |
54 |
55 | 56 | -------------------------------------------------------------------------------- /spec/xml/get_zestimate_missing_value_duration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29366758 5 | 6 | 7 | Request successfully processed 8 | 0 9 | 10 | 11 | 29366758 12 | 13 | http://www.zillow.com/homedetails/7500-Blue-Beach-Cv-Austin-TX-78759/29366758_zpid/ 14 | http://www.zillow.com/homedetails/7500-Blue-Beach-Cv-Austin-TX-78759/29366758_zpid/#charts-and-data 15 | http://www.zillow.com/homes/29366758_zpid/ 16 | http://www.zillow.com/homes/comps/29366758_zpid/ 17 | 18 |
19 | 7500 Blue Beach Cv 20 | 78759 21 | Austin 22 | TX 23 | 30.414619 24 | -97.777438 25 |
26 | 27 | 382942 28 | 04/09/2013 29 | 30 | 4774 31 | 32 | 329330 33 | 436554 34 | 35 | 73 36 | 37 | 38 | 2505 39 | 04/01/2013 40 | 41 | 42 | 43 | 1979 44 | 3056 45 | 46 | 47 | 48 | 49 | 50 | http://www.zillow.com/local-info/TX-Austin/r_10221/ 51 | http://www.zillow.com/austin-tx/fsbo/ 52 | http://www.zillow.com/austin-tx/ 53 | 54 | 55 | 56 | 57 | 92668 58 | 10221 59 | 1440 60 | 54 61 | 62 |
63 |
64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rubillow 2 | 3 | Rubillow is a ruby library to access the [Zillow API](http://www.zillow.com/howto/api/APIOverview.htm). 4 | 5 | Supports all of the API methods, with real ruby classes returned for each call. 6 | 7 | ### [Home Valuation API](http://www.zillow.com/howto/api/HomeValuationAPIOverview.htm) 8 | * [GetSearchResults](http://www.zillow.com/howto/api/GetSearchResults.htm) 9 | * [GetZestimate](http://www.zillow.com/howto/api/GetZestimate.htm) 10 | * [GetChart](http://www.zillow.com/howto/api/GetChart.htm) 11 | * [GetComps](http://www.zillow.com/howto/api/GetComps.htm) 12 | 13 | ### [Neighborhood Data](http://www.zillow.com/webtools/neighborhood-data/) 14 | * [GetDemographics](http://www.zillow.com/howto/api/GetDemographics.htm) 15 | * [GetRegionChildren](http://www.zillow.com/howto/api/GetRegionChildren.htm) 16 | * [GetRegionChart](http://www.zillow.com/howto/api/GetRegionChart.htm) 17 | 18 | ### [Mortgage API](http://www.zillow.com/howto/api/MortgageAPIOverview.htm) 19 | * [GetRateSummary](http://www.zillow.com/howto/api/GetRateSummary.htm) 20 | * [GetMonthlyPayments](http://www.zillow.com/howto/api/GetMonthlyPayments.htm) 21 | 22 | ### [Property Details API](http://www.zillow.com/howto/api/PropertyDetailsAPIOverview.htm) 23 | * [GetDeepSearchResults](http://www.zillow.com/howto/api/GetDeepSearchResults.htm) 24 | * [GetDeepComps](http://www.zillow.com/howto/api/GetDeepComps.htm) 25 | * [GetUpdatedPropertyDetails](http://www.zillow.com/howto/api/GetUpdatedPropertyDetails.htm) 26 | 27 | ### [Postings API](http://www.zillow.com/howto/api/GetRegionPostings.htm) 28 | * [GetRegionPostings](http://www.zillow.com/howto/api/GetRegionPostings.htm) 29 | 30 | # Installing 31 | 32 | gem install rubillow 33 | 34 | or add the following to your Gemfile: 35 | 36 | gem "rubillow" 37 | 38 | # Examples 39 | 40 | Adding setup into an initializer: 41 | 42 | Rubillow.configure do |configuration| 43 | configuration.zwsid = "abcd1234" 44 | end 45 | 46 | Getting property Zestimate: 47 | 48 | property = Rubillow::HomeValuation.zestimate({ :zpid => '48749425' }) 49 | if property.success? 50 | puts property.price 51 | end 52 | 53 | # Documentation 54 | 55 | You should find the documentation for your version of Rubillow on [Rubygems](http://rubygems.org/gems/rubillow). 56 | 57 | # More Information 58 | 59 | * [Rubygems](http://rubygems.org/gems/rubillow) 60 | * [Issues](http://github.com/synewaves/rubillow/issues) 61 | 62 | # Build & Dependency Status 63 | 64 | [![Gem Version](https://badge.fury.io/rb/rubillow.png)](http://badge.fury.io/rb/rubillow) 65 | [![Build Status](https://travis-ci.org/synewaves/rubillow.png?branch=master)](https://travis-ci.org/synewaves/rubillow) 66 | [![Dependency Status](https://gemnasium.com/synewaves/rubillow.png?travis)](https://gemnasium.com/synewaves/rubillow) 67 | [![Code Climate](https://codeclimate.com/github/synewaves/rubillow.png)](https://codeclimate.com/github/synewaves/rubillow) 68 | [![Coverage Status](https://coveralls.io/repos/synewaves/rubillow/badge.png?branch=master)](https://coveralls.io/r/synewaves/rubillow) 69 | 70 | # License 71 | 72 | Rubillow uses the MIT license. See LICENSE for more details. -------------------------------------------------------------------------------- /spec/xml/get_zestimate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 48749425 5 | 6 | 7 | Request successfully processed 8 | 0 9 | 10 | 11 | 48749425 12 | 13 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/ 14 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data 15 | http://www.zillow.com/homes/48749425_zpid/ 16 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 17 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 18 | http://www.zillow.com/homes/comps/48749425_zpid/ 19 | 20 |
21 | 2114 Bigelow Ave N 22 | 98109 23 | Seattle 24 | WA 25 | 47.637933 26 | -122.347938 27 |
28 | 29 | 1032000 30 | 08/24/2011 31 | 32 | 5900 33 | 34 | 866880 35 | 1259040 36 | 37 | 95 38 | 39 | 40 | 3379 41 | 12/17/2012 42 | 43 | 107 44 | 45 | 2154 46 | 5102 47 | 48 | 49 | 50 | 51 | 52 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 53 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 54 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 55 | 56 | 57 | 58 | 59 | 99569 60 | 16037 61 | 207 62 | 59 63 | 64 |
65 |
66 | 67 | -------------------------------------------------------------------------------- /lib/rubillow/models/updated_property_details.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # List of updated attributes for a property. 4 | class UpdatedPropertyDetails < Base 5 | include Zpidable 6 | include Addressable 7 | include Linkable 8 | include Images 9 | 10 | # @return [Hash] number of page views (:current_month, :total). 11 | # 12 | # @example 13 | # puts page_views[:current_month] 14 | # 15 | attr_accessor :page_views 16 | 17 | # @return [String] price. 18 | attr_accessor :price 19 | 20 | # @return [String] neighborhood. 21 | attr_accessor :neighborhood 22 | 23 | # @return [String] elementary school's name. 24 | attr_accessor :elementary_school 25 | 26 | # @return [String] middle school's name. 27 | attr_accessor :middle_school 28 | 29 | # @return [String] school district's name. 30 | attr_accessor :school_district 31 | 32 | # @return [String] Realtor provided home description 33 | attr_accessor :home_description 34 | 35 | # @return [Hash] posting information 36 | # 37 | # @example 38 | # posting.each do |key, value| 39 | # end 40 | # 41 | attr_accessor :posting 42 | 43 | # @return [Hash] list of edited facts 44 | # 45 | # @example 46 | # edited_facts.each do |key, value| 47 | # end 48 | # 49 | attr_accessor :edited_facts 50 | 51 | protected 52 | 53 | # @private 54 | def parse 55 | super 56 | 57 | return if !success? 58 | 59 | extract_zpid(@parser) 60 | extract_links(@parser) 61 | extract_address(@parser) 62 | extract_images(@parser) 63 | 64 | @page_views = { 65 | :current_month => @parser.xpath('//pageViewCount/currentMonth').first.text, 66 | :total => @parser.xpath('//pageViewCount/total').first.text 67 | } 68 | @price = @parser.xpath('//price').first.text 69 | @neighborhood = @parser.xpath('//neighborhood').first.text 70 | @school_district = @parser.xpath('//schoolDistrict').first.text 71 | @elementary_school = @parser.xpath('//elementarySchool').first.text 72 | @middle_school = @parser.xpath('//middleSchool').first.text 73 | @home_description = @parser.xpath('//homeDescription').first.text 74 | 75 | @posting = {} 76 | @parser.xpath('//posting').children.each do |elm| 77 | @posting[underscore(elm.name).to_sym] = elm.text 78 | end 79 | 80 | @edited_facts = {} 81 | @parser.xpath('//editedFacts').children.each do |elm| 82 | @edited_facts[underscore(elm.name).to_sym] = elm.text 83 | end 84 | end 85 | 86 | # @private 87 | def underscore(string) 88 | word = string.to_s.dup 89 | word.gsub!(/::/, '/') 90 | word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') 91 | word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') 92 | word.gsub!(/\-/, '_') 93 | word.downcase! 94 | word 95 | end 96 | end 97 | end 98 | end -------------------------------------------------------------------------------- /spec/xml/get_deep_search_results.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
2114 Bigelow Ave
5 | Seattle, WA 6 |
7 | 8 | Request successfully processed 9 | 0 10 | 11 | 12 | 13 | 14 | 48749425 15 | 16 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/ 17 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data 18 | http://www.zillow.com/homes/48749425_zpid/ 19 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 20 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 21 | http://www.zillow.com/homes/comps/48749425_zpid/ 22 | 23 |
24 | 2114 Bigelow Ave N 25 | 98109 26 | Seattle 27 | WA 28 | 47.637933 29 | -122.347938 30 |
31 | 53033 32 | SingleFamily 33 | 2010 34 | 872000.0 35 | 1924 36 | 4680 37 | 3470 38 | 3.0 39 | 4 40 | 11/26/2008 41 | 1025000 42 | 43 | 1032000 44 | 08/24/2011 45 | 46 | 5900 47 | 48 | 866880 49 | 1259040 50 | 51 | 0 52 | 53 | 54 | 55 | 56 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 57 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 58 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 59 | 60 | 61 | 62 |
63 |
64 |
65 |
66 | 67 | -------------------------------------------------------------------------------- /lib/rubillow/mortgage.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | # Interface for the Mortgage API. 3 | # 4 | # Read the more about this API at: {http://www.zillow.com/howto/api/MortgageAPIOverview.htm} 5 | class Mortgage 6 | # Retrieve the current rates for today and one week ago for each loan type. 7 | # 8 | # Read more at: {http://www.zillow.com/howto/api/GetRateSummary.htm}. 9 | # 10 | # @example 11 | # data = Rubillow::Mortgage.rate_summary 12 | # 13 | # if data.success? 14 | # puts data.today[:thirty_year_fixed] # "4.01" 15 | # puts data.today[:five_one_arm] # "2.73" 16 | # puts data.last_week[:fifteen_year_fixed] # "3.27" 17 | # end 18 | # 19 | # @param [Hash] options The options for the API request. 20 | # @option options [String] :state The state to limit rates. 21 | # @return [Models::RateSummary] Mortgage rate summary. 22 | def self.rate_summary(options = {}) 23 | options = { 24 | :zws_id => Rubillow.configuration.zwsid, 25 | :state => nil, 26 | }.merge!(options) 27 | options[:output] = 'xml' 28 | 29 | Models::RateSummary.new(Rubillow::Request.get("GetRateSummary", options)) 30 | end 31 | 32 | # Retrieve the current monthly payment information for a given loan amount 33 | # 34 | # Read more at: {http://www.zillow.com/howto/api/GetMonthlyPayments.htm}. 35 | # 36 | # \* Either the down or dollars down options are required. 37 | # 38 | # @example 39 | # data = Rubillow::Mortgage.monthly_payments({ :price => "300000", :down => "15", :zip => "98104" }) 40 | # 41 | # if data.success? 42 | # puts data.thirty_year_fixed[:rate] # "4.01" 43 | # puts data.thirty_year_fixed[:mortgage_insurance] # "93" 44 | # 45 | # puts data.down_payment # "45000" 46 | # puts data.monthly_property_taxes # "193" 47 | # end 48 | # 49 | # @param [Hash] options The options for the API request. 50 | # @option options [Float] :price The loan amount. (required) 51 | # @option options [Float] :down The percentage of the price as a down payment. Defaults to 20%. If less than 20%, private mortgage insurance is returned. (required *) 52 | # @option options [Float] :dollarsdown The dollar amount as down payment. Used if down option is omitted. If less than 20% of total price, private mortgage insurance is returned. (required *) 53 | # @option options [String] :zip The ZIP code for the property. If provided, property tax and hazard insurance will be returned. 54 | # @return [Models::MonthlyPayments] Monthly mortage information. 55 | def self.monthly_payments(options = {}) 56 | options = { 57 | :zws_id => Rubillow.configuration.zwsid, 58 | :price => nil, 59 | :down => nil, 60 | :dollarsdown => nil, 61 | :zip => nil, 62 | }.merge!(options) 63 | options[:output] = 'xml' 64 | 65 | if options[:price].nil? 66 | raise ArgumentError, "The price option is required" 67 | end 68 | if options[:down].nil? && options[:dollarsdown].nil? 69 | raise ArgumentError, "Either the down or dollarsdown option is required" 70 | end 71 | 72 | Models::MonthlyPayments.new(Rubillow::Request.get("GetMonthlyPayments", options)) 73 | end 74 | end 75 | end -------------------------------------------------------------------------------- /spec/rubillow/home_valuation_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::HomeValuation, ".search_results" do 4 | let(:request) { stub("Request", :get => response) } 5 | let(:response) { stub("Response", :body => "", :code => 200) } 6 | 7 | it "requires the address option" do 8 | lambda { 9 | Rubillow::HomeValuation.search_results({ :citystatezip => 'Seattle, WA' }) 10 | }.should raise_error(ArgumentError, "The address option is required") 11 | end 12 | 13 | it "requires the citystatezip option" do 14 | lambda { 15 | Rubillow::HomeValuation.search_results({ :address => '2114 Bigelow Ave' }) 16 | }.should raise_error(ArgumentError, "The citystatezip option is required") 17 | end 18 | 19 | it "fetches the XML" do 20 | Rubillow::Request.stub(:request).and_return(request) 21 | 22 | response = Rubillow::HomeValuation.search_results({ :address => '2114 Bigelow Ave', :citystatezip => 'Seattle, WA' }) 23 | response.should be_an_instance_of(Rubillow::Models::SearchResult) 24 | end 25 | end 26 | 27 | describe Rubillow::HomeValuation, ".zestimate" do 28 | let(:request) { stub("Request", :get => response) } 29 | let(:response) { stub("Response", :body => "", :code => 200) } 30 | 31 | it "requires the zpid option" do 32 | lambda { 33 | Rubillow::HomeValuation.zestimate() 34 | }.should raise_error(ArgumentError, "The zpid option is required") 35 | end 36 | 37 | it "fetches the XML" do 38 | Rubillow::Request.stub(:request).and_return(request) 39 | 40 | response = Rubillow::HomeValuation.zestimate({ :zpid => '48749425' }) 41 | response.should be_an_instance_of(Rubillow::Models::SearchResult) 42 | end 43 | end 44 | 45 | describe Rubillow::HomeValuation, ".chart" do 46 | let(:request) { stub("Request", :get => response) } 47 | let(:response) { stub("Response", :body => "", :code => 200) } 48 | 49 | it "requires the zpid option" do 50 | lambda { 51 | Rubillow::HomeValuation.chart() 52 | }.should raise_error(ArgumentError, "The zpid option is required") 53 | end 54 | 55 | it "requires the unit_type option" do 56 | lambda { 57 | Rubillow::HomeValuation.chart({ :zpid => '48749425' }) 58 | }.should raise_error(ArgumentError, "The unit_type option is required") 59 | end 60 | 61 | it "fetches the XML" do 62 | Rubillow::Request.stub(:request).and_return(request) 63 | 64 | response = Rubillow::HomeValuation.chart({ :zpid => '48749425', :unit_type => "percent" }) 65 | response.should be_an_instance_of(Rubillow::Models::PropertyChart) 66 | end 67 | end 68 | 69 | describe Rubillow::HomeValuation, ".comps" do 70 | let(:request) { stub("Request", :get => response) } 71 | let(:response) { stub("Response", :body => "", :code => 200) } 72 | 73 | it "requires the zpid option" do 74 | lambda { 75 | Rubillow::HomeValuation.comps() 76 | }.should raise_error(ArgumentError, "The zpid option is required") 77 | end 78 | 79 | it "requires the count option" do 80 | lambda { 81 | Rubillow::HomeValuation.comps({ :zpid => '48749425' }) 82 | }.should raise_error(ArgumentError, "The count option is required") 83 | end 84 | 85 | it "fetches the XML" do 86 | Rubillow::Request.stub(:request).and_return(request) 87 | 88 | response = Rubillow::HomeValuation.comps({ :zpid => '48749425', :count => 5 }) 89 | response.should be_an_instance_of(Rubillow::Models::Comps) 90 | end 91 | end -------------------------------------------------------------------------------- /spec/rubillow/models/demographics_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::Demographics do 4 | it "populates the data" do 5 | data = Rubillow::Models::Demographics.new(get_xml('get_demographics.xml')) 6 | 7 | data.region.id.should == "250017" 8 | data.region.state.should == "Washington" 9 | data.region.neighborhood.should == "Ballard" 10 | data.region.latitude.should == "47.668329" 11 | data.region.longitude.should == "-122.384536" 12 | data.region.zmmrateurl.should == "http://www.zillow.com/mortgage-rates/wa/seattle/" 13 | 14 | data.links.count.should == 8 15 | 16 | data.charts.count.should == 6 17 | data.charts['Median Condo Value'].should == "http://www.zillow.com/app?chartType=affordability_avgCondoValue&graphType=barChart®ionId=250017®ionType=8&service=chart" 18 | 19 | data.affordability_data.count.should == 20 20 | data.affordability_data['Zillow Home Value Index'][:neighborhood].value.should == "305200" 21 | data.affordability_data['Zillow Home Value Index'][:neighborhood].type.should == "USD" 22 | data.affordability_data['Median Single Family Home Value'][:city].value.should == "377000" 23 | data.affordability_data['Median Condo Value'][:nation].value.should == "155800" 24 | 25 | data.census_data.count.should == 7 26 | data.census_data['HomeSize']['<1000sqft'].value.should == "0.3922527265889" 27 | data.census_data['HomeSize']['<1000sqft'].type.should == "percent" 28 | data.census_data['HomeSize']['1800-2400sqft'].value.should == "0.0699511094396" 29 | data.census_data['HomeType']['Other'].value.should == "1.9343463444890998" 30 | data.census_data['HomeType']['SingleFamily'].value.should == "0.1712158808933" 31 | data.census_data['Occupancy']['Rent'].value.should == "0.64971382" 32 | data.census_data['Occupancy']['Rent'].type.should == "percent" 33 | data.census_data['AgeDecade']['40s'].value.should == "0.159760457231474" 34 | data.census_data['AgeDecade']['40s'].type.should == "percent" 35 | data.census_data['Household']['NoKids'].value.should == "0.850066140827795" 36 | data.census_data['Household']['NoKids'].type.should == "percent" 37 | 38 | data.metrics.count.should == 3 39 | data.metrics['BuiltYear']['<1900'].value.should == "0.0419354838709" 40 | data.metrics['BuiltYear']['<1900'].to_s.should == "0.0419354838709" 41 | data.metrics['BuiltYear']['<1900'].type.should == "percent" 42 | data.metrics['BuiltYear']['1940-1959'].value.should == "0.0537634408602" 43 | data.metrics['BuiltYear']['1940-1959'].type.should == "percent" 44 | data.metrics['People Data']['Median Household Income'][:neighborhood].value.should == "41202.9453206937" 45 | data.metrics['People Data']['Single Females'][:city].value.should == "0.187486853578992" 46 | data.metrics['People Data']['Average Commute Time (Minutes)'][:nation].value.should == "26.375545725891282" 47 | 48 | data.segmentation.count.should == 3 49 | data.segmentation["Makin' It Singles"][:name].should == "Upper-scale urban singles." 50 | data.segmentation["Makin' It Singles"][:description].should == "Pre-middle-age to middle-age singles with upper-scale incomes. May or may not own their own home. Most have college educations and are employed in mid-management professions." 51 | data.segmentation['Aspiring Urbanites'][:name].should == "Urban singles with moderate income." 52 | data.segmentation['Aspiring Urbanites'][:description].should == "Low- to middle-income singles over a wide age range. Some have a college education. They work in a variety of occupations, including some management-level positions." 53 | 54 | data.characteristics.count.should == 4 55 | data.characteristics['Education'].should include("Bachelor's degrees") 56 | data.characteristics['Employment'].should include("Work in office and administrative support occupations") 57 | data.characteristics['People & Culture'].should include("Divorced females") 58 | data.characteristics['Transportation'].should include("Get to work by bus") 59 | end 60 | end -------------------------------------------------------------------------------- /lib/rubillow/models/zestimateable.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Common data for responses containing Zestimate information 4 | module Zestimateable 5 | include Zpidable 6 | include Addressable 7 | include Linkable 8 | 9 | # @return [String] price 10 | attr_accessor :price 11 | 12 | # @return [Date] last updated date 13 | attr_accessor :last_updated 14 | 15 | # @return [Hash] valuation range (values: Strings, keys: :low, :high) 16 | # 17 | # @example 18 | # puts valuation_range[:low] 19 | # 20 | attr_accessor :valuation_range 21 | 22 | # @return [String] change value 23 | attr_accessor :change 24 | 25 | # @return [String] duration of change value 26 | attr_accessor :change_duration 27 | 28 | # @return [String] percentile 29 | attr_accessor :percentile 30 | 31 | # @return [Hash] local real estate links (values: URL strings, keys: :overview, :for_sale_by_owner, :for_sale) 32 | # 33 | # @example 34 | # puts local_real_estate[:overview] 35 | # 36 | attr_accessor :local_real_estate 37 | 38 | # @return [String] region name 39 | attr_accessor :region 40 | 41 | # @return [String] region id 42 | attr_accessor :region_id 43 | 44 | # @return [String] region type 45 | attr_accessor :region_type 46 | 47 | # @return [Hash] Rent Zestimate information (keys: :price, :last_updated, :value_change, :value_duration, :valuation_range => { :low, :high }, :percentile) 48 | attr_accessor :rent_zestimate 49 | 50 | protected 51 | 52 | # @private 53 | def extract_zestimate(xml) 54 | extract_zpid(xml) 55 | extract_links(xml) 56 | extract_address(xml) 57 | 58 | @price = xml.xpath('//zestimate/amount').first.text 59 | @last_updated = Date.strptime(xml.xpath('//zestimate/last-updated').first.text, "%m/%d/%Y") 60 | @valuation_range = { 61 | :low => xml.xpath('//zestimate/valuationRange/low').first.text, 62 | :high => xml.xpath('//zestimate/valuationRange/high').first.text, 63 | } 64 | @change = xml.xpath('//zestimate/valueChange').first.text 65 | if xml.xpath('//rentzestimate/amount').text.length > 0 66 | @rent_zestimate = { 67 | :price => xml.xpath('//rentzestimate/amount').first.text, 68 | :last_updated => xml.xpath('//rentzestimate/last-updated').first.text, 69 | :value_change => xml.xpath('//rentzestimate/valueChange').first.text, 70 | :value_duration => xml.xpath('//rentzestimate').first.xpath("//valueChange").first.attr("duration"), 71 | :valuation_range => { 72 | :low => xml.xpath('//rentzestimate/valuationRange/low').first.text, 73 | :high => xml.xpath('//rentzestimate/valuationRange/high').first.text 74 | }, 75 | :percentile => xml.xpath('//rentzestimate/percentile').text 76 | } 77 | else 78 | @rent_zestimate = {} 79 | end 80 | 81 | if tmp = xml.xpath('//zestimate/valueChange').attr('duration') 82 | @change_duration = tmp.value 83 | end 84 | @percentile = xml.xpath('//zestimate/percentile').first.text 85 | 86 | if xml.at_xpath('//localRealEstate/region') 87 | @region = xml.xpath('//localRealEstate/region').attribute('name').value 88 | @region_id = xml.xpath('//localRealEstate/region').attribute('id').value 89 | @region_type = xml.xpath('//localRealEstate/region').attribute('type').value 90 | 91 | @local_real_estate = { 92 | :overview => xml.xpath('//localRealEstate/region/links/overview').first.text, 93 | :for_sale_by_owner => xml.xpath('//localRealEstate/region/links/forSaleByOwner').first.text, 94 | :for_sale => xml.xpath('//localRealEstate/region/links/forSale').first.text, 95 | } 96 | end 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /spec/rubillow/models/updated_property_details_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::UpdatedPropertyDetails do 4 | it "populates the data" do 5 | data = Rubillow::Models::UpdatedPropertyDetails.new(get_xml('get_updated_property_details.xml')) 6 | 7 | data.zpid.should == '48749425' 8 | data.links.count.should == 3 9 | data.links[:homeDetails].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/" 10 | data.links[:photoGallery].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#image=lightbox%3Dtrue" 11 | data.links[:homeInfo].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/" 12 | data.address[:street].should == "2114 Bigelow Ave N" 13 | data.address[:city].should == "Seattle" 14 | data.address[:state].should == "WA" 15 | data.address[:zipcode].should == "98109" 16 | data.address[:latitude].should == "47.637933" 17 | data.address[:longitude].should == "-122.347938" 18 | data.page_views[:current_month].should == "27" 19 | data.page_views[:total].should == "8095" 20 | data.price.should == "1290000" 21 | data.neighborhood.should == "Queen Anne" 22 | data.school_district.should == "Seattle" 23 | data.elementary_school.should == "John Hay" 24 | data.middle_school.should == "McClure" 25 | data.home_description.should == "Bright, spacious, 4 bedroom/3 bath Craftsman, with stunning, expansive views, on one of Queen Anne's finest streets. Views of Lk Union, Lk Washington,the Cascades from Mt. Baker to Mt. Rainier, and the city-from two levels and 2 view decks. Craftsman charm intact: hardwood floors, cove moldings, crystal doorknobs, Batchelder tile fireplace. Huge gourmet eat-in kitchen with slab granite countertops, deluxe master suite, theater-like media room, level rear yard with garden space and covered patio." 26 | data.posting[:status].should == "Active" 27 | data.posting[:agent_name].should == "John Blacksmith" 28 | data.posting[:agent_profile_url].should == "/profile/John.Blacksmith" 29 | data.posting[:brokerage].should == "Lake and Company Real Estate" 30 | data.posting[:type].should == "For sale by agent" 31 | data.posting[:last_updated_date].should == "2008-06-05 10:28:00.0" 32 | data.posting[:external_url].should == "http://mls.lakere.com/srch_mls/detail.php?mode=ag&LN=28097669&t=listings&l=" 33 | data.posting[:mls].should == "28097669" 34 | data.images_count.should == "17" 35 | data.images.count.should == 5 36 | data.images[0].should == "http://photos1.zillow.com/is/image/i0/i4/i3019/IS1d2piz9kupb4z.jpg?op_sharpen=1&qlt=90&hei=400&wid=400" 37 | data.images[1].should == "http://photos3.zillow.com/is/image/i0/i4/i3019/ISkzzhgcun7u03.jpg?op_sharpen=1&qlt=90&hei=400&wid=400" 38 | data.images[2].should == "http://photos2.zillow.com/is/image/i0/i4/i3019/ISkzzhg8wkzq1v.jpg?op_sharpen=1&qlt=90&hei=400&wid=400" 39 | data.images[3].should == "http://photos1.zillow.com/is/image/i0/i4/i3019/ISkzzhg4yirm3n.jpg?op_sharpen=1&qlt=90&hei=400&wid=400" 40 | data.images[4].should == "http://photos3.zillow.com/is/image/i0/i4/i3019/ISkzzhg10gji5f.jpg?op_sharpen=1&qlt=90&hei=400&wid=400" 41 | data.edited_facts[:use_code].should == "SingleFamily" 42 | data.edited_facts[:bedrooms].should == "4" 43 | data.edited_facts[:bathrooms].should == "3.0" 44 | data.edited_facts[:finished_sq_ft].should == "3470" 45 | data.edited_facts[:lot_size_sq_ft].should == "4680" 46 | data.edited_facts[:year_built].should == "1924" 47 | data.edited_facts[:year_updated].should == "2003" 48 | data.edited_facts[:num_floors].should == "2" 49 | data.edited_facts[:basement].should == "Finished" 50 | data.edited_facts[:roof].should == "Composition" 51 | data.edited_facts[:view].should == "Water, City, Mountain" 52 | data.edited_facts[:parking_type].should == "Off-street" 53 | data.edited_facts[:heating_sources].should == "Gas" 54 | data.edited_facts[:heating_system].should == "Forced air" 55 | data.edited_facts[:rooms].should == "Laundry room, Walk-in closet, Master bath, Office, Dining room, Family room, Breakfast nook" 56 | end 57 | end -------------------------------------------------------------------------------- /spec/rubillow/models/comps_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::Comps do 4 | it "populates the results" do 5 | data = Rubillow::Models::Comps.new(get_xml('get_comps.xml')) 6 | 7 | data.principal.should be_a(Rubillow::Models::SearchResult) 8 | data.comparables.count.should == 5 9 | 10 | principal = data.principal 11 | 12 | principal.zpid.should == '48749425' 13 | principal.links.count.should == 5 14 | principal.links[:homedetails].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/" 15 | principal.links[:graphsanddata].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data" 16 | principal.links[:mapthishome].should == "http://www.zillow.com/homes/48749425_zpid/" 17 | principal.links[:myestimator].should == "http://www.zillow.com/myestimator/Edit.htm?zprop=48749425" 18 | principal.links[:comparables].should == "http://www.zillow.com/homes/comps/48749425_zpid/" 19 | principal.address[:street].should == "2114 Bigelow Ave N" 20 | principal.address[:city].should == "Seattle" 21 | principal.address[:state].should == "WA" 22 | principal.address[:zipcode].should == "98109" 23 | principal.address[:latitude].should == "47.637933" 24 | principal.address[:longitude].should == "-122.347938" 25 | principal.price.should == "1032000" 26 | principal.percentile.should == "95" 27 | principal.last_updated.strftime("%m/%d/%Y").should == "08/24/2011" 28 | principal.valuation_range[:low].should == "866880" 29 | principal.valuation_range[:high].should == "1259040" 30 | principal.change.should == "5900" 31 | principal.change_duration.should == "30" 32 | principal.local_real_estate.count.should == 3 33 | principal.local_real_estate[:overview].should == "http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/" 34 | principal.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/" 35 | principal.local_real_estate[:for_sale].should == "http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/" 36 | principal.region.should == "East Queen Anne" 37 | principal.region_type.should == "neighborhood" 38 | principal.region_id.should == "271856" 39 | 40 | comp = data.comparables['0.0813344'] 41 | comp.should be_a(Rubillow::Models::SearchResult) 42 | 43 | comp.zpid.should == '48768095' 44 | comp.links.count.should == 5 45 | comp.links[:homedetails].should == "http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/" 46 | comp.links[:graphsanddata].should == "http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/#charts-and-data" 47 | comp.links[:mapthishome].should == "http://www.zillow.com/homes/48768095_zpid/" 48 | comp.links[:myestimator].should == "http://www.zillow.com/myestimator/Edit.htm?zprop=48768095" 49 | comp.links[:comparables].should == "http://www.zillow.com/homes/comps/48768095_zpid/" 50 | comp.address[:street].should == "2538 Mayfair Ave N" 51 | comp.address[:city].should == "Seattle" 52 | comp.address[:state].should == "WA" 53 | comp.address[:zipcode].should == "98109" 54 | comp.address[:latitude].should == "47.642566" 55 | comp.address[:longitude].should == "-122.352512" 56 | comp.price.should == "584400" 57 | comp.percentile.should == "75" 58 | comp.last_updated.strftime("%m/%d/%Y").should == "08/24/2011" 59 | comp.valuation_range[:low].should == "449988" 60 | comp.valuation_range[:high].should == "625308" 61 | comp.change.should == "-100" 62 | comp.change_duration.should == "30" 63 | comp.local_real_estate.count.should == 3 64 | comp.local_real_estate[:overview].should == "http://www.zillow.com/local-info/WA-Seattle/North-Queen-Anne/r_271942/" 65 | comp.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/homes/fsbo/North-Queen-Anne-Seattle-WA/" 66 | comp.local_real_estate[:for_sale].should == "http://www.zillow.com/homes/for_sale/North-Queen-Anne-Seattle-WA/" 67 | comp.region.should == "North Queen Anne" 68 | comp.region_type.should == "neighborhood" 69 | comp.region_id.should == "271942" 70 | end 71 | end -------------------------------------------------------------------------------- /spec/xml/get_updated_property_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 48749425 5 | 6 | 7 | Request successfully processed 8 | 0 9 | 10 | 11 | 48749425 12 | 13 | 27 14 | 8095 15 | 16 |
17 | 2114 Bigelow Ave N 18 | 98109 19 | Seattle 20 | WA 21 | 47.637933 22 | -122.347938 23 |
24 | 25 | Active 26 | John Blacksmith 27 | /profile/John.Blacksmith 28 | Lake and Company Real Estate 29 | For sale by agent 30 | 2008-06-05 10:28:00.0 31 | http://mls.lakere.com/srch_mls/detail.php?mode=ag&LN=28097669&t=listings&l= 32 | 28097669 33 | 34 | 1290000 35 | 36 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/ 37 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#image=lightbox%3Dtrue 38 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/ 39 | 40 | 41 | 17 42 | 43 | http://photos1.zillow.com/is/image/i0/i4/i3019/IS1d2piz9kupb4z.jpg?op_sharpen=1&qlt=90&hei=400&wid=400 44 | http://photos3.zillow.com/is/image/i0/i4/i3019/ISkzzhgcun7u03.jpg?op_sharpen=1&qlt=90&hei=400&wid=400 45 | http://photos2.zillow.com/is/image/i0/i4/i3019/ISkzzhg8wkzq1v.jpg?op_sharpen=1&qlt=90&hei=400&wid=400 46 | http://photos1.zillow.com/is/image/i0/i4/i3019/ISkzzhg4yirm3n.jpg?op_sharpen=1&qlt=90&hei=400&wid=400 47 | http://photos3.zillow.com/is/image/i0/i4/i3019/ISkzzhg10gji5f.jpg?op_sharpen=1&qlt=90&hei=400&wid=400 48 | 49 | 50 | 51 | SingleFamily 52 | 4 53 | 3.0 54 | 3470 55 | 4680 56 | 1924 57 | 2003 58 | 2 59 | Finished 60 | Composition 61 | Water, City, Mountain 62 | Off-street 63 | Gas 64 | Forced air 65 | Laundry room, Walk-in closet, Master bath, Office, Dining room, Family room, Breakfast nook 66 | 67 | Bright, spacious, 4 bedroom/3 bath Craftsman, with stunning, expansive views, on one of Queen Anne's finest streets. Views of Lk Union, Lk Washington,the Cascades from Mt. Baker to Mt. Rainier, and the city-from two levels and 2 view decks. Craftsman charm intact: hardwood floors, cove moldings, crystal doorknobs, Batchelder tile fireplace. Huge gourmet eat-in kitchen with slab granite countertops, deluxe master suite, theater-like media room, level rear yard with garden space and covered patio. 68 | Queen Anne 69 | Seattle 70 | John Hay 71 | McClure 72 |
73 |
74 | 75 | -------------------------------------------------------------------------------- /lib/rubillow/property_details.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | # Interface for the Property Details API. 3 | # 4 | # Read the more about this API at: {http://www.zillow.com/howto/api/PropertyDetailsAPIOverview.htm} 5 | class PropertyDetails 6 | # Retrieve extended details for a property. 7 | # 8 | # Read more at: {http://www.zillow.com/howto/api/GetDeepSearchResults.htm}. 9 | # 10 | # @example 11 | # data = Rubillow::PropertyDetails.deep_search_results({ :address => '2114 Bigelow Ave', :citystatezip => 'Seattle, WA' }) 12 | # 13 | # if data.success? 14 | # puts data.tax_assessment_year # "2010" 15 | # puts data.last_sold_price # "1025000" 16 | # puts data.address[:latitude] # "47.637933" 17 | # end 18 | # 19 | # @param [Hash] options The options for the API request. 20 | # @option options [String] :address The address of the property to search. (required) 21 | # @option options [String] :citystatezip The city+state combination and/or ZIP code for which to search. Note that giving both city and state is required. Using just one will not work. (required) 22 | # @option options [Boolean] :rentzestimate Return Rent Zestimate information if available. Default: false 23 | # @return [Models::DeepSearchResult] Extended property details. 24 | def self.deep_search_results(options = {}) 25 | options = { 26 | :zws_id => Rubillow.configuration.zwsid, 27 | :address => nil, 28 | :citystatezip => nil, 29 | :rentzestimate => false, 30 | }.merge!(options) 31 | 32 | if options[:address].nil? 33 | raise ArgumentError, "The address option is required" 34 | end 35 | if options[:citystatezip].nil? 36 | raise ArgumentError, "The citystatezip option is required" 37 | end 38 | 39 | Models::DeepSearchResult.new(Rubillow::Request.get("GetDeepSearchResults", options)) 40 | end 41 | 42 | # Retrieve extended details for property and its comps. 43 | # 44 | # Read more at: {http://www.zillow.com/howto/api/GetDeepComps.htm}. 45 | # 46 | # @example 47 | # data = Rubillow::PropertyDetails.deep_comps({ :zpid => '48749425', :count => 5 }) 48 | # 49 | # if data.success? 50 | # puts data.principal.price # "1032000" 51 | # data.comparables.each |comp| 52 | # puts comp.price 53 | # puts comp.address[:street] 54 | # end 55 | # end 56 | # 57 | # @param [Hash] options The options for the API request. 58 | # @option options [Integer] :zpid The Zillow Property ID of the property. (required) 59 | # @option options [Integer] :count The number of comps to return, between 1 and 25 inclusive. (required) 60 | # @option options [Boolean] :rentzestimate Return Rent Zestimate information if available. Default: false 61 | # @return [Models::DeepComps] Extended property and comp details. 62 | def self.deep_comps(options = {}) 63 | options = { 64 | :zws_id => Rubillow.configuration.zwsid, 65 | :zpid => nil, 66 | :count => nil, 67 | :rentzestimate => false, 68 | }.merge!(options) 69 | 70 | if options[:zpid].nil? 71 | raise ArgumentError, "The zpid option is required" 72 | end 73 | if options[:count].nil? 74 | raise ArgumentError, "The count option is required" 75 | end 76 | 77 | Models::DeepComps.new(Rubillow::Request.get("GetDeepComps", options)) 78 | end 79 | 80 | # Retrieve updated property facts for a given property. 81 | # 82 | # Read more at: {http://www.zillow.com/howto/api/GetUpdatedPropertyDetails.htm}. 83 | # 84 | # @example 85 | # data = Rubillow::PropertyDetails.updated_property_details({ :zpid => '48749425' }) 86 | # 87 | # if data.success? 88 | # puts data.posting[:status] # "1032000" 89 | # puts data.posting[:type] # "For sale by agent" 90 | # data.edited_facts.each |fact| 91 | # puts fact 92 | # end 93 | # end 94 | # 95 | # @param [Hash] options The options for the API request. 96 | # @option options [Integer] :zpid The Zillow Property ID of the property. (required) 97 | # @return [Models::UpdatedPropertyDetails] Updated property information. 98 | def self.updated_property_details(options = {}) 99 | options = { 100 | :zws_id => Rubillow.configuration.zwsid, 101 | :zpid => nil, 102 | }.merge!(options) 103 | 104 | if options[:zpid].nil? 105 | raise ArgumentError, "The zpid option is required" 106 | end 107 | 108 | Models::UpdatedPropertyDetails.new(Rubillow::Request.get("GetUpdatedPropertyDetails", options)) 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/rubillow/models/demographics.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | module Models 3 | # Demographics data 4 | class Demographics < Base 5 | include Linkable 6 | 7 | # @return [Models::Region] region data. 8 | attr_accessor :region 9 | 10 | # @return [Hash] Charts (key: name, value: url). 11 | # 12 | # @example 13 | # charts.each do |name, value| 14 | # end 15 | # 16 | attr_accessor :charts 17 | 18 | # @return [Hash] Metrics ([high level point][low level point]). 19 | # 20 | # @example 21 | # puts metrics['BuiltYear']['<1900'] 22 | # 23 | attr_accessor :metrics 24 | 25 | # @return [Hash] Affordability metrics (key: name, value: value). 26 | # 27 | # @example 28 | # puts affordability_data['Zillow Home Value Index'][:neighborhood] 29 | # 30 | attr_accessor :affordability_data 31 | 32 | # @return [Hash] US Census metrics ([high level point][low level point]). 33 | # 34 | # @example 35 | # puts census_data['Household']['NoKids'] 36 | attr_accessor :census_data 37 | 38 | # @return [Hash] Segmentation metrics (key: name, value: [:name, :description]) 39 | # 40 | # @example 41 | # puts segmentation["Makin' It Singles"][:name] 42 | # puts segmentation["Makin' It Singles"][:description] 43 | # 44 | attr_accessor :segmentation 45 | 46 | # @return [Hash] Characteristics metrics ([high level point] -> hash) 47 | # 48 | # @example 49 | # characteristics['Education'] 50 | attr_accessor :characteristics 51 | 52 | protected 53 | 54 | # @private 55 | def parse 56 | super 57 | 58 | return if !success? 59 | 60 | extract_links(@parser) 61 | 62 | @region = Region.new(@parser.xpath('//region').to_xml) 63 | 64 | @charts = {} 65 | @parser.xpath('//charts').children.each do |elm| 66 | if elm.xpath('name').attribute('deprecated').nil? 67 | @charts[elm.xpath('name').text] = elm.xpath('url').text 68 | end 69 | end 70 | 71 | @metrics = {} 72 | @affordability_data = {} 73 | @census_data = {} 74 | @segmentation = {} 75 | @characteristics = {} 76 | 77 | @parser.xpath('//pages').children.each do |page| 78 | page.xpath('tables').children.each do |table| 79 | table_name = table.xpath('name').text 80 | 81 | if table_name == "Affordability Data" && table.parent.parent.xpath('name').text == "Affordability" 82 | extract_affordability_data(table) 83 | elsif table_name[0,14] == "Census Summary" 84 | extract_census_data(table, table_name[15, table_name.length]) 85 | else 86 | extract_data(table, table_name) 87 | end 88 | end 89 | end 90 | 91 | @parser.xpath('//segmentation').children.each do |segment| 92 | @segmentation[segment.xpath('title').text] = { 93 | :name => segment.xpath('name').text, 94 | :description => segment.xpath('description').text, 95 | } 96 | end 97 | 98 | @parser.xpath('//uniqueness').children.each do |category| 99 | key = category.attribute('type').text 100 | @characteristics[key] = [] 101 | category.xpath('characteristic').each do |c| 102 | @characteristics[key] << c.text 103 | end 104 | end 105 | end 106 | 107 | # @private 108 | def extract_affordability_data(xml) 109 | xml.xpath('data').children.each do |data| 110 | @affordability_data[data.xpath('name').text] = extract_metrics(data) 111 | end 112 | end 113 | 114 | # @private 115 | def extract_census_data(xml, type) 116 | @census_data[type] = {} 117 | 118 | xml.xpath('data').children.each do |data| 119 | @census_data[type][data.xpath('name').text] = extract_metrics(data) 120 | end 121 | end 122 | 123 | # @private 124 | def extract_data(xml, type) 125 | if @metrics[type].nil? 126 | @metrics[type] = {} 127 | end 128 | 129 | xml.xpath('data').children.each do |data| 130 | @metrics[type][data.xpath('name').text] = extract_metrics(data) 131 | end 132 | end 133 | 134 | # @private 135 | def extract_metrics(xml) 136 | if xml.xpath('values').empty? 137 | DemographicValue.new(xml.xpath('value')) 138 | else 139 | { 140 | :neighborhood => DemographicValue.new(xml.xpath('values/neighborhood/value')), 141 | :city => DemographicValue.new(xml.xpath('values/city/value')), 142 | :nation => DemographicValue.new(xml.xpath('values/nation/value')), 143 | :zip => DemographicValue.new(xml.xpath('values/zip/value')), 144 | } 145 | end 146 | end 147 | end 148 | end 149 | end -------------------------------------------------------------------------------- /spec/rubillow/models/deep_search_results_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::DeepSearchResult do 4 | it "populates the data" do 5 | data = Rubillow::Models::DeepSearchResult.new(get_xml('get_deep_search_results.xml')) 6 | 7 | data.zpid.should == '48749425' 8 | data.links.count.should == 5 9 | data.links[:homedetails].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/" 10 | data.links[:graphsanddata].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data" 11 | data.links[:mapthishome].should == "http://www.zillow.com/homes/48749425_zpid/" 12 | data.links[:myestimator].should == "http://www.zillow.com/myestimator/Edit.htm?zprop=48749425" 13 | data.links[:comparables].should == "http://www.zillow.com/homes/comps/48749425_zpid/" 14 | data.address[:street].should == "2114 Bigelow Ave N" 15 | data.address[:city].should == "Seattle" 16 | data.address[:state].should == "WA" 17 | data.address[:zipcode].should == "98109" 18 | data.address[:latitude].should == "47.637933" 19 | data.address[:longitude].should == "-122.347938" 20 | data.price.should == "1032000" 21 | data.percentile.should == "0" 22 | data.last_updated.strftime("%m/%d/%Y").should == "08/24/2011" 23 | data.valuation_range[:low].should == "866880" 24 | data.valuation_range[:high].should == "1259040" 25 | data.change.should == "5900" 26 | data.change_duration.should == "30" 27 | data.local_real_estate.count.should == 3 28 | data.local_real_estate[:overview].should == "http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/" 29 | data.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/" 30 | data.local_real_estate[:for_sale].should == "http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/" 31 | data.region.should == "East Queen Anne" 32 | data.region_type.should == "neighborhood" 33 | data.region_id.should == "271856" 34 | data.fips_county.should == "53033" 35 | data.tax_assessment_year.should == "2010" 36 | data.tax_assessment.should == "872000.0" 37 | data.year_built.should == "1924" 38 | data.lot_size_square_feet.should == "4680" 39 | data.finished_square_feet.should == "3470" 40 | data.bathrooms.should == "3.0" 41 | data.bedrooms.should == "4" 42 | data.total_rooms.should == "" 43 | data.last_sold_date.strftime("%m/%d/%Y").should == "11/26/2008" 44 | data.last_sold_price.should == "1025000" 45 | data.use_code.should == "SingleFamily" 46 | end 47 | 48 | it "populates the data" do 49 | data = Rubillow::Models::DeepSearchResult.new(get_xml('get_deep_search_results_duplicated.xml')) 50 | 51 | data.zpid.should == '66109976' 52 | data.links.count.should == 4 53 | data.links[:homedetails].should == "http://www.zillow.com/homedetails/3651-Louisiana-St-APT-205-San-Diego-CA-92104/2126762315_zpid/" 54 | data.links[:graphsanddata].should == "http://www.zillow.com/homedetails/3651-Louisiana-St-APT-205-San-Diego-CA-92104/66109976_zpid/#charts-and-data" 55 | data.links[:mapthishome].should == "http://www.zillow.com/homes/2126762315_zpid/" 56 | data.links[:comparables].should == "http://www.zillow.com/homes/comps/2126762315_zpid/" 57 | data.address[:street].should == "3651 Louisiana St APT 205" 58 | data.address[:city].should == "San Diego" 59 | data.address[:state].should == "CA" 60 | data.address[:zipcode].should == "92104" 61 | data.address[:latitude].should == "32.744895" 62 | data.address[:longitude].should == "-117.139743" 63 | data.price.should == "249461" 64 | data.percentile.should == "0" 65 | data.last_updated.strftime("%m/%d/%Y").should == "04/02/2013" 66 | data.valuation_range[:low].should == "219526" 67 | data.valuation_range[:high].should == "271912" 68 | data.change.should == "20262" 69 | data.change_duration.should == "30" 70 | data.local_real_estate.count.should == 3 71 | data.local_real_estate[:overview].should == "http://www.zillow.com/local-info/CA-San-Diego/North-Park/r_274717/" 72 | data.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/north-park-san-diego-ca/fsbo/" 73 | data.local_real_estate[:for_sale].should == "http://www.zillow.com/north-park-san-diego-ca/" 74 | data.region.should == "North Park" 75 | data.region_type.should == "neighborhood" 76 | data.region_id.should == "274717" 77 | data.fips_county.should == "6073" 78 | data.tax_assessment_year.should == "2012" 79 | data.tax_assessment.should == "180539.0" 80 | data.year_built.should == "1985" 81 | data.lot_size_square_feet.should == "" 82 | data.finished_square_feet.should == "800" 83 | data.bathrooms.should == "2.0" 84 | data.bedrooms.should == "2" 85 | data.total_rooms.should == "" 86 | data.last_sold_date.strftime("%m/%d/%Y").should == "06/10/2011" 87 | data.last_sold_price.should == "186000" 88 | data.use_code.should == "Condominium" 89 | end 90 | 91 | it "doesn't raise an error when data is missing" do 92 | lambda { 93 | Rubillow::Models::DeepSearchResult.new(get_xml("get_deep_search_results_missing_data.xml")) 94 | }.should_not raise_error 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/rubillow/models/deep_comps_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::DeepComps do 4 | it "populates the data" do 5 | data = Rubillow::Models::DeepComps.new(get_xml('get_deep_comps.xml')) 6 | 7 | data.principal.should be_a(Rubillow::Models::DeepSearchResult) 8 | data.comparables.count.should == 5 9 | 10 | principal = data.principal 11 | principal.zpid.should == '48749425' 12 | principal.links.count.should == 5 13 | principal.links[:homedetails].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/" 14 | principal.links[:graphsanddata].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data" 15 | principal.links[:mapthishome].should == "http://www.zillow.com/homes/48749425_zpid/" 16 | principal.links[:myestimator].should == "http://www.zillow.com/myestimator/Edit.htm?zprop=48749425" 17 | principal.links[:comparables].should == "http://www.zillow.com/homes/comps/48749425_zpid/" 18 | principal.address[:street].should == "2114 Bigelow Ave N" 19 | principal.address[:city].should == "Seattle" 20 | principal.address[:state].should == "WA" 21 | principal.address[:zipcode].should == "98109" 22 | principal.address[:latitude].should == "47.637933" 23 | principal.address[:longitude].should == "-122.347938" 24 | principal.price.should == "1032000" 25 | principal.percentile.should == "95" 26 | principal.last_updated.strftime("%m/%d/%Y").should == "08/24/2011" 27 | principal.valuation_range[:low].should == "866880" 28 | principal.valuation_range[:high].should == "1259040" 29 | principal.change.should == "5900" 30 | principal.change_duration.should == "30" 31 | principal.local_real_estate.count.should == 3 32 | principal.local_real_estate[:overview].should == "http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/" 33 | principal.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/" 34 | principal.local_real_estate[:for_sale].should == "http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/" 35 | principal.region.should == "East Queen Anne" 36 | principal.region_type.should == "neighborhood" 37 | principal.region_id.should == "271856" 38 | principal.fips_county.should == "" 39 | principal.tax_assessment_year.should == "2010" 40 | principal.tax_assessment.should == "872000.0" 41 | principal.year_built.should == "1924" 42 | principal.lot_size_square_feet.should == "4680" 43 | principal.finished_square_feet.should == "3470" 44 | principal.bathrooms.should == "3.0" 45 | principal.bedrooms.should == "4" 46 | principal.total_rooms.should == "" 47 | principal.last_sold_date.strftime("%m/%d/%Y").should == "11/26/2008" 48 | principal.last_sold_price.should == "1025000" 49 | principal.use_code.should == "" 50 | 51 | comp = data.comparables["0.0813344"] 52 | comp.should be_a(Rubillow::Models::DeepSearchResult) 53 | comp.zpid.should == '48768095' 54 | comp.links.count.should == 5 55 | comp.links[:homedetails].should == "http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/" 56 | comp.links[:graphsanddata].should == "http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/#charts-and-data" 57 | comp.links[:mapthishome].should == "http://www.zillow.com/homes/48768095_zpid/" 58 | comp.links[:myestimator].should == "http://www.zillow.com/myestimator/Edit.htm?zprop=48768095" 59 | comp.links[:comparables].should == "http://www.zillow.com/homes/comps/48768095_zpid/" 60 | comp.address[:street].should == "2538 Mayfair Ave N" 61 | comp.address[:city].should == "Seattle" 62 | comp.address[:state].should == "WA" 63 | comp.address[:zipcode].should == "98109" 64 | comp.address[:latitude].should == "47.642566" 65 | comp.address[:longitude].should == "-122.352512" 66 | comp.price.should == "584400" 67 | comp.percentile.should == "75" 68 | comp.last_updated.strftime("%m/%d/%Y").should == "08/24/2011" 69 | comp.valuation_range[:low].should == "449988" 70 | comp.valuation_range[:high].should == "625308" 71 | comp.change.should == "-100" 72 | comp.change_duration.should == "30" 73 | comp.local_real_estate.count.should == 3 74 | comp.local_real_estate[:overview].should == "http://www.zillow.com/local-info/WA-Seattle/North-Queen-Anne/r_271942/" 75 | comp.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/homes/fsbo/North-Queen-Anne-Seattle-WA/" 76 | comp.local_real_estate[:for_sale].should == "http://www.zillow.com/homes/for_sale/North-Queen-Anne-Seattle-WA/" 77 | comp.region.should == "North Queen Anne" 78 | comp.region_type.should == "neighborhood" 79 | comp.region_id.should == "271942" 80 | comp.fips_county.should == "" 81 | comp.tax_assessment_year.should == "2010" 82 | comp.tax_assessment.should == "431000.0" 83 | comp.year_built.should == "1955" 84 | comp.lot_size_square_feet.should == "16514" 85 | comp.finished_square_feet.should == "2600" 86 | comp.bathrooms.should == "2.75" 87 | comp.bedrooms.should == "4" 88 | comp.total_rooms.should == "8" 89 | comp.last_sold_date.strftime("%m/%d/%Y").should == "07/27/2011" 90 | comp.last_sold_price.should == "545000" 91 | comp.use_code.should == "" 92 | end 93 | end -------------------------------------------------------------------------------- /spec/xml/get_deep_search_results_duplicated.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
3651 louisiana st 205 san diego ca
4 | 92104 5 |
6 | 7 | Request successfully processed 8 | 0 9 | 10 | 11 | 12 | 13 | 66109976 14 | 15 | http://www.zillow.com/homedetails/3651-Louisiana-St-APT-205-San-Diego-CA-92104/66109976_zpid/ 16 | http://www.zillow.com/homedetails/3651-Louisiana-St-APT-205-San-Diego-CA-92104/66109976_zpid/#charts-and-data 17 | http://www.zillow.com/homes/66109976_zpid/ 18 | http://www.zillow.com/homes/comps/66109976_zpid/ 19 | 20 |
21 | 3651 Louisiana St APT 205 22 | 92104 23 | San Diego 24 | CA 25 | 32.744895 26 | -117.139743 27 |
28 | 6073 29 | Condominium 30 | 2012 31 | 180539.0 32 | 1985 33 | 800 34 | 2.0 35 | 2 36 | 06/10/2011 37 | 186000 38 | 39 | 249461 40 | 04/02/2013 41 | 42 | 20262 43 | 44 | 219526 45 | 271912 46 | 47 | 0 48 | 49 | 50 | 1483 51 | 04/01/2013 52 | 53 | 54 | 55 | 1201 56 | 1883 57 | 58 | 59 | 60 | 61 | 62 | http://www.zillow.com/local-info/CA-San-Diego/North-Park/r_274717/ 63 | http://www.zillow.com/north-park-san-diego-ca/fsbo/ 64 | http://www.zillow.com/north-park-san-diego-ca/ 65 | 66 | 67 | 68 |
69 | 70 | 2126762315 71 | 72 | http://www.zillow.com/homedetails/3651-Louisiana-St-APT-205-San-Diego-CA-92104/2126762315_zpid/ 73 | http://www.zillow.com/homes/2126762315_zpid/ 74 | http://www.zillow.com/homes/comps/2126762315_zpid/ 75 | 76 |
77 | 3651 Louisiana St APT 205 78 | 92104 79 | San Diego 80 | CA 81 | 32.7447 82 | -117.13995 83 |
84 | 6073 85 | Condominium 86 | 1985 87 | 800 88 | 1.5 89 | 2 90 | 91 | 246695 92 | 04/02/2013 93 | 94 | 19294 95 | 96 | 214625 97 | 288633 98 | 99 | 0 100 | 101 | 102 | 1343 103 | 04/01/2013 104 | 105 | 106 | 107 | 1222 108 | 1800 109 | 110 | 111 | 112 | 113 | 114 | http://www.zillow.com/local-info/CA-San-Diego/North-Park/r_274717/ 115 | http://www.zillow.com/north-park-san-diego-ca/fsbo/ 116 | http://www.zillow.com/north-park-san-diego-ca/ 117 | 118 | 119 | 120 |
121 |
122 |
123 |
124 | -------------------------------------------------------------------------------- /lib/rubillow/neighborhood.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | # Interface for the Neighborhood API. 3 | # 4 | # Read the more about this API at: {http://www.zillow.com/webtools/neighborhood-data/} 5 | class Neighborhood 6 | # Retrieve demographic data for a given region. 7 | # 8 | # Read more at: {http://www.zillow.com/howto/api/GetDemographics.htm}. 9 | # 10 | # \* At least regionid or state/city, city/neighborhood, or zip options are required. 11 | # 12 | # @example 13 | # data = Rubillow::Neighborhood.demographics({ :state => 'WA', :city => 'Seattle', :neighborhood => 'Ballard' }) 14 | # 15 | # if data.success? 16 | # puts data.charts['Median Condo Value'] 17 | # puts data.affordability_data['Zillow Home Value Index'][:neighborhood] 18 | # data.characteristics.each do |stat| 19 | # stat 20 | # end 21 | # end 22 | # 23 | # @param [Hash] options The options for the API request. 24 | # @option options [String] :regionid The id of the region (required *) 25 | # @option options [String] :state The state of the region (required *) 26 | # @option options [String] :city The city of the region (required *) 27 | # @option options [String] :neighborhood The neighborhood of the region (required *) 28 | # @option options [String] :zip The zopcode of the region (required *) 29 | # @return [Models::Demographics] Region demographics data. 30 | def self.demographics(options = {}) 31 | options = { 32 | :zws_id => Rubillow.configuration.zwsid, 33 | :regionid => nil, 34 | :state => nil, 35 | :city => nil, 36 | :neighborhood => nil, 37 | :zip => nil, 38 | }.merge!(options) 39 | 40 | if options[:regionid].nil? && options[:zip].nil? && (options[:state].nil? || options[:city].nil?) && (options[:city].nil? || options[:neighborhood].nil?) 41 | raise ArgumentError, "The regionid, state and city, city and neighborhood or zip option is required" 42 | end 43 | 44 | Models::Demographics.new(Rubillow::Request.get("GetDemographics", options)) 45 | end 46 | 47 | # Retrieve sub-regions for a region 48 | # 49 | # Read more at: {http://www.zillow.com/howto/api/GetRegionChildren.htm}. 50 | # 51 | # \* At least regionid or state options are required. 52 | # 53 | # @example 54 | # data = Rubillow::Neighborhood.region_children({ :state => 'WA', :city => 'Seattle', :childtype => 'neighborhood' }) 55 | # 56 | # if data.success? 57 | # puts data.region.id # "16037" 58 | # puts data.region.latitude # "47.559364" 59 | # data.regions.each do |region| 60 | # puts region.id 61 | # end 62 | # end 63 | # 64 | # @param [Hash] options The options for the API request. 65 | # @option options [String] :regionid The id of the region (required *) 66 | # @option options [String] :state The state of the region (required *) 67 | # @option options [String] :county The county of the region 68 | # @option options [String] :city The city of the region 69 | # @option options [String] :childType The type of regions to retrieve (available types: +state+, +county+, +city+, +zipcode+, and +neighborhood+) 70 | # @return [Models::RegionChildren] Region children list. 71 | def self.region_children(options = {}) 72 | options = { 73 | :zws_id => Rubillow.configuration.zwsid, 74 | :regionid => nil, 75 | :state => nil, 76 | :county => nil, 77 | :city => nil, 78 | :childtype => nil, 79 | }.merge!(options) 80 | 81 | if options[:regionid].nil? && options[:state].nil? 82 | raise ArgumentError, "The regionid or state option is required" 83 | end 84 | 85 | Models::RegionChildren.new(Rubillow::Request.get("GetRegionChildren", options)) 86 | end 87 | 88 | # Retrieve a chart for the specified region. 89 | # 90 | # Read more at: {http://www.zillow.com/howto/api/GetRegionChart.htm}. 91 | # 92 | # @example 93 | # chart = Rubillow::Neighborhood.chart({ :city => 'Seattle', :state => 'WA', :unit_type => 'percent', :width => 300, :height => 150 }) 94 | # 95 | # if chart.success? 96 | # puts chart.to_html 97 | # end 98 | # 99 | # @param [Hash] options The options for the API request. 100 | # @option options [String] :city The city of the region 101 | # @option options [String] :state The state of the region 102 | # @option options [String] :neighborhood The county of the region 103 | # @option options [String] :zip The zipcode of the region 104 | # @option options [String] :unit-type Show the percent change (+percent+), or dollar change (+dollar+). (required) 105 | # @option options [Integer] :width The width of the image; between 200 and 600, inclusive. 106 | # @option options [Integer] :height The height of the image; between 100 and 300, inclusive. 107 | # @option options [Integer] :chartDuration The duration of past data to show. Valid values are +1year+, +5years+ and +10years+. Defaults to +1year+. 108 | # @return [Models::RegionChart] Region chart. 109 | def self.region_chart(options = {}) 110 | options = { 111 | :zws_id => Rubillow.configuration.zwsid, 112 | :city => nil, 113 | :state => nil, 114 | :neighborhood => nil, 115 | :zip => nil, 116 | :unit_type => nil, 117 | :width => nil, 118 | :height => nil, 119 | :chartDuration => nil, 120 | }.merge!(options) 121 | 122 | if options[:unit_type].nil? 123 | raise ArgumentError, "The unit_type option is required" 124 | end 125 | 126 | Models::RegionChart.new(Rubillow::Request.get("GetRegionChart", options)) 127 | end 128 | end 129 | end -------------------------------------------------------------------------------- /spec/rubillow/models/search_result_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rubillow::Models::SearchResult do 4 | it "populates the results from GetSearchResults" do 5 | data = Rubillow::Models::SearchResult.new(get_xml('get_search_results.xml')) 6 | 7 | data.zpid.should == '48749425' 8 | data.links.count.should == 5 9 | data.links[:homedetails].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/" 10 | data.links[:graphsanddata].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data" 11 | data.links[:mapthishome].should == "http://www.zillow.com/homes/48749425_zpid/" 12 | data.links[:myestimator].should == "http://www.zillow.com/myestimator/Edit.htm?zprop=48749425" 13 | data.links[:comparables].should == "http://www.zillow.com/homes/comps/48749425_zpid/" 14 | data.address[:street].should == "2114 Bigelow Ave N" 15 | data.address[:city].should == "Seattle" 16 | data.address[:state].should == "WA" 17 | data.address[:zipcode].should == "98109" 18 | data.address[:latitude].should == "47.637933" 19 | data.address[:longitude].should == "-122.347938" 20 | data.full_address.should == "2114 Bigelow Ave N, Seattle, WA 98109" 21 | data.price.should == "1032000" 22 | data.percentile.should == "0" 23 | data.last_updated.strftime("%m/%d/%Y").should == "08/24/2011" 24 | data.valuation_range[:low].should == "866880" 25 | data.valuation_range[:high].should == "1259040" 26 | data.change.should == "5900" 27 | data.change_duration.should == "30" 28 | data.local_real_estate.count.should == 3 29 | data.local_real_estate[:overview].should == "http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/" 30 | data.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/" 31 | data.local_real_estate[:for_sale].should == "http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/" 32 | data.region.should == "East Queen Anne" 33 | data.region_type.should == "neighborhood" 34 | data.region_id.should == "271856" 35 | end 36 | 37 | it "populates the results from GetZestimate" do 38 | data = Rubillow::Models::SearchResult.new(get_xml('get_zestimate.xml')) 39 | 40 | data.zpid.should == '48749425' 41 | data.links.count.should == 5 42 | data.links[:homedetails].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/" 43 | data.links[:graphsanddata].should == "http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data" 44 | data.links[:mapthishome].should == "http://www.zillow.com/homes/48749425_zpid/" 45 | data.links[:myestimator].should == "http://www.zillow.com/myestimator/Edit.htm?zprop=48749425" 46 | data.links[:comparables].should == "http://www.zillow.com/homes/comps/48749425_zpid/" 47 | data.address[:street].should == "2114 Bigelow Ave N" 48 | data.address[:city].should == "Seattle" 49 | data.address[:state].should == "WA" 50 | data.address[:zipcode].should == "98109" 51 | data.address[:latitude].should == "47.637933" 52 | data.address[:longitude].should == "-122.347938" 53 | data.price.should == "1032000" 54 | data.percentile.should == "95" 55 | data.last_updated.strftime("%m/%d/%Y").should == "08/24/2011" 56 | data.valuation_range[:low].should == "866880" 57 | data.valuation_range[:high].should == "1259040" 58 | data.change.should == "5900" 59 | data.change_duration.should == "30" 60 | data.local_real_estate.count.should == 3 61 | data.local_real_estate[:overview].should == "http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/" 62 | data.local_real_estate[:for_sale_by_owner].should == "http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/" 63 | data.local_real_estate[:for_sale].should == "http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/" 64 | data.region.should == "East Queen Anne" 65 | data.region_type.should == "neighborhood" 66 | data.region_id.should == "271856" 67 | data.rent_zestimate[:price].should == "3379" 68 | data.rent_zestimate[:last_updated].should == "12/17/2012" 69 | data.rent_zestimate[:value_change].should == "107" 70 | data.rent_zestimate[:value_duration].should == "30" 71 | data.rent_zestimate[:valuation_range][:low].should == "2154" 72 | data.rent_zestimate[:valuation_range][:high].should == "5102" 73 | end 74 | 75 | it "populates the results from GetZestimate with missing region data" do 76 | data = Rubillow::Models::SearchResult.new(get_xml('get_zestimate_missing_region.xml')) 77 | 78 | data.zpid.should == '78264249' 79 | data.links.count.should == 4 80 | data.links[:homedetails].should == "http://www.zillow.com/homedetails/8663-Orchard-Loop-Rd-NE-Leland-NC-28451/78264249_zpid/" 81 | data.links[:graphsanddata].should == "http://www.zillow.com/homedetails/8663-Orchard-Loop-Rd-NE-Leland-NC-28451/78264249_zpid/#charts-and-data" 82 | data.links[:mapthishome].should == "http://www.zillow.com/homes/78264249_zpid/" 83 | data.links[:comparables].should == "http://www.zillow.com/homes/comps/78264249_zpid/" 84 | data.address[:street].should == "8663 Orchard Loop Rd NE" 85 | data.address[:city].should == "Leland" 86 | data.address[:state].should == "NC" 87 | data.address[:zipcode].should == "28451" 88 | data.address[:latitude].should == "34.217408" 89 | data.address[:longitude].should == "-78.054412" 90 | data.price.should == "136518" 91 | data.percentile.should == "58" 92 | data.last_updated.strftime("%m/%d/%Y").should == "12/27/2012" 93 | data.valuation_range[:low].should == "23208" 94 | data.valuation_range[:high].should == "203412" 95 | data.change.should == "-1299" 96 | data.change_duration.should == "30" 97 | data.local_real_estate.should == nil 98 | data.region.should == nil 99 | end 100 | 101 | it "populates the results from GetZestimate with missing valueDuration data" do 102 | data = Rubillow::Models::SearchResult.new(get_xml('get_zestimate_missing_value_duration.xml')) 103 | 104 | data.zpid.should == '29366758' 105 | data.rent_zestimate.should be_a Hash 106 | data.rent_zestimate[:value_duration].should be == '30' 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/rubillow/home_valuation.rb: -------------------------------------------------------------------------------- 1 | module Rubillow 2 | # Interface for the Home Valuation API. 3 | # 4 | # Read the more about this API at: {http://www.zillow.com/howto/api/HomeValuationAPIOverview.htm} 5 | class HomeValuation 6 | # Retrieve a property by the specified address. 7 | # 8 | # Read more at: {http://www.zillow.com/howto/api/GetSearchResults.htm}. 9 | # 10 | # @example 11 | # data = Rubillow::HomeValuation.search_results({ :address => '2114 Bigelow Ave', :citystatezip => 'Seattle, WA' }) 12 | # 13 | # if data.success? 14 | # puts data.zpid # "48749425" 15 | # puts data.price # "1032000" 16 | # end 17 | # 18 | # @param [Hash] options The options for the API request. 19 | # @option options [String] :address The address of the property to search. (required) 20 | # @option options [String] :citystatezip The city+state combination and/or ZIP code for which to search. Note that giving both city and state is required. Using just one will not work. (required) 21 | # @option options [Boolean] :rentzestimate Return Rent Zestimate information if available. Default: false 22 | # @return [Models::SearchResult] Property information. 23 | def self.search_results(options = {}) 24 | options = { 25 | :zws_id => Rubillow.configuration.zwsid, 26 | :address => nil, 27 | :citystatezip => nil, 28 | :rentzestimate => false, 29 | }.merge!(options) 30 | 31 | if options[:address].nil? 32 | raise ArgumentError, "The address option is required" 33 | end 34 | if options[:citystatezip].nil? 35 | raise ArgumentError, "The citystatezip option is required" 36 | end 37 | 38 | Models::SearchResult.new(Rubillow::Request.get("GetSearchResults", options)) 39 | end 40 | 41 | # Retrieve a zestimate for the specified property. 42 | # 43 | # Read more at: {http://www.zillow.com/howto/api/GetZestimate.htm}. 44 | # 45 | # @example 46 | # data = Rubillow::HomeValuation.zestimate({ :zpid => '48749425' }) 47 | # 48 | # if data.success? 49 | # puts data.zpid # "48749425" 50 | # puts data.price # "1032000" 51 | # puts data.value_change # "5900" 52 | # end 53 | # 54 | # @param [Hash] options The options for the API request. 55 | # @option options [Integer] :zpid The Zillow Property ID of the property. (required) 56 | # @option options [Boolean] :rentzestimate Return Rent Zestimate information if available. Default: false 57 | # @return [Models::SearchResult] Property pricing information. 58 | def self.zestimate(options = {}) 59 | options = { 60 | :zws_id => Rubillow.configuration.zwsid, 61 | :zpid => nil, 62 | :rentzestimate => false, 63 | }.merge!(options) 64 | 65 | if options[:zpid].nil? 66 | raise ArgumentError, "The zpid option is required" 67 | end 68 | 69 | Models::SearchResult.new(Rubillow::Request.get("GetZestimate", options)) 70 | end 71 | 72 | # Retrieve a chart for the specified property. 73 | # 74 | # Read more at: {http://www.zillow.com/howto/api/GetChart.htm}. 75 | # 76 | # @example 77 | # chart = Rubillow::HomeValuation.chart({ :zpid => '48749425', :height => '300', :width => '150' }) 78 | # 79 | # if chart.success? 80 | # puts chart.to_html 81 | # end 82 | # 83 | # @param [Hash] options The options for the API request. 84 | # @option options [Integer] :zpid The Zillow Property ID of the property. (required) 85 | # @option options [String] :unit-type Show the percent change ("percent"), or dollar change ("dollar"). (required) 86 | # @option options [Integer] :width The width of the image; between 200 and 600, inclusive. 87 | # @option options [Integer] :height The height of the image; between 100 and 300, inclusive. 88 | # @option options [Integer] :chartDuration The duration of past data to show. Valid values are "1year", "5years" and "10years". If unspecified, the value defaults to "1year". 89 | # @return [Models::PropertyChart] Property chart. 90 | def self.chart(options = {}) 91 | options = { 92 | :zws_id => Rubillow.configuration.zwsid, 93 | :zpid => nil, 94 | :unit_type => nil, 95 | :width => nil, 96 | :height => nil, 97 | :chartDuration => nil, 98 | }.merge!(options) 99 | 100 | if options[:zpid].nil? 101 | raise ArgumentError, "The zpid option is required" 102 | end 103 | if options[:unit_type].nil? 104 | raise ArgumentError, "The unit_type option is required" 105 | end 106 | 107 | Models::PropertyChart.new(Rubillow::Request.get("GetChart", options)) 108 | end 109 | 110 | # Retrieve a list of comps for the specified property. 111 | # 112 | # Read more at: {http://www.zillow.com/howto/api/GetComps.htm}. 113 | # 114 | # @example 115 | # data = Rubillow::HomeValuation.comps({ :zpid => '48749425', :count => 5 }) 116 | # 117 | # if data.success? 118 | # puts data.principal.price # "1032000" 119 | # data.comparables.each do |comp| 120 | # puts comparables.price 121 | # end 122 | # end 123 | # 124 | # @param [Hash] options The options for the API request. 125 | # @option options [Integer] :zpid The Zillow Property ID of the property. (required) 126 | # @option options [Integer] :count The number of comps to return, between 1 and 25 inclusive. (required) 127 | # @option options [Boolean] :rentzestimate Return Rent Zestimate information if available. Default: false 128 | # @return [Models::Comps] Comps Property information and comps list. 129 | def self.comps(options = {}) 130 | options = { 131 | :zws_id => Rubillow.configuration.zwsid, 132 | :zpid => nil, 133 | :count => nil, 134 | :rentzestimate => false, 135 | }.merge!(options) 136 | 137 | if options[:zpid].nil? 138 | raise ArgumentError, "The zpid option is required" 139 | end 140 | if options[:count].nil? 141 | raise ArgumentError, "The count option is required" 142 | end 143 | 144 | Models::Comps.new(Rubillow::Request.get("GetComps", options)) 145 | end 146 | end 147 | end -------------------------------------------------------------------------------- /spec/xml/get_comps.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 48749425 5 | 5 6 | 7 | 8 | Request successfully processed 9 | 0 10 | 11 | 12 | 13 | 14 | 48749425 15 | 16 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/ 17 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data 18 | http://www.zillow.com/homes/48749425_zpid/ 19 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 20 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 21 | http://www.zillow.com/homes/comps/48749425_zpid/ 22 | 23 |
24 | 2114 Bigelow Ave N 25 | 98109 26 | Seattle 27 | WA 28 | 47.637933 29 | -122.347938 30 |
31 | 32 | 1032000 33 | 08/24/2011 34 | 35 | 5900 36 | 37 | 866880 38 | 1259040 39 | 40 | 95 41 | 42 | 43 | 44 | 45 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 46 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 47 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 48 | 49 | 50 | 51 |
52 | 53 | 54 | 48768095 55 | 56 | http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/ 57 | http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/#charts-and-data 58 | http://www.zillow.com/homes/48768095_zpid/ 59 | http://www.zillow.com/myestimator/Edit.htm?zprop=48768095 60 | http://www.zillow.com/myestimator/Edit.htm?zprop=48768095 61 | http://www.zillow.com/homes/comps/48768095_zpid/ 62 | 63 |
64 | 2538 Mayfair Ave N 65 | 98109 66 | Seattle 67 | WA 68 | 47.642566 69 | -122.352512 70 |
71 | 72 | 584400 73 | 08/24/2011 74 | 75 | -100 76 | 77 | 449988 78 | 625308 79 | 80 | 75 81 | 82 | 83 | 84 | 85 | http://www.zillow.com/local-info/WA-Seattle/North-Queen-Anne/r_271942/ 86 | http://www.zillow.com/homes/fsbo/North-Queen-Anne-Seattle-WA/ 87 | http://www.zillow.com/homes/for_sale/North-Queen-Anne-Seattle-WA/ 88 | 89 | 90 | 91 |
92 | 93 | 48690326 94 | 95 | http://www.zillow.com/homedetails/1612-Warren-Ave-N-Seattle-WA-98109/48690326_zpid/ 96 | http://www.zillow.com/homedetails/1612-Warren-Ave-N-Seattle-WA-98109/48690326_zpid/#charts-and-data 97 | http://www.zillow.com/homes/48690326_zpid/ 98 | http://www.zillow.com/myestimator/Edit.htm?zprop=48690326 99 | http://www.zillow.com/myestimator/Edit.htm?zprop=48690326 100 | http://www.zillow.com/homes/comps/48690326_zpid/ 101 | 102 |
103 | 1612 Warren Ave N 104 | 98109 105 | Seattle 106 | WA 107 | 47.633573 108 | -122.354064 109 |
110 | 111 | 1167500 112 | 08/24/2011 113 | 114 | 85400 115 | 116 | 898975 117 | 1482725 118 | 119 | 97 120 | 121 | 122 | 123 | 124 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 125 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 126 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 127 | 128 | 129 | 130 |
131 | 132 | 48689869 133 | 134 | http://www.zillow.com/homedetails/1606-Nob-Hill-Ave-N-Seattle-WA-98109/48689869_zpid/ 135 | http://www.zillow.com/homedetails/1606-Nob-Hill-Ave-N-Seattle-WA-98109/48689869_zpid/#charts-and-data 136 | http://www.zillow.com/homes/48689869_zpid/ 137 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689869 138 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689869 139 | http://www.zillow.com/homes/comps/48689869_zpid/ 140 | 141 |
142 | 1606 Nob Hill Ave N 143 | 98109 144 | Seattle 145 | WA 146 | 47.633416 147 | -122.350494 148 |
149 | 150 | 750100 151 | 08/24/2011 152 | 153 | 2700 154 | 155 | 652587 156 | 877617 157 | 158 | 87 159 | 160 | 161 | 162 | 163 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 164 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 165 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 166 | 167 | 168 | 169 |
170 | 171 | 48689978 172 | 173 | http://www.zillow.com/homedetails/352-Blaine-St-Seattle-WA-98109/48689978_zpid/ 174 | http://www.zillow.com/homedetails/352-Blaine-St-Seattle-WA-98109/48689978_zpid/#charts-and-data 175 | http://www.zillow.com/homes/48689978_zpid/ 176 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689978 177 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689978 178 | http://www.zillow.com/homes/comps/48689978_zpid/ 179 | 180 |
181 | 352 Blaine St 182 | 98109 183 | Seattle 184 | WA 185 | 47.635005 186 | -122.350517 187 |
188 | 189 | 760000 190 | 08/24/2011 191 | 192 | -400 193 | 194 | 661200 195 | 881600 196 | 197 | 88 198 | 199 | 200 | 201 | 202 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 203 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 204 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 205 | 206 | 207 | 208 |
209 | 210 | 48690151 211 | 212 | http://www.zillow.com/homedetails/161-Crockett-St-Seattle-WA-98109/48690151_zpid/ 213 | http://www.zillow.com/homedetails/161-Crockett-St-Seattle-WA-98109/48690151_zpid/#charts-and-data 214 | http://www.zillow.com/homes/48690151_zpid/ 215 | http://www.zillow.com/myestimator/Edit.htm?zprop=48690151 216 | http://www.zillow.com/myestimator/Edit.htm?zprop=48690151 217 | http://www.zillow.com/homes/comps/48690151_zpid/ 218 | 219 |
220 | 161 Crockett St 221 | 98109 222 | Seattle 223 | WA 224 | 47.637268 225 | -122.353885 226 |
227 | 228 | 522600 229 | 08/24/2011 230 | 231 | 7900 232 | 233 | 433758 234 | 627120 235 | 236 | 69 237 | 238 | 239 | 240 | 241 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 242 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 243 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 244 | 245 | 246 | 247 |
248 |
249 |
250 |
251 |
252 | 253 | -------------------------------------------------------------------------------- /spec/xml/get_deep_comps.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 48749425 5 | 5 6 | 7 | 8 | Request successfully processed 9 | 0 10 | 11 | 12 | 13 | 14 | 48749425 15 | 16 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/ 17 | http://www.zillow.com/homedetails/2114-Bigelow-Ave-N-Seattle-WA-98109/48749425_zpid/#charts-and-data 18 | http://www.zillow.com/homes/48749425_zpid/ 19 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 20 | http://www.zillow.com/myestimator/Edit.htm?zprop=48749425 21 | http://www.zillow.com/homes/comps/48749425_zpid/ 22 | 23 |
24 | 2114 Bigelow Ave N 25 | 98109 26 | Seattle 27 | WA 28 | 47.637933 29 | -122.347938 30 |
31 | 2010 32 | 872000.0 33 | 1924 34 | 4680 35 | 3470 36 | 3.0 37 | 4 38 | 11/26/2008 39 | 1025000 40 | 41 | 1032000 42 | 08/24/2011 43 | 44 | 5900 45 | 46 | 866880 47 | 1259040 48 | 49 | 95 50 | 51 | 52 | 53 | 54 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 55 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 56 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 57 | 58 | 59 | 60 |
61 | 62 | 63 | 48768095 64 | 65 | http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/ 66 | http://www.zillow.com/homedetails/2538-Mayfair-Ave-N-Seattle-WA-98109/48768095_zpid/#charts-and-data 67 | http://www.zillow.com/homes/48768095_zpid/ 68 | http://www.zillow.com/myestimator/Edit.htm?zprop=48768095 69 | http://www.zillow.com/myestimator/Edit.htm?zprop=48768095 70 | http://www.zillow.com/homes/comps/48768095_zpid/ 71 | 72 |
73 | 2538 Mayfair Ave N 74 | 98109 75 | Seattle 76 | WA 77 | 47.642566 78 | -122.352512 79 |
80 | 2010 81 | 431000.0 82 | 1955 83 | 16514 84 | 2600 85 | 2.75 86 | 4 87 | 8 88 | 07/27/2011 89 | 545000 90 | 91 | 584400 92 | 08/24/2011 93 | 94 | -100 95 | 96 | 449988 97 | 625308 98 | 99 | 75 100 | 101 | 102 | 103 | 104 | http://www.zillow.com/local-info/WA-Seattle/North-Queen-Anne/r_271942/ 105 | http://www.zillow.com/homes/fsbo/North-Queen-Anne-Seattle-WA/ 106 | http://www.zillow.com/homes/for_sale/North-Queen-Anne-Seattle-WA/ 107 | 108 | 109 | 110 |
111 | 112 | 48810502 113 | 114 | http://www.zillow.com/homedetails/555-Prospect-St-APT-4-Seattle-WA-98109/48810502_zpid/ 115 | http://www.zillow.com/homedetails/555-Prospect-St-APT-4-Seattle-WA-98109/48810502_zpid/#charts-and-data 116 | http://www.zillow.com/homes/48810502_zpid/ 117 | http://www.zillow.com/myestimator/Edit.htm?zprop=48810502 118 | http://www.zillow.com/myestimator/Edit.htm?zprop=48810502 119 | http://www.zillow.com/homes/comps/48810502_zpid/ 120 | 121 |
122 | 555 Prospect St APT 4 123 | 98109 124 | Seattle 125 | WA 126 | 47.628775 127 | -122.345839 128 |
129 | 2010 130 | 288000.0 131 | 1979 132 | 5762 133 | 1237 134 | 2.0 135 | 2 136 | 07/19/2011 137 | 295000 138 | 139 | 301500 140 | 08/24/2011 141 | 142 | -1700 143 | 144 | 238185 145 | 340695 146 | 147 | 34 148 | 149 | 150 | 151 | 152 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 153 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 154 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 155 | 156 | 157 | 158 |
159 | 160 | 49099112 161 | 162 | http://www.zillow.com/homedetails/2500-6th-Ave-N-APT-2-Seattle-WA-98109/49099112_zpid/ 163 | http://www.zillow.com/homedetails/2500-6th-Ave-N-APT-2-Seattle-WA-98109/49099112_zpid/#charts-and-data 164 | http://www.zillow.com/homes/49099112_zpid/ 165 | http://www.zillow.com/myestimator/Edit.htm?zprop=49099112 166 | http://www.zillow.com/myestimator/Edit.htm?zprop=49099112 167 | http://www.zillow.com/homes/comps/49099112_zpid/ 168 | 169 |
170 | 2500 6th Ave N APT 2 171 | 98109 172 | Seattle 173 | WA 174 | 47.642435 175 | -122.345685 176 |
177 | 2010 178 | 821000.0 179 | 1965 180 | 7646 181 | 2000 182 | 1.5 183 | 2 184 | 08/10/2011 185 | 695000 186 | 187 | 735300 188 | 08/24/2011 189 | 190 | 17800 191 | 192 | 522063 193 | 904419 194 | 195 | 87 196 | 197 | 198 | 199 | 200 | http://www.zillow.com/local-info/WA-Seattle/Westlake/r_272022/ 201 | http://www.zillow.com/homes/fsbo/Westlake-Seattle-WA/ 202 | http://www.zillow.com/homes/for_sale/Westlake-Seattle-WA/ 203 | 204 | 205 | 206 |
207 | 208 | 48689869 209 | 210 | http://www.zillow.com/homedetails/1606-Nob-Hill-Ave-N-Seattle-WA-98109/48689869_zpid/ 211 | http://www.zillow.com/homedetails/1606-Nob-Hill-Ave-N-Seattle-WA-98109/48689869_zpid/#charts-and-data 212 | http://www.zillow.com/homes/48689869_zpid/ 213 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689869 214 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689869 215 | http://www.zillow.com/homes/comps/48689869_zpid/ 216 | 217 |
218 | 1606 Nob Hill Ave N 219 | 98109 220 | Seattle 221 | WA 222 | 47.633416 223 | -122.350494 224 |
225 | 2010 226 | 556000.0 227 | 1924 228 | 3200 229 | 2570 230 | 2.0 231 | 3 232 | 04/29/2011 233 | 740000 234 | 235 | 750100 236 | 08/24/2011 237 | 238 | 2700 239 | 240 | 652587 241 | 877617 242 | 243 | 87 244 | 245 | 246 | 247 | 248 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 249 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 250 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 251 | 252 | 253 | 254 |
255 | 256 | 48689978 257 | 258 | http://www.zillow.com/homedetails/352-Blaine-St-Seattle-WA-98109/48689978_zpid/ 259 | http://www.zillow.com/homedetails/352-Blaine-St-Seattle-WA-98109/48689978_zpid/#charts-and-data 260 | http://www.zillow.com/homes/48689978_zpid/ 261 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689978 262 | http://www.zillow.com/myestimator/Edit.htm?zprop=48689978 263 | http://www.zillow.com/homes/comps/48689978_zpid/ 264 | 265 |
266 | 352 Blaine St 267 | 98109 268 | Seattle 269 | WA 270 | 47.635005 271 | -122.350517 272 |
273 | 2010 274 | 572000.0 275 | 1922 276 | 3000 277 | 2670 278 | 2.0 279 | 4 280 | 10/26/2010 281 | 815000 282 | 283 | 760000 284 | 08/24/2011 285 | 286 | -400 287 | 288 | 661200 289 | 881600 290 | 291 | 88 292 | 293 | 294 | 295 | 296 | http://www.zillow.com/local-info/WA-Seattle/East-Queen-Anne/r_271856/ 297 | http://www.zillow.com/homes/fsbo/East-Queen-Anne-Seattle-WA/ 298 | http://www.zillow.com/homes/for_sale/East-Queen-Anne-Seattle-WA/ 299 | 300 | 301 | 302 |
303 |
304 |
305 |
306 |
307 | 308 | -------------------------------------------------------------------------------- /spec/xml/get_demographics.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WA 5 | Seattle 6 | Ballard 7 | 8 | 9 | Request successfully processed 10 | 0 11 | 12 | 13 | 14 | 250017 15 | Washington 16 | Seattle 17 | Ballard 18 | 47.668329 19 | -122.384536 20 | http://www.zillow.com/mortgage-rates/wa/seattle/ 21 | 22 | 23 |
http://www.zillow.com/homes/Ballard-Seattle-WA/
24 | http://www.zillow.com/local-info/WA-Seattle/Ballard-home-value/r_250017/ 25 | http://www.zillow.com/local-info/WA-Seattle/Ballard-homes/r_250017/ 26 | http://www.zillow.com/local-info/WA-Seattle/Ballard-people/r_250017/ 27 | http://www.zillow.com/homes/for_sale/Ballard-Seattle-WA/ 28 | http://www.zillow.com/homes/fsbo/Ballard-Seattle-WA/ 29 | http://www.zillow.com/homes/for_sale/Ballard-Seattle-WA/fore_lt/0_mmm/ 30 | http://www.zillow.com/homes/recently_sold/Ballard-Seattle-WA/ 31 |
32 | 33 | 34 | Median Condo Value 35 | http://www.zillow.com/app?chartType=affordability_avgCondoValue&graphType=barChart&regionId=250017&regionType=8&service=chart 36 | 37 | 38 | Median Home Value 39 | http://www.zillow.com/app?chartType=affordability_avgHomeValue&graphType=barChart&regionId=250017&regionType=8&service=chart 40 | 41 | 42 | Dollars Per Square Feet 43 | http://www.zillow.com/app?chartType=affordability_pricePerSqft&graphType=barChart&regionId=250017&regionType=8&service=chart 44 | 45 | 46 | Zillow Home Value Index Distribution 47 | http://www.zillow.com/app?chartType=affordability_ZindexByDistribution&graphType=barChart&regionId=250017&regionType=8&service=chart 48 | 49 | 50 | Home Type 51 | http://www.zillow.com/app?chartType=home_homeType&graphType=barChart&regionId=250017&regionType=8&service=chart 52 | 53 | 54 | Owners vs. Renters 55 | http://www.zillow.com/app?chartType=home_ownVsRent&graphType=barChart&regionId=250017&regionType=8&service=chart 56 | 57 | 58 | Home Size in Square Feet 59 | http://www.zillow.com/app?chartType=home_homeSize&graphType=barChart&regionId=250017&regionType=8&service=chart 60 | 61 | 62 | Year Built 63 | http://www.zillow.com/app?chartType=home_yearBuilt&graphType=barChart&regionId=250017&regionType=8&service=chart 64 | 65 | 66 | 67 | 68 | 69 | Affordability 70 | 71 | 72 | Affordability Data 73 | 74 | 75 | Zillow Home Value Index 76 | 77 | 78 | 305200 79 | 80 | 81 | 348800 82 | 83 | 84 | 171600 85 | 86 | 87 | 88 | 89 | Median Single Family Home Value 90 | 91 | 92 | 365200 93 | 94 | 95 | 377000 96 | 97 | 98 | 173500 99 | 100 | 101 | 102 | 103 | Median Condo Value 104 | 105 | 106 | 271700 107 | 108 | 109 | 266900 110 | 111 | 112 | 155800 113 | 114 | 115 | 116 | 117 | Median 2-Bedroom Home Value 118 | 119 | 120 | 316200 121 | 122 | 123 | 305700 124 | 125 | 126 | 127700 127 | 128 | 129 | 130 | 131 | Median 3-Bedroom Home Value 132 | 133 | 134 | 368000 135 | 136 | 137 | 377900 138 | 139 | 140 | 156400 141 | 142 | 143 | 144 | 145 | Median 4-Bedroom Home Value 146 | 147 | 148 | 413600 149 | 150 | 151 | 443100 152 | 153 | 154 | 246600 155 | 156 | 157 | 158 | 159 | Percent Homes Decreasing 160 | 161 | 162 | 0.802 163 | 164 | 165 | 0.778 166 | 167 | 168 | 0.734 169 | 170 | 171 | 172 | 173 | Percent Listing Price Reduction 174 | 175 | 176 | 0.291 177 | 178 | 179 | 0.372 180 | 181 | 182 | 0.308 183 | 184 | 185 | 186 | 187 | Median List Price Per Sq Ft 188 | 189 | 190 | 280 191 | 192 | 193 | 247 194 | 195 | 196 | 103 197 | 198 | 199 | 200 | 201 | Median List Price 202 | 203 | 204 | 300000 205 | 206 | 207 | 359000 208 | 209 | 210 | 189900 211 | 212 | 213 | 214 | 215 | Median Sale Price 216 | 217 | 218 | 335000 219 | 220 | 221 | 383600 222 | 223 | 224 | 191700 225 | 226 | 227 | 228 | 229 | Homes For Sale 230 | 231 | 232 | 233 | Homes Recently Sold 234 | 235 | 236 | 12 237 | 238 | 239 | 876 240 | 241 | 242 | 341521 243 | 244 | 245 | 246 | 247 | Property Tax 248 | 249 | 250 | 2780 251 | 252 | 253 | 3569 254 | 255 | 256 | 2117 257 | 258 | 259 | 260 | 261 | Turnover (Sold Within Last Yr.) 262 | 263 | 264 | 0.054 265 | 266 | 267 | 0.042 268 | 269 | 270 | 0.034 271 | 272 | 273 | 274 | 275 | Median Value Per Sq Ft 276 | 277 | 278 | 311 279 | 280 | 281 | 250 282 | 283 | 284 | 104 285 | 286 | 287 | 288 | 289 | 1-Yr. Change 290 | 291 | 292 | -0.054 293 | 294 | 295 | -0.044 296 | 297 | 298 | -0.062 299 | 300 | 301 | 302 | 303 | >Homes For Sale By Owner 304 | 305 | 306 | 0 307 | 308 | 309 | 47 310 | 311 | 312 | 33971 313 | 314 | 315 | 316 | 317 | >New Construction 318 | 319 | 320 | 0 321 | 322 | 323 | 0 324 | 325 | 326 | 30794 327 | 328 | 329 | 330 | 331 | >Foreclosures 332 | 333 | 334 | 15 335 | 336 | 337 | 814 338 | 339 | 340 | 593884 341 | 342 | 343 | 344 | 345 |
346 |
347 |
348 | 349 | Homes & Real Estate 350 | 351 | 352 | Homes & Real Estate Data 353 | 354 | 355 | Owners 356 | 357 | 358 | 0.35028618 359 | 360 | 361 | 0.48412441 362 | 363 | 364 | 0.66268764 365 | 366 | 367 | 368 | 369 | Renters 370 | 371 | 372 | 0.64971382 373 | 374 | 375 | 0.51587559 376 | 377 | 378 | 0.33731236 379 | 380 | 381 | 382 | 383 | Median Home Size (Sq. Ft.) 384 | 385 | 386 | 1180 387 | 388 | 389 | 1540 390 | 391 | 392 | 393 | 394 | Avg. Year Built 395 | 396 | 397 | 1994 398 | 399 | 400 | 1949 401 | 402 | 403 | 404 | 405 | Single-Family Homes 406 | 407 | 408 | 0.1712158808933 409 | 410 | 411 | 0.6496783406203 412 | 413 | 414 | 415 | 416 | Condos 417 | 418 | 419 | 0.6398440269408 420 | 421 | 422 | 0.2483180968364 423 | 424 | 425 | 426 | 427 |
428 | 429 | BuiltYear 430 | 431 | 432 | <1900 433 | 0.0419354838709 434 | 435 | 436 | >2000 437 | 0.4691756272401 438 | 439 | 440 | 1900-1919 441 | 0.1168458781362 442 | 443 | 444 | 1920-1939 445 | 0.04229390681 446 | 447 | 448 | 1940-1959 449 | 0.0537634408602 450 | 451 | 452 | 1960-1979 453 | 0.1121863799283 454 | 455 | 456 | 1980-1999 457 | 0.1637992831541 458 | 459 | 460 |
461 | 462 | Census Summary-HomeSize 463 | 464 | 465 | <1000sqft 466 | 0.3922527265889 467 | 468 | 469 | >3600sqft 470 | 0.0684467845054 471 | 472 | 473 | 1000-1400sqft 474 | 0.2651372696502 475 | 476 | 477 | 1400-1800sqft 478 | 0.1214742384355 479 | 480 | 481 | 1800-2400sqft 482 | 0.0699511094396 483 | 484 | 485 | 2400-3600sqft 486 | 0.0684467845054 487 | 488 | 489 |
490 | 491 | Census Summary-HomeType 492 | 493 | 494 | Condo 495 | 0.6398440269408 496 | 497 | 498 | Other 499 | 1.9343463444890998 500 | 501 | 502 | SingleFamily 503 | 0.1712158808933 504 | 505 | 506 |
507 | 508 | Census Summary-Occupancy 509 | 510 | 511 | Own 512 | 0.35028618 513 | 514 | 515 | Rent 516 | 0.64971382 517 | 518 | 519 |
520 |
521 |
522 | 523 | People 524 | 525 | 526 | People Data 527 | 528 | 529 | Median Household Income 530 | 531 | 532 | 41202.9453206937 533 | 534 | 535 | 45736 536 | 537 | 538 | 44512.0130806292 539 | 540 | 541 | 542 | 543 | Single Males 544 | 545 | 546 | 0.218182040689239 547 | 548 | 549 | 0.230033266826908 550 | 551 | 552 | 0.146462187349365 553 | 554 | 555 | 556 | 557 | Single Females 558 | 559 | 560 | 0.197726979090431 561 | 562 | 563 | 0.187486853578992 564 | 565 | 566 | 0.124578258618535 567 | 568 | 569 | 570 | 571 | Median Age 572 | 573 | 574 | 39 575 | 576 | 577 | 37 578 | 579 | 580 | 36 581 | 582 | 583 | 584 | 585 | Homes With Kids 586 | 587 | 588 | 0.149933859172205 589 | 590 | 591 | 0.181808339938523 592 | 593 | 594 | 0.313623902816284 595 | 596 | 597 | 598 | 599 | Average Household Size 600 | 601 | 602 | 1.82278897942217 603 | 604 | 605 | 2.08 606 | 607 | 608 | 2.58883240001203 609 | 610 | 611 | 612 | 613 | Average Commute Time (Minutes) 614 | 615 | 616 | 26.56776121676753 617 | 618 | 619 | 26.6363786935206 620 | 621 | 622 | 26.375545725891282 623 | 624 | 625 | 626 | 627 |
628 | 629 | Census Summary-AgeDecade 630 | 631 | 632 | >=70s 633 | 0.114872901061 634 | 635 | 636 | 0s 637 | 0.0698273234810158 638 | 639 | 640 | 10s 641 | 0.0614721332267584 642 | 643 | 644 | 20s 645 | 0.210411237406907 646 | 647 | 648 | 30s 649 | 0.222130722421361 650 | 651 | 652 | 40s 653 | 0.159760457231474 654 | 655 | 656 | 50s 657 | 0.100382039995932 658 | 659 | 660 | 60s 661 | 0.0611431851755522 662 | 663 | 664 |
665 | 666 | Census Summary-CommuteTime 667 | 668 | 669 | <10min 670 | 0.116523248268039 671 | 672 | 673 | >=60min 674 | 0.0482377198229543 675 | 676 | 677 | 10-20min 678 | 0.266281330068427 679 | 680 | 681 | 20-30min 682 | 0.255069379257092 683 | 684 | 685 | 30-45min 686 | 0.189151878627933 687 | 688 | 689 | 45-60min 690 | 0.124736443955555 691 | 692 | 693 |
694 | 695 | Census Summary-Household 696 | 697 | 698 | NoKids 699 | 0.850066140827795 700 | 701 | 702 | WithKids 703 | 0.149933859172205 704 | 705 | 706 |
707 | 708 | Census Summary-RelationshipStatus 709 | 710 | 711 | Divorced-Female 712 | 0.0854375513590899 713 | 714 | 715 | Divorced-Male 716 | 0.0602982799519792 717 | 718 | 719 | Married-Female 720 | 0.178297193386233 721 | 722 | 723 | Married-Male 724 | 0.186687382837076 725 | 726 | 727 | Single-Female 728 | 0.197726979090431 729 | 730 | 731 | Single-Male 732 | 0.218182040689239 733 | 734 | 735 | Widowed-Female 736 | 0.0632616593158969 737 | 738 | 739 | Widowed-Male 740 | 0.0101089133700551 741 | 742 | 743 |
744 |
745 | 746 | 747 | Makin' It Singles 748 | Upper-scale urban singles. 749 | Pre-middle-age to middle-age singles with upper-scale incomes. May or may not own their own home. Most have college educations and are employed in mid-management professions. 750 | 751 | 752 | Aspiring Urbanites 753 | Urban singles with moderate income. 754 | Low- to middle-income singles over a wide age range. Some have a college education. They work in a variety of occupations, including some management-level positions. 755 | 756 | 757 | Bright Lights, Big City 758 | Very mobile singles living in the city. 759 | Singles ranging in age from early 20s to mid-40s who have moved to an urban setting. Most rent their apartment or condo. Some have a college education and work in services and the professional sector. 760 | 761 | 762 | 763 | 764 | Bachelor's degrees 765 | 766 | 767 | Females working for non-profits 768 | Self-employed (unincorporated businesses) 769 | Work in arts, design, entertainment, sports, or media occupations 770 | Work in computer or mathematical occupations 771 | Work in office and administrative support occupations 772 | 773 | 774 | Born in the Midwest 775 | Born in the Northeast 776 | Born in the South 777 | Divorced females 778 | Single females 779 | Single males 780 | Widowed females 781 | 782 | 783 | Get to work by bicycle 784 | Get to work by bus 785 | 786 | 787 |
788 |
789 |
790 |
791 | 792 | --------------------------------------------------------------------------------