rubillow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +4 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +43 -0
  6. data/LICENSE +21 -0
  7. data/README.md +64 -0
  8. data/Rakefile +38 -0
  9. data/lib/rubillow.rb +29 -0
  10. data/lib/rubillow/configuration.rb +31 -0
  11. data/lib/rubillow/home_valuation.rb +141 -0
  12. data/lib/rubillow/models.rb +83 -0
  13. data/lib/rubillow/models/addressable.rb +38 -0
  14. data/lib/rubillow/models/chart.rb +34 -0
  15. data/lib/rubillow/models/comps.rb +35 -0
  16. data/lib/rubillow/models/deep_comps.rb +35 -0
  17. data/lib/rubillow/models/deep_search_result.rb +44 -0
  18. data/lib/rubillow/models/demographic_value.rb +28 -0
  19. data/lib/rubillow/models/demographics.rb +149 -0
  20. data/lib/rubillow/models/images.rb +24 -0
  21. data/lib/rubillow/models/linkable.rb +39 -0
  22. data/lib/rubillow/models/monthly_payments.rb +64 -0
  23. data/lib/rubillow/models/posting.rb +37 -0
  24. data/lib/rubillow/models/postings.rb +64 -0
  25. data/lib/rubillow/models/property_basics.rb +36 -0
  26. data/lib/rubillow/models/property_chart.rb +26 -0
  27. data/lib/rubillow/models/rate_summary.rb +38 -0
  28. data/lib/rubillow/models/region.rb +44 -0
  29. data/lib/rubillow/models/region_chart.rb +30 -0
  30. data/lib/rubillow/models/region_children.rb +30 -0
  31. data/lib/rubillow/models/search_result.rb +19 -0
  32. data/lib/rubillow/models/updated_property_details.rb +87 -0
  33. data/lib/rubillow/models/zestimateable.rb +77 -0
  34. data/lib/rubillow/models/zpidable.rb +25 -0
  35. data/lib/rubillow/mortgage.rb +75 -0
  36. data/lib/rubillow/neighborhood.rb +129 -0
  37. data/lib/rubillow/postings.rb +46 -0
  38. data/lib/rubillow/property_details.rb +107 -0
  39. data/lib/rubillow/request.rb +67 -0
  40. data/lib/rubillow/version.rb +4 -0
  41. data/rubillow.gemspec +31 -0
  42. data/spec/rubillow/configuration_spec.rb +10 -0
  43. data/spec/rubillow/home_valuation_spec.rb +91 -0
  44. data/spec/rubillow/models/comps_spec.rb +71 -0
  45. data/spec/rubillow/models/deep_comps_spec.rb +93 -0
  46. data/spec/rubillow/models/deep_search_results_spec.rb +47 -0
  47. data/spec/rubillow/models/demographics_spec.rb +60 -0
  48. data/spec/rubillow/models/monthly_payments_spec.rb +20 -0
  49. data/spec/rubillow/models/posting_spec.rb +28 -0
  50. data/spec/rubillow/models/postings_spec.rb +15 -0
  51. data/spec/rubillow/models/property_chart_spec.rb +18 -0
  52. data/spec/rubillow/models/rate_summary_spec.rb +14 -0
  53. data/spec/rubillow/models/region_chart_spec.rb +19 -0
  54. data/spec/rubillow/models/region_children_spec.rb +11 -0
  55. data/spec/rubillow/models/search_result_spec.rb +68 -0
  56. data/spec/rubillow/models/updated_property_details_spec.rb +57 -0
  57. data/spec/rubillow/models_spec.rb +46 -0
  58. data/spec/rubillow/mortgage_spec.rb +37 -0
  59. data/spec/rubillow/neighborhood_spec.rb +55 -0
  60. data/spec/rubillow/postings_spec.rb +19 -0
  61. data/spec/rubillow/property_details_spec.rb +67 -0
  62. data/spec/rubillow/request_spec.rb +65 -0
  63. data/spec/rubillow/rubillow_spec.rb +13 -0
  64. data/spec/spec_helper.rb +15 -0
  65. data/spec/support/have_configuration_matcher.rb +23 -0
  66. data/spec/xml/general_failure.xml +12 -0
  67. data/spec/xml/get_chart.xml +18 -0
  68. data/spec/xml/get_comps.xml +252 -0
  69. data/spec/xml/get_deep_comps.xml +307 -0
  70. data/spec/xml/get_deep_search_results.xml +66 -0
  71. data/spec/xml/get_demographics.xml +791 -0
  72. data/spec/xml/get_monthly_payments.xml +33 -0
  73. data/spec/xml/get_rate_summary.xml +21 -0
  74. data/spec/xml/get_region_chart.xml +25 -0
  75. data/spec/xml/get_region_children.xml +870 -0
  76. data/spec/xml/get_region_postings.xml +2660 -0
  77. data/spec/xml/get_search_results.xml +55 -0
  78. data/spec/xml/get_updated_property_details.xml +74 -0
  79. data/spec/xml/get_zestimate.xml +56 -0
  80. data/spec/xml/near_limit.xml +13 -0
  81. metadata +324 -0
@@ -0,0 +1,25 @@
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).text
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
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
@@ -0,0 +1,129 @@
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
@@ -0,0 +1,46 @@
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
@@ -0,0 +1,107 @@
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
+ # @return [Models::DeepSearchResult] Extended property details.
23
+ def self.deep_search_results(options = {})
24
+ options = {
25
+ :zws_id => Rubillow.configuration.zwsid,
26
+ :address => nil,
27
+ :citystatezip => nil,
28
+ }.merge!(options)
29
+
30
+ if options[:address].nil?
31
+ raise ArgumentError, "The address option is required"
32
+ end
33
+ if options[:citystatezip].nil?
34
+ raise ArgumentError, "The citystatezip option is required"
35
+ end
36
+
37
+ Models::DeepSearchResult.new(Rubillow::Request.get("GetDeepSearchResults", options))
38
+ end
39
+
40
+ # Retrieve extended details for property and its comps.
41
+ #
42
+ # Read more at: {http://www.zillow.com/howto/api/GetDeepComps.htm}.
43
+ #
44
+ # @example
45
+ # data = Rubillow::PropertyDetails.deep_comps({ :zpid => '48749425', :count => 5 })
46
+ #
47
+ # if data.success?
48
+ # puts data.principal.price # "1032000"
49
+ # data.comparables.each |comp|
50
+ # puts comp.price
51
+ # puts comp.address[:street]
52
+ # end
53
+ # end
54
+ #
55
+ # @param [Hash] options The options for the API request.
56
+ # @option options [Integer] :zpid The Zillow Property ID of the property. (required)
57
+ # @option options [Integer] :count The number of comps to return, between 1 and 25 inclusive. (required)
58
+ # @return [Models::DeepComps] Extended property and comp details.
59
+ def self.deep_comps(options = {})
60
+ options = {
61
+ :zws_id => Rubillow.configuration.zwsid,
62
+ :zpid => nil,
63
+ :count => nil,
64
+ }.merge!(options)
65
+
66
+ if options[:zpid].nil?
67
+ raise ArgumentError, "The zpid option is required"
68
+ end
69
+ if options[:count].nil?
70
+ raise ArgumentError, "The count option is required"
71
+ end
72
+
73
+ Models::DeepComps.new(Rubillow::Request.get("GetDeepComps", options))
74
+ end
75
+
76
+ # Retrieve updated property facts for a given property.
77
+ #
78
+ # Read more at: {http://www.zillow.com/howto/api/GetUpdatedPropertyDetails.htm}.
79
+ #
80
+ # @example
81
+ # data = Rubillow::PropertyDetails.updated_property_details({ :zpid => '48749425' })
82
+ #
83
+ # if data.success?
84
+ # puts data.posting[:status] # "1032000"
85
+ # puts data.posting[:type] # "For sale by agent"
86
+ # data.edited_facts.each |fact|
87
+ # puts fact
88
+ # end
89
+ # end
90
+ #
91
+ # @param [Hash] options The options for the API request.
92
+ # @option options [Integer] :zpid The Zillow Property ID of the property. (required)
93
+ # @return [Models::UpdatedPropertyDetails] Updated property information.
94
+ def self.updated_property_details(options = {})
95
+ options = {
96
+ :zws_id => Rubillow.configuration.zwsid,
97
+ :zpid => nil,
98
+ }.merge!(options)
99
+
100
+ if options[:zpid].nil?
101
+ raise ArgumentError, "The zpid option is required"
102
+ end
103
+
104
+ Models::UpdatedPropertyDetails.new(Rubillow::Request.get("GetUpdatePropertyDetails", options))
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,67 @@
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