you_got_listed 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +25 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.rdoc +226 -0
- data/Rakefile +18 -0
- data/lib/you_got_listed.rb +17 -0
- data/lib/you_got_listed/accounts.rb +9 -0
- data/lib/you_got_listed/agent.rb +24 -0
- data/lib/you_got_listed/client.rb +25 -0
- data/lib/you_got_listed/complex.rb +42 -0
- data/lib/you_got_listed/complexes.rb +53 -0
- data/lib/you_got_listed/error.rb +17 -0
- data/lib/you_got_listed/lead.rb +13 -0
- data/lib/you_got_listed/listing.rb +73 -0
- data/lib/you_got_listed/listings.rb +110 -0
- data/lib/you_got_listed/resource.rb +19 -0
- data/lib/you_got_listed/response.rb +25 -0
- data/lib/you_got_listed/version.rb +3 -0
- data/spec/fixtures/responses/error.xml +2 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/complex_helper.rb +82 -0
- data/spec/support/listing_helper.rb +59 -0
- data/spec/support/webmock_helper.rb +26 -0
- data/spec/you_got_listed/accounts_spec.rb +26 -0
- data/spec/you_got_listed/agent_spec.rb +94 -0
- data/spec/you_got_listed/client_spec.rb +17 -0
- data/spec/you_got_listed/complex_spec.rb +26 -0
- data/spec/you_got_listed/complexes_spec.rb +80 -0
- data/spec/you_got_listed/error_spec.rb +10 -0
- data/spec/you_got_listed/lead_spec.rb +56 -0
- data/spec/you_got_listed/listing_spec.rb +110 -0
- data/spec/you_got_listed/listings_spec.rb +229 -0
- data/spec/you_got_listed/resource_spec.rb +17 -0
- data/spec/you_got_listed/response_spec.rb +119 -0
- data/spec/you_got_listed_api_key.yml.example +1 -0
- data/you_got_listed.gemspec +31 -0
- 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,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
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -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
|