you_got_listed 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.document +5 -0
  2. data/.gitignore +25 -0
  3. data/Gemfile +2 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +226 -0
  6. data/Rakefile +18 -0
  7. data/lib/you_got_listed.rb +17 -0
  8. data/lib/you_got_listed/accounts.rb +9 -0
  9. data/lib/you_got_listed/agent.rb +24 -0
  10. data/lib/you_got_listed/client.rb +25 -0
  11. data/lib/you_got_listed/complex.rb +42 -0
  12. data/lib/you_got_listed/complexes.rb +53 -0
  13. data/lib/you_got_listed/error.rb +17 -0
  14. data/lib/you_got_listed/lead.rb +13 -0
  15. data/lib/you_got_listed/listing.rb +73 -0
  16. data/lib/you_got_listed/listings.rb +110 -0
  17. data/lib/you_got_listed/resource.rb +19 -0
  18. data/lib/you_got_listed/response.rb +25 -0
  19. data/lib/you_got_listed/version.rb +3 -0
  20. data/spec/fixtures/responses/error.xml +2 -0
  21. data/spec/spec.opts +1 -0
  22. data/spec/spec_helper.rb +18 -0
  23. data/spec/support/complex_helper.rb +82 -0
  24. data/spec/support/listing_helper.rb +59 -0
  25. data/spec/support/webmock_helper.rb +26 -0
  26. data/spec/you_got_listed/accounts_spec.rb +26 -0
  27. data/spec/you_got_listed/agent_spec.rb +94 -0
  28. data/spec/you_got_listed/client_spec.rb +17 -0
  29. data/spec/you_got_listed/complex_spec.rb +26 -0
  30. data/spec/you_got_listed/complexes_spec.rb +80 -0
  31. data/spec/you_got_listed/error_spec.rb +10 -0
  32. data/spec/you_got_listed/lead_spec.rb +56 -0
  33. data/spec/you_got_listed/listing_spec.rb +110 -0
  34. data/spec/you_got_listed/listings_spec.rb +229 -0
  35. data/spec/you_got_listed/resource_spec.rb +17 -0
  36. data/spec/you_got_listed/response_spec.rb +119 -0
  37. data/spec/you_got_listed_api_key.yml.example +1 -0
  38. data/you_got_listed.gemspec +31 -0
  39. metadata +231 -0
@@ -0,0 +1,17 @@
1
+ module YouGotListed
2
+ class Error < Exception
3
+
4
+ attr_reader :code, :error
5
+
6
+ def initialize(code, error)
7
+ @code = code
8
+ @error = error
9
+ super(message)
10
+ end
11
+
12
+ def message
13
+ "YouGotListed Error: #{@error} (code: #{@code})"
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module YouGotListed
2
+ class Lead < Resource
3
+
4
+ def create(lead_params = {}, raise_error = false)
5
+ process_post("/leads/insert.php", lead_params, raise_error)
6
+ end
7
+
8
+ def create!(lead_params = {})
9
+ create(lead_params, true)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,73 @@
1
+ module YouGotListed
2
+ class Listing
3
+
4
+ attr_accessor :client
5
+
6
+ def initialize(listing, client)
7
+ listing.each do |key, value|
8
+ self.instance_variable_set('@'+key, value)
9
+ unless ["latitude", "longitude"].include?(key)
10
+ self.class.send(:define_method, key, proc{self.instance_variable_get("@#{key}")})
11
+ end
12
+ end
13
+ self.client = client
14
+ end
15
+
16
+ def similar_listings(limit = 6)
17
+ min_baths = ((self.baths.to_i - 1) <= 0 ? 0 : (self.baths.to_i - 1))
18
+ max_baths = self.baths.to_i + 1
19
+ min_rent = (self.price.to_i * 0.9).to_i
20
+ max_rent = (self.price.to_i * 1.1).to_i
21
+ search_params = {
22
+ :limit => limit + 1,
23
+ :min_rent => min_rent,
24
+ :max_rent => max_rent,
25
+ :min_bed => ((self.beds.to_i - 1) <= 0 ? 0 : (self.beds.to_i - 1)),
26
+ :max_bed => self.beds.to_i + 1,
27
+ :baths => [min_baths, self.baths, max_baths].join(','),
28
+ :city_neighborhood => self.city_neighborhood
29
+ }
30
+
31
+ similar = []
32
+ listings = YouGotListed::Listings.new(self.client)
33
+ listings.search(search_params).properties.each do |prop|
34
+ similar << prop unless prop.id == self.id
35
+ break if similar.size == limit
36
+ end
37
+ similar
38
+ end
39
+
40
+ def town_neighborhood
41
+ str = self.city
42
+ str += " - #{neighborhood}" unless self.neighborhood.blank?
43
+ str
44
+ end
45
+
46
+ def city_neighborhood
47
+ str = self.city
48
+ str += ":#{neighborhood}" unless self.neighborhood.blank?
49
+ str
50
+ end
51
+
52
+ def pictures
53
+ self.photos.photo unless self.photos.blank? || self.photos.photo.blank?
54
+ end
55
+
56
+ def main_picture
57
+ pictures.first if pictures
58
+ end
59
+
60
+ def mls_listing?
61
+ source && source == "MLS"
62
+ end
63
+
64
+ def latitude
65
+ @latitude.to_f unless @latitude.blank?
66
+ end
67
+
68
+ def longitude
69
+ @longitude.to_f unless @longitude.blank?
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,110 @@
1
+ module YouGotListed
2
+ class Listings < Resource
3
+
4
+ def search(params = {})
5
+ params[:page_count] ||= 20
6
+ params[:page_index] ||= 1
7
+ params[:sort_name] ||= "rent"
8
+ params[:sort_dir] ||= "asc"
9
+ params[:detail_level] ||= 2
10
+ SearchResponse.new(self.client.perform_request(:get, '/rentals/search.php', params), self.client, params[:page_count])
11
+ end
12
+
13
+ def featured(params = {}, featured_tag = 'Featured Rentals')
14
+ if params[:tags].blank?
15
+ params[:tags] = featured_tag
16
+ else
17
+ params[:tags] += (params[:tags].ends_with?(',') ? featured_tag : ",#{featured_tag}")
18
+ end
19
+ search(params)
20
+ end
21
+
22
+ def find_by_id(listing_id)
23
+ params = {:listing_id => listing_id, :detail_level => 2}
24
+ response = SearchResponse.new(self.client.perform_request(:get, '/rentals/search.php', params), self.client, 20)
25
+ (response.success? && response.properties.size > 0) ? response.properties.first : nil
26
+ end
27
+
28
+ def find_all(params = {})
29
+ params[:page_count] ||= 20
30
+ all_listings = []
31
+ response = search(params)
32
+ if response.success?
33
+ all_listings << response.properties
34
+ total_pages = (response.ygl_response.total.to_i/params[:page_count].to_f).ceil
35
+ if total_pages > 1
36
+ (2..total_pages).each do |page_num|
37
+ resp = search(params.merge(:page => page_num))
38
+ if resp.success?
39
+ all_listings << resp.properties
40
+ end
41
+ end
42
+ end
43
+ end
44
+ all_listings.flatten
45
+ end
46
+
47
+ def find_all_by_ids(listing_ids, include_off_market = true)
48
+ listing_ids = listing_ids.split(',') if listing_ids.is_a?(String)
49
+ mls_ids = []
50
+ ygl_ids = []
51
+ all_listings = []
52
+ listing_ids.each do |list_id|
53
+ if list_id =~ /[A-Z]{3}-[0-9]{3}-[0-9]{3}/
54
+ ygl_ids << list_id
55
+ else
56
+ mls_ids << list_id
57
+ end
58
+ end
59
+ ygl_ids.in_groups_of(500, false).each_with_index do |group, index|
60
+ search_params = {:listing_ids => group.join(','), :page_count => 500, :page_index => (index + 1)}
61
+ search_params[:include_off_market] = 1 if include_off_market
62
+ group.delete_if{|x| x.nil?}
63
+ all_listings << find_all({:listing_ids => group.join(','), :page_count => 500, :page_index => (index + 1)})
64
+ end
65
+ mls_ids.each do |mls_id|
66
+ all_listings << find_by_id(mls_id)
67
+ end
68
+ all_listings.compact.flatten
69
+ end
70
+
71
+ class SearchResponse < YouGotListed::Response
72
+
73
+ attr_accessor :limit, :paginator_cache, :client
74
+
75
+ def initialize(response, client, limit = 20, raise_error = false)
76
+ super(response, raise_error)
77
+ self.limit = limit
78
+ self.client = client
79
+ end
80
+
81
+ def properties
82
+ return [] if self.ygl_response.listings.blank?
83
+ props = []
84
+ if self.ygl_response.listings.listing.is_a?(Array)
85
+ self.ygl_response.listings.listing.each do |listing|
86
+ props << YouGotListed::Listing.new(listing, self.client)
87
+ end
88
+ else
89
+ props << YouGotListed::Listing.new(self.ygl_response.listings.listing, self.client)
90
+ end
91
+ props
92
+ end
93
+
94
+ def mls_results?
95
+ @has_mls_properties ||= properties.any?{|property| property.mls_listing?}
96
+ end
97
+
98
+ def paginator
99
+ paginator_cache if paginator_cache
100
+ self.paginator_cache = WillPaginate::Collection.create(
101
+ (self.ygl_response.page_index ? self.ygl_response.page_index : 1),
102
+ self.limit,
103
+ (self.ygl_response.total ? self.ygl_response.total : properties.size)) do |pager|
104
+ pager.replace properties
105
+ end
106
+ end
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,19 @@
1
+ module YouGotListed
2
+ class Resource
3
+
4
+ attr_accessor :client
5
+
6
+ def initialize(client)
7
+ self.client = client
8
+ end
9
+
10
+ def process_get(path, params = {}, raise_error = false)
11
+ Response.new(self.client.perform_request(:get, path, params), raise_error)
12
+ end
13
+
14
+ def process_post(path, params = {}, raise_error = false)
15
+ Response.new(self.client.perform_request(:post, path, params), raise_error)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module YouGotListed
2
+ class Response
3
+
4
+ attr_accessor :ygl_response
5
+
6
+ def initialize(response, raise_error = true)
7
+ rash = Hashie::Rash.new(response)
8
+ self.ygl_response = rash.ygl_response
9
+ raise Error.new(self.ygl_response.response_code, self.ygl_response.error) if !success? && raise_error
10
+ end
11
+
12
+ def success?
13
+ self.ygl_response.response_code.to_i < 300
14
+ end
15
+
16
+ def method_missing(method_name, *args)
17
+ if self.ygl_response.respond_to?(method_name)
18
+ self.ygl_response.send(method_name)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module YouGotListed
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" standalone="yes"?>
2
+ <YGLResponse responseCode="998"><Error>Invalid key.</Error></YGLResponse>
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'you_got_listed'
4
+ require 'rspec'
5
+ require 'webmock/rspec'
6
+ require 'vcr'
7
+
8
+ Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
13
+
14
+ VCR.config do |c|
15
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
16
+ c.stub_with :webmock
17
+ c.default_cassette_options = { :record => :new_episodes }
18
+ end
@@ -0,0 +1,82 @@
1
+ def valid_complex_rash
2
+ Hashie::Rash.new({
3
+ 'id' => 'CP-000-676',
4
+ 'name' => 'Church Park',
5
+ 'addresses' => Hashie::Rash.new({
6
+ 'address' => [
7
+ Hashie::Rash.new({
8
+ 'id' => '1051',
9
+ 'street_number' => '221',
10
+ 'street_name' => 'Massachusetts Ave.`',
11
+ 'city' => 'Boston',
12
+ 'state' => 'MA',
13
+ 'zip' => '02115',
14
+ 'neighborhood' => 'Back Bay',
15
+ 'latitude' => '42.344133',
16
+ 'longitude' => '-71.086222'
17
+ }),
18
+ Hashie::Rash.new({
19
+ 'id' => '1052',
20
+ 'street_number' => '255',
21
+ 'street_name' => 'Massachusetts Ave.`',
22
+ 'city' => 'Boston',
23
+ 'state' => 'MA',
24
+ 'zip' => '02115',
25
+ 'neighborhood' => 'Back Bay',
26
+ 'latitude' => '42.343736',
27
+ 'longitude' => '-71.086023'
28
+ }),
29
+ Hashie::Rash.new({
30
+ 'id' => '1053',
31
+ 'street_number' => '199',
32
+ 'street_name' => 'Massachusetts Ave.`',
33
+ 'city' => 'Boston',
34
+ 'state' => 'MA',
35
+ 'zip' => '02115',
36
+ 'neighborhood' => 'Back Bay',
37
+ 'latitude' => '42.344390',
38
+ 'longitude' => '-71.086350'
39
+ })
40
+ ]
41
+ }),
42
+ 'public_notes' => 'Our apartments mingle casual elegance with contemporary details. Gourmet kitchens with ceramic tile floors, granite counter tops, Gaggeneau, GE and Miele convection ovens and dishwashers, Sub Zero and GE refrigerators, Birchwood cabinets, and built-in pantries. Living areas with hardwood floors and crown molding, elegant baths, walk-in closets, private balconies and spectacular views make it home.',
43
+ 'photos' => Hashie::Rash.new({
44
+ 'photo' => [
45
+ 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F516795.jpg',
46
+ 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F516796.jpg',
47
+ 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F516797.jpg',
48
+ 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F516798.jpg',
49
+ 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F516799.jpg',
50
+ 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F516800.jpg'
51
+ ]
52
+ }),
53
+ 'listings' => Hashie::Rash.new({
54
+ 'listing' => [
55
+ Hashie::Rash.new({
56
+ 'listing_id' => 'BOS-009-741',
57
+ 'address_id' => '1051',
58
+ 'type' => 'R',
59
+ 'source' => 'F',
60
+ 'price' => '1423',
61
+ 'beds' => '1',
62
+ 'baths' => '1',
63
+ 'square_feet' => '',
64
+ 'unit_level' => '',
65
+ 'photo' => 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F65131.jpg'
66
+ }),
67
+ Hashie::Rash.new({
68
+ 'listing_id' => 'BOS-331-359',
69
+ 'address_id' => '1053',
70
+ 'type' => 'R',
71
+ 'source' => 'F',
72
+ 'price' => '1423',
73
+ 'beds' => '1',
74
+ 'baths' => '1',
75
+ 'square_feet' => '',
76
+ 'unit_level' => '',
77
+ 'photo' => 'http://ygl-photos.s3.amazonaws.com/AC-000-010%2F65130.jpg'
78
+ })
79
+ ]
80
+ })
81
+ })
82
+ end
@@ -0,0 +1,59 @@
1
+ def valid_listing_rash
2
+ Hashie::Rash.new({
3
+ "unit_level" => nil,
4
+ "pet" => "Unknown",
5
+ "include_hot_water" => "1",
6
+ "square_footage" => nil,
7
+ "external_id" => nil,
8
+ "zip" => "02215",
9
+ "student_policy" => "Student Ok",
10
+ "building_id" => "1278",
11
+ "parking" => "Available",
12
+ "split" => "0",
13
+ "city" => "Boston",
14
+ "neighborhood" => "Fenway",
15
+ "fee" => "None",
16
+ "heat_source" => nil,
17
+ "street_name" => "Park Dr.",
18
+ "price" => "800",
19
+ "building_type" => "Apartment Complex",
20
+ "include_heat" => "1",
21
+ "beds" => "0.5",
22
+ "id" => "BOS-182-933",
23
+ "agency_id" => "AC-000-010",
24
+ "status" => "ONMARKET",
25
+ "source" => "YGL",
26
+ "longitude" => "-71.104427",
27
+ "available_date" => "01/01/2011",
28
+ "include_electricity" => "0",
29
+ "latitude" => "42.346182",
30
+ "street_number" => "463",
31
+ "unit" => "17",
32
+ "include_gas" => "0",
33
+ "photos" => Hashie::Rash.new({
34
+ "photo" => [
35
+ "http://yougotlistings.com/photos/AC-000-103/500141.jpg",
36
+ "http://yougotlistings.com/photos/AC-000-103/500143.jpg",
37
+ "http://yougotlistings.com/photos/AC-000-103/500144.jpg",
38
+ "http://yougotlistings.com/photos/AC-000-103/500145.jpg",
39
+ "http://yougotlistings.com/photos/AC-000-103/500146.jpg",
40
+ "http://yougotlistings.com/photos/AC-000-103/500147.jpg",
41
+ "http://yougotlistings.com/photos/AC-000-103/500148.jpg",
42
+ "http://yougotlistings.com/photos/AC-000-103/500149.jpg",
43
+ "http://yougotlistings.com/photos/AC-000-103/500150.jpg",
44
+ "http://yougotlistings.com/photos/AC-000-103/500151.jpg",
45
+ "http://yougotlistings.com/photos/AC-000-103/500156.jpg",
46
+ "http://yougotlistings.com/photos/AC-000-103/500160.jpg",
47
+ "http://yougotlistings.com/photos/AC-000-103/500161.jpg",
48
+ "http://yougotlistings.com/photos/AC-000-103/500162.jpg",
49
+ "http://yougotlistings.com/photos/AC-000-103/500164.jpg",
50
+ "http://yougotlistings.com/photos/AC-000-103/500166.jpg",
51
+ "http://yougotlistings.com/photos/AC-000-103/500167.jpg",
52
+ "http://yougotlistings.com/photos/AC-000-103/500168.jpg",
53
+ "http://yougotlistings.com/photos/AC-000-103/140824.jpg"
54
+ ]
55
+ }),
56
+ "state" => "MA",
57
+ "baths" => "1"
58
+ })
59
+ end
@@ -0,0 +1,26 @@
1
+ YGL_API_KEY = YAML.load_file(File.join(File.dirname(__FILE__), '/../you_got_listed_api_key.yml'))["api_key"]
2
+
3
+ def new_ygl
4
+ YouGotListed::Client.new(YGL_API_KEY)
5
+ end
6
+
7
+ def mock_get(base_uri, method, response_fixture, params = {})
8
+ url = base_uri + method
9
+ unless params.blank?
10
+ stub_http_request(:get, url).with(:query => params).to_return(:body => mocked_response(response_fixture))
11
+ else
12
+ stub_http_request(:get, url).to_return(:body => mocked_response(response_fixture))
13
+ end
14
+ end
15
+
16
+ def mocked_response(response_fixture)
17
+ File.read(File.join(File.dirname(__FILE__), '/../fixtures/responses', "#{response_fixture}"))
18
+ end
19
+
20
+ def httparty_get(base_uri, method, response_fixture, params = {})
21
+ mock_get(base_uri, method, response_fixture, params)
22
+ url = base_uri + method
23
+ VCR.use_cassette(method.gsub('/', '_')) do
24
+ HTTParty.get url, :format => :xml, :query => params
25
+ end
26
+ end