you_got_listed 0.2.0
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.
- 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
|