mls 0.14.0 → 1.0.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Rakefile +6 -6
  4. data/lib/mls.rb +73 -427
  5. data/lib/mls/account.rb +15 -0
  6. data/lib/mls/address.rb +4 -0
  7. data/lib/mls/agency.rb +6 -0
  8. data/lib/mls/brokerage.rb +7 -0
  9. data/lib/mls/coworking_space.rb +5 -0
  10. data/lib/mls/floorplan.rb +11 -0
  11. data/lib/mls/flyer.rb +11 -0
  12. data/lib/mls/lease.rb +2 -0
  13. data/lib/mls/listing.rb +135 -0
  14. data/lib/mls/locality.rb +6 -0
  15. data/lib/mls/photo.rb +18 -0
  16. data/lib/mls/property.rb +24 -0
  17. data/lib/mls/region.rb +13 -0
  18. data/lib/mls/sale.rb +2 -0
  19. data/lib/mls/space.rb +3 -0
  20. data/lib/mls/sublease.rb +2 -0
  21. data/lib/mls/use.rb +25 -0
  22. data/mls.gemspec +11 -15
  23. data/test/mls/attribute_test.rb +55 -0
  24. data/test/mls/resource_test.rb +31 -0
  25. data/test/mls_test.rb +27 -0
  26. data/test/test_helper.rb +26 -27
  27. metadata +43 -75
  28. data/lib/mls/attribute.rb +0 -44
  29. data/lib/mls/attributes/array.rb +0 -11
  30. data/lib/mls/attributes/boolean.rb +0 -9
  31. data/lib/mls/attributes/datetime.rb +0 -25
  32. data/lib/mls/attributes/decimal.rb +0 -21
  33. data/lib/mls/attributes/fixnum.rb +0 -16
  34. data/lib/mls/attributes/hash.rb +0 -15
  35. data/lib/mls/attributes/string.rb +0 -15
  36. data/lib/mls/errors.rb +0 -29
  37. data/lib/mls/factories/account.rb +0 -34
  38. data/lib/mls/factories/address.rb +0 -16
  39. data/lib/mls/factories/listing.rb +0 -29
  40. data/lib/mls/factories_helper.rb +0 -7
  41. data/lib/mls/model.rb +0 -125
  42. data/lib/mls/models/account.rb +0 -174
  43. data/lib/mls/models/address.rb +0 -58
  44. data/lib/mls/models/brokerage.rb +0 -31
  45. data/lib/mls/models/floorplan.rb +0 -45
  46. data/lib/mls/models/flyer.rb +0 -45
  47. data/lib/mls/models/listing.rb +0 -394
  48. data/lib/mls/models/pdf.rb +0 -15
  49. data/lib/mls/models/photo.rb +0 -40
  50. data/lib/mls/models/property.rb +0 -114
  51. data/lib/mls/models/region.rb +0 -56
  52. data/lib/mls/models/tour.rb +0 -54
  53. data/lib/mls/models/video.rb +0 -18
  54. data/lib/mls/parser.rb +0 -74
  55. data/lib/mls/resource.rb +0 -99
  56. data/lib/rdoc/generator/template/42floors/_context.rhtml +0 -209
  57. data/lib/rdoc/generator/template/42floors/_head.rhtml +0 -7
  58. data/lib/rdoc/generator/template/42floors/class.rhtml +0 -39
  59. data/lib/rdoc/generator/template/42floors/file.rhtml +0 -35
  60. data/lib/rdoc/generator/template/42floors/index.rhtml +0 -13
  61. data/lib/rdoc/generator/template/42floors/resources/apple-touch-icon.png +0 -0
  62. data/lib/rdoc/generator/template/42floors/resources/css/github.css +0 -129
  63. data/lib/rdoc/generator/template/42floors/resources/css/main.css +0 -339
  64. data/lib/rdoc/generator/template/42floors/resources/css/panel.css +0 -389
  65. data/lib/rdoc/generator/template/42floors/resources/css/reset.css +0 -48
  66. data/lib/rdoc/generator/template/42floors/resources/favicon.ico +0 -0
  67. data/lib/rdoc/generator/template/42floors/resources/i/arrows.png +0 -0
  68. data/lib/rdoc/generator/template/42floors/resources/i/results_bg.png +0 -0
  69. data/lib/rdoc/generator/template/42floors/resources/i/tree_bg.png +0 -0
  70. data/lib/rdoc/generator/template/42floors/resources/js/highlight.pack.js +0 -1
  71. data/lib/rdoc/generator/template/42floors/resources/js/jquery-1.3.2.min.js +0 -19
  72. data/lib/rdoc/generator/template/42floors/resources/js/jquery-effect.js +0 -593
  73. data/lib/rdoc/generator/template/42floors/resources/js/main.js +0 -20
  74. data/lib/rdoc/generator/template/42floors/resources/js/searchdoc.js +0 -442
  75. data/lib/rdoc/generator/template/42floors/resources/panel/index.html +0 -73
  76. data/lib/rdoc/generator/template/42floors/se_index.rhtml +0 -8
  77. data/test/units/properties/test_boolean.rb +0 -4
  78. data/test/units/test_errors.rb +0 -32
  79. data/test/units/test_mls.rb +0 -294
  80. data/test/units/test_property.rb +0 -35
@@ -1,58 +0,0 @@
1
- class MLS::Address < MLS::Resource
2
-
3
- attribute :id, Fixnum, :serialize => :false
4
- attribute :property_id, Fixnum, :serialize => :false
5
- attribute :slug, String, :serialize => :false
6
-
7
- attribute :formatted_address, String
8
- attribute :street_number, String
9
- attribute :street, String
10
- attribute :neighborhood, String
11
- attribute :city, String
12
- attribute :county, String
13
- attribute :state, String
14
- attribute :country, String
15
- attribute :postal_code, String
16
-
17
- attr_accessor :property
18
-
19
- def save
20
- MLS.put("/addresses/#{id}", {:address => to_hash}, 400) do |response, code|
21
- if code == 200 || code == 400
22
- MLS::Address::Parser.update(self, response.body)
23
- code == 200
24
- else
25
- raise MLS::Exception::UnexpectedResponse, code
26
- end
27
- end
28
- end
29
-
30
- def to_param
31
- slug
32
- end
33
-
34
- class << self
35
-
36
- def find(id)
37
- response = MLS.get("/addresses/#{id}")
38
- MLS::Address::Parser.parse(response.body)
39
- end
40
-
41
- # currently supported options are :include, :where, :limit, :offset
42
- def all(options={})
43
- response = MLS.get('/addresses', options)
44
- MLS::Address::Parser.parse_collection(response.body)
45
- end
46
-
47
- end
48
-
49
- end
50
-
51
-
52
- class MLS::Address::Parser < MLS::Parser
53
-
54
- def property=(property)
55
- @object.property = MLS::Property::Parser.build(property)
56
- end
57
-
58
- end
@@ -1,31 +0,0 @@
1
- class MLS::Brokerage < MLS::Resource
2
-
3
- attr_accessor :avatar
4
-
5
- attribute :id, Fixnum, :serialize => :if_present
6
- attribute :name, String, :serialize => :if_present
7
- attribute :admin_id, Fixnum, :serialize => :if_present
8
- attribute :slug, String, :serialize => false
9
- attribute :palette, Hash, :serialize => :if_present
10
-
11
- class << self
12
-
13
- def find(id)
14
- response = MLS.get("/brokerages/#{id}")
15
- MLS::Brokerage::Parser.parse(response.body)
16
- end
17
-
18
- def all(options={})
19
- response = MLS.get('/brokerages', options)
20
- MLS::Brokerage::Parser.parse_collection(response.body)
21
- end
22
-
23
- end
24
-
25
- end
26
-
27
- class MLS::Brokerage::Parser < MLS::Parser
28
- def avatar=(avatar)
29
- @object.avatar = MLS::Photo::Parser.build(avatar)
30
- end
31
- end
@@ -1,45 +0,0 @@
1
- require 'restclient'
2
-
3
- class MLS::Floorplan < MLS::Resource
4
-
5
- attribute :id, Fixnum
6
- attribute :digest, String
7
- attribute :avatar_digest, String
8
- attribute :file_name, String
9
- attribute :file_size, Fixnum
10
-
11
- def url(protocol='http')
12
- "#{protocol}://#{MLS.asset_host}/floorplans/#{digest}/compressed/#{file_name}"
13
- end
14
-
15
- def avatar(size='150x100#', protocol='http')
16
- "#{protocol}://#{MLS.image_host}/#{avatar_digest}.jpg?s=#{URI.escape(size)}"
17
- end
18
-
19
- def self.create(attrs)
20
- attrs[:file].rewind
21
- url = MLS.url.dup
22
- url.user = nil
23
- url.path = "/floorplans"
24
-
25
- if attrs[:subject]
26
- attrs[:subject_id] = attrs[:subject].id
27
- attrs[:subject_type] = attrs[:subject].class.name.split("::").last
28
- attrs.delete(:subject)
29
- end
30
- response = RestClient.post(url.to_s, {:floorplan => attrs}, MLS.headers)
31
- attrs[:file].close unless attrs[:file].closed?
32
-
33
- MLS::Floorplan::Parser.parse(response.body)
34
- end
35
-
36
- def self.find(id)
37
- response = MLS.get("/floorplans/#{id}")
38
- MLS::Floorplan::Parser.parse(response.body)
39
- end
40
-
41
- end
42
-
43
- class MLS::Floorplan::Parser < MLS::Parser
44
-
45
- end
@@ -1,45 +0,0 @@
1
- require 'restclient'
2
-
3
- class MLS::Flyer < MLS::Resource
4
-
5
- attribute :id, Fixnum
6
- attribute :digest, String
7
- attribute :avatar_digest, String
8
- attribute :file_name, String
9
- attribute :file_size, Fixnum
10
-
11
- def url(protocol='http')
12
- "#{protocol}://#{MLS.asset_host}/flyers/#{digest}/compressed/#{file_name}"
13
- end
14
-
15
- def avatar(size='150x100#', protocol='http')
16
- "#{protocol}://#{MLS.image_host}/#{avatar_digest}.jpg?s=#{URI.escape(size)}"
17
- end
18
-
19
- def self.create(attrs)
20
- attrs[:file].rewind
21
- url = MLS.url.dup
22
- url.user = nil
23
- url.path = "/flyers"
24
-
25
- if attrs[:subject]
26
- attrs[:subject_id] = attrs[:subject].id
27
- attrs[:subject_type] = attrs[:subject].class.name.split("::").last
28
- attrs.delete(:subject)
29
- end
30
- response = RestClient.post(url.to_s, {:flyer => attrs}, MLS.headers)
31
- attrs[:file].close unless attrs[:file].closed?
32
-
33
- MLS::Flyer::Parser.parse(response.body)
34
- end
35
-
36
- def self.find(id)
37
- response = MLS.get("/flyers/#{id}")
38
- MLS::Flyer::Parser.parse(response.body)
39
- end
40
-
41
- end
42
-
43
- class MLS::Flyer::Parser < MLS::Parser
44
-
45
- end
@@ -1,394 +0,0 @@
1
- class MLS::Listing < MLS::Resource
2
-
3
- WORKFLOW_STATES = %w(visible processing invisible expired)
4
- LEASE_STATES = %w(listed leased)
5
- TYPES = %w(lease sublease coworking_space)
6
- SPACE_TYPES = %w(unit floor building)
7
- LEASE_TERMS = ['Full Service', 'NNN', 'Modified Gross']
8
- RATE_UNITS = ['/sqft/yr', '/sqft/mo', '/mo', '/yr', '/desk/mo']
9
- USES = ["Office", "Creative", "Loft", "Medical Office", "Flex Space", "R&D", "Office Showroom", "Industrial", "Retail"]
10
- SOURCE_TYPES = %w(website flyer)
11
- CHANNELS = %w(excavator mls staircase broker_dashboard)
12
-
13
- attribute :id, Fixnum, :serialize => :false
14
- attribute :address_id, Fixnum, :serialize => :false
15
- attribute :slug, String, :serialize => :false
16
- attribute :use, String, :serialize => :if_present
17
- attribute :account_id, Fixnum
18
- attribute :private, Boolean, :default => false, :serialize => false
19
- attribute :source, String
20
- attribute :source_url, String
21
- attribute :source_type, String, :serialize => :if_present
22
- attribute :channel, String, :serialize => :if_present
23
- attribute :photo_ids, Array, :serialize => :if_present
24
-
25
- attribute :name, String
26
- attribute :type, String, :default => 'lease'
27
- attribute :workflow_state, String, :default => 'visible'
28
- attribute :lease_state, String, :default => 'listed'
29
- attribute :space_type, String, :default => 'unit'
30
- attribute :unit, String
31
- attribute :floor, Fixnum
32
- attribute :description, String
33
-
34
- attribute :size, Fixnum
35
- attribute :maximum_contiguous_size, Fixnum
36
- attribute :minimum_divisible_size, Fixnum
37
-
38
- attribute :amenities, Hash
39
- attribute :lease_terms, String
40
- attribute :rate, Decimal
41
- attribute :rate_units, String, :default => '/sqft/mo'
42
- attribute :low_rate, Decimal, :serialize => :false
43
- attribute :high_rate, Decimal, :serialize => :false
44
- attribute :sublease_expiration, DateTime
45
-
46
- attribute :forecast_rate_per_year, Decimal, :serialize => :false
47
- attribute :forecast_rate_per_month, Decimal, :serialize => :false
48
- attribute :forecast_rate_per_sqft_per_month, Decimal, :serialize => :false
49
- attribute :forecast_rate_per_sqft_per_year, Decimal, :serialize => :false
50
-
51
- attribute :available_on, DateTime
52
- attribute :term, Fixnum
53
- attribute :term_units, String, :default => 'years'
54
-
55
- attribute :weekday_hours, String
56
- attribute :saturday_hours, String
57
- attribute :sunday_hours, String
58
-
59
- attribute :offices, Fixnum
60
- attribute :desks, Fixnum
61
- attribute :conference_rooms, Fixnum
62
- attribute :bathrooms, Fixnum
63
-
64
- attribute :kitchen, Boolean
65
- attribute :showers, Boolean
66
- attribute :patio, Boolean
67
- attribute :reception_area, Boolean
68
- attribute :ready_to_move_in, Boolean
69
- attribute :furniture_available, Boolean
70
- attribute :natural_light, Boolean
71
- attribute :high_ceilings, Boolean
72
-
73
- attribute :created_at, DateTime, :serialize => :false
74
- attribute :updated_at, DateTime, :serialize => :false
75
- attribute :touched_at, DateTime, :serialize => :false
76
- attribute :leased_on, DateTime
77
- attribute :photography_requested_on, DateTime, :serialize => :false
78
-
79
- attribute :awesome_score, Fixnum
80
- attribute :awesome_needs, Array, :serialize => :if_present
81
- attribute :awesome_label, String
82
-
83
- attribute :flyer_id, Fixnum, :serialize => :if_present
84
- attribute :floorplan_id, Fixnum, :serialize => :if_present
85
-
86
- attribute :avatar_digest, String, :serialize => false
87
-
88
- # Counter Caches
89
- attribute :photos_count, Fixnum, :serialize => :false
90
-
91
- attr_accessor :property, :address, :agents, :account, :photos, :flyer, :floorplan, :videos, :similar_photos, :spaces, :primary_agent
92
-
93
- def avatar(size='150x100#', protocol='http')
94
- if avatar_digest
95
- "#{protocol}://#{MLS.image_host}/#{avatar_digest}.jpg?s=#{URI.escape(size)}"
96
- else
97
- address.avatar(size, protocol)
98
- end
99
- end
100
-
101
- def processing?
102
- workflow_state == 'processing'
103
- end
104
-
105
- def leased?
106
- lease_state == 'leased'
107
- end
108
-
109
- def active?
110
- lease_state == 'listed' && workflow_state == 'visible'
111
- end
112
-
113
- def inactive?
114
- !self.active?
115
- end
116
-
117
- def lease?
118
- type == 'lease'
119
- end
120
-
121
- def sublease?
122
- type == 'sublease'
123
- end
124
-
125
- def coworking?
126
- type == 'coworking_space'
127
- end
128
-
129
-
130
- def space_name
131
- return name if !name.nil?
132
-
133
- case space_type
134
- when 'unit'
135
- if unit
136
- "Unit #{unit}"
137
- elsif floor
138
- "#{floor.ordinalize} Floor"
139
- else
140
- "Unit Lease"
141
- end
142
- when 'building'
143
- "Entire Building"
144
- when 'floor'
145
- if floor
146
- "#{floor.ordinalize} Floor"
147
- elsif unit
148
- "Unit #{unit}"
149
- else
150
- "Floor Lease"
151
- end
152
- end
153
- end
154
-
155
- def rate?
156
- !!@rate
157
- end
158
-
159
- # TODO: remove /desk/mo conversions
160
- def rate(units=nil)
161
- return nil if !@rate
162
- units ||= rate_units
163
-
164
- price = if rate_units == '/sqft/mo'
165
- if units == '/sqft/mo'
166
- @rate
167
- elsif units == '/sqft/yr'
168
- @rate * 12.0
169
- elsif units == '/mo'
170
- @rate * @size
171
- elsif units == '/yr'
172
- @rate * @size * 12.0
173
- elsif units == '/desk/mo'
174
- @rate * 200.0
175
- else
176
- raise "Invalid rate conversion (#{rate_units} => #{units})"
177
- end
178
-
179
- elsif rate_units == '/sqft/yr'
180
- if units == '/sqft/mo'
181
- @rate / 12.0
182
- elsif units == '/sqft/yr'
183
- @rate
184
- elsif units == '/mo'
185
- (@rate * @size) / 12.0
186
- elsif units == '/yr'
187
- @rate * @size
188
- elsif units == '/desk/mo'
189
- (@rate / 12.0) * 200.0
190
- else
191
- raise "Invalid rate conversion (#{rate_units} => #{units})"
192
- end
193
-
194
- elsif rate_units == '/mo'
195
- if units == '/sqft/mo'
196
- @rate / @size.to_f
197
- elsif units == '/sqft/yr'
198
- (@rate * 12) / @size.to_f
199
- elsif units == '/mo'
200
- @rate
201
- elsif units == '/yr'
202
- @rate * 12
203
- elsif units == '/desk/mo'
204
- (@rate / @size.to_f) * 200.0
205
- else
206
- raise "Invalid rate conversion (#{rate_units} => #{units})"
207
- end
208
-
209
- elsif rate_units == '/yr'
210
- if units == '/sqft/mo'
211
- (@rate / 12.0) / @size.to_f
212
- elsif units == '/sqft/yr'
213
- @rate / @size.to_f
214
- elsif units == '/mo'
215
- @rate / 12.0
216
- elsif units == '/yr'
217
- @rate
218
- elsif units == '/desk/mo'
219
- ((@rate / 12.0) / @size.to_f) * 200.0
220
- else
221
- raise "Invalid rate conversion (#{rate_units} => #{units})"
222
- end
223
-
224
- elsif rate_units == '/desk/mo'
225
- if units == '/sqft/mo'
226
- @rate / 200.0
227
- elsif units == '/sqft/yr'
228
- (@rate * 12) / 200.0
229
- elsif units == '/mo'
230
- @rate
231
- elsif units == '/yr'
232
- @rate * 12
233
- elsif units == '/desk/mo'
234
- @rate
235
- else
236
- raise "Invalid rate conversion (#{rate_units} => #{units})"
237
- end
238
-
239
- end
240
-
241
- price.round(2)
242
- end
243
-
244
- # Creates a tour request for the listing.
245
- #
246
- # Paramaters::
247
- #
248
- # * +account+ - A +Hash+ of the user account. Valid keys are:
249
- # * +:name+ - Name of the User requesting the tour (Required)
250
- # * +:email+ - Email of the User requesting the tour (Required)
251
- # * +:phone+ - Phone of the User requesting the tour
252
- # * +info+ - A optional +Hash+ of *company* info. Valid keys are:
253
- # * +:message+ - Overrides the default message on the email sent to the broker
254
- # * +:company+ - The name of the company that is interested in the space
255
- # * +:population+ - The current number of employees at the company
256
- # * +:growing+ - A boolean of weather or not the company is expecting to grow
257
- #
258
- # Examples:
259
- #
260
- # #!ruby
261
- # listing = MLS::Listing.find(@id)
262
- # info => {:company => 'name', :population => 10, :funding => 'string', :move_id => '2012-09-12'}
263
- # listing.request_tour('name', 'email@address.com', info) # => #<MLS::Tour>
264
- #
265
- # listing.request_tour('', 'emai', info) # => #<MLS::Tour> will have errors on account
266
- def request_tour(account, tour={})
267
- MLS::Tour.create(id, account, tour)
268
- end
269
-
270
-
271
- def create
272
- MLS.post('/listings', {:listing => to_hash}, 201, 400) do |response, code|
273
- raise MLS::Exception::UnexpectedResponse if ![201, 400].include?(code)
274
- MLS::Listing::Parser.update(self, response.body)
275
- end
276
- end
277
-
278
- def save
279
- return create unless id
280
- MLS.put("/listings/#{id}", {:listing => to_hash}, 400) do |response, code|
281
- if code == 200 || code == 400
282
- MLS::Listing::Parser.update(self, response.body)
283
- code == 200
284
- else
285
- raise MLS::Exception::UnexpectedResponse, code
286
- end
287
- end
288
- end
289
-
290
- def to_hash
291
- hash = super
292
- hash[:address_attributes] = address.to_hash if address
293
- hash[:spaces_attributes] = spaces.inject([]) { |acc, x| acc << x.to_hash; acc } if spaces
294
- hash[:agents_attributes] = agents.inject([]) { |acc, x| acc << x.to_hash; acc } if agents
295
- hash[:photo_ids] = photos.map(&:id) if photos
296
- hash[:videos_attributes] = videos.map(&:to_hash) unless videos.blank?
297
- hash
298
- end
299
-
300
- def to_param
301
- "#{address.to_param}/#{id}"
302
- end
303
-
304
- def import #TODO test me
305
- result = :failure
306
- MLS.post('/import', {:listing => to_hash}, 400) do |response, code|
307
- case code
308
- when 200
309
- result = :duplicate
310
- when 201
311
- result = :created
312
- when 202
313
- result = :updated
314
- when 400
315
- result = :failure
316
- else
317
- raise MLS::Exception::UnexpectedResponse, code
318
- end
319
- MLS::Listing::Parser.update(self, response.body)
320
- end
321
- result
322
- end
323
-
324
- def similar
325
- [] # Similar Listings not supported for now
326
- end
327
-
328
- class << self
329
-
330
- def find(id)
331
- response = MLS.get("/listings/#{id}")
332
- MLS::Listing::Parser.parse(response.body)
333
- end
334
-
335
- # currently supported options are filters, page, per_page, offset, order
336
- def all(options={})
337
- response = MLS.get('/listings', options)
338
- MLS::Listing::Parser.parse_collection(response.body)
339
- end
340
-
341
- def import(attrs)
342
- model = self.new(attrs)
343
- {:status => model.import, :model => model}
344
- end
345
-
346
- def amenities
347
- @amenities ||= Yajl::Parser.new.parse(MLS.get('/listings/amenities').body).map(&:to_sym)
348
- end
349
-
350
- end
351
-
352
- end
353
-
354
-
355
- class MLS::Listing::Parser < MLS::Parser
356
-
357
- def photos=(photos)
358
- @object.photos = photos.map {|p| MLS::Photo::Parser.build(p)}
359
- end
360
-
361
- def similar_photos=(photos)
362
- @object.similar_photos = photos.map { |p| MLS::Photo::Parser.build(p) }
363
- end
364
-
365
- def videos=(videos)
366
- @object.videos = videos.map do |video|
367
- MLS::Video::Parser.build(video)
368
- end
369
- end
370
-
371
- def floorplan=(floorplan)
372
- @object.floorplan = MLS::Floorplan::Parser.build(floorplan)
373
- end
374
-
375
- def flyer=(flyer)
376
- @object.flyer = MLS::Flyer::Parser.build(flyer)
377
- end
378
-
379
- def address=(address)
380
- @object.address = MLS::Address::Parser.build(address)
381
- end
382
-
383
- def property=(property)
384
- @object.property = MLS::Property::Parser.build(property)
385
- end
386
-
387
- def agents=(agents)
388
- @object.agents = agents.map {|a| MLS::Account::Parser.build(a) }
389
- end
390
-
391
- def primary_agent=(agent)
392
- @object.primary_agent = MLS::Account::Parser.build(agent)
393
- end
394
- end