mls 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +1 -0
  2. data/README.rdoc +54 -0
  3. data/Rakefile +20 -2
  4. data/lib/mls.rb +307 -78
  5. data/lib/mls/errors.rb +13 -4
  6. data/lib/mls/model.rb +36 -5
  7. data/lib/mls/models/account.rb +74 -66
  8. data/lib/mls/models/address.rb +87 -6
  9. data/lib/mls/models/area.rb +27 -0
  10. data/lib/mls/models/flyer.rb +41 -0
  11. data/lib/mls/models/listing.rb +180 -34
  12. data/lib/mls/models/photo.rb +25 -3
  13. data/lib/mls/models/tour_request.rb +18 -31
  14. data/lib/mls/parser.rb +5 -4
  15. data/lib/mls/properties/datetime.rb +5 -1
  16. data/lib/mls/properties/decimal.rb +3 -1
  17. data/lib/mls/properties/hash.rb +7 -0
  18. data/lib/mls/resource.rb +35 -5
  19. data/lib/rdoc/generator/template/42floors/_context.rhtml +209 -0
  20. data/lib/rdoc/generator/template/42floors/_head.rhtml +7 -0
  21. data/lib/rdoc/generator/template/42floors/class.rhtml +39 -0
  22. data/lib/rdoc/generator/template/42floors/file.rhtml +35 -0
  23. data/lib/rdoc/generator/template/42floors/index.rhtml +13 -0
  24. data/lib/rdoc/generator/template/42floors/resources/apple-touch-icon.png +0 -0
  25. data/lib/rdoc/generator/template/42floors/resources/css/github.css +129 -0
  26. data/lib/rdoc/generator/template/42floors/resources/css/main.css +339 -0
  27. data/lib/rdoc/generator/template/42floors/resources/css/panel.css +389 -0
  28. data/lib/rdoc/generator/template/42floors/resources/css/reset.css +48 -0
  29. data/lib/rdoc/generator/template/42floors/resources/favicon.ico +0 -0
  30. data/lib/rdoc/generator/template/42floors/resources/i/arrows.png +0 -0
  31. data/lib/rdoc/generator/template/42floors/resources/i/results_bg.png +0 -0
  32. data/lib/rdoc/generator/template/42floors/resources/i/tree_bg.png +0 -0
  33. data/lib/rdoc/generator/template/42floors/resources/js/highlight.pack.js +1 -0
  34. data/lib/rdoc/generator/template/42floors/resources/js/jquery-1.3.2.min.js +19 -0
  35. data/lib/rdoc/generator/template/42floors/resources/js/jquery-effect.js +593 -0
  36. data/lib/rdoc/generator/template/42floors/resources/js/main.js +20 -0
  37. data/lib/rdoc/generator/template/42floors/resources/js/searchdoc.js +442 -0
  38. data/lib/rdoc/generator/template/42floors/resources/panel/index.html +73 -0
  39. data/lib/rdoc/generator/template/42floors/se_index.rhtml +8 -0
  40. data/mls.gemspec +7 -4
  41. data/test/factories/account.rb +18 -0
  42. data/test/factories/address.rb +15 -0
  43. data/test/factories/listing.rb +30 -0
  44. data/test/factories/tour_request.rb +9 -0
  45. data/test/fixtures/flyer.pdf +68 -0
  46. data/test/test_helper.rb +44 -5
  47. data/test/units/models/test_account.rb +20 -43
  48. data/test/units/models/test_flyer.rb +22 -0
  49. data/test/units/models/test_listing.rb +119 -0
  50. data/test/units/models/test_photo.rb +136 -3
  51. data/test/units/models/test_tour_request.rb +25 -20
  52. data/test/units/test_errors.rb +12 -4
  53. data/test/units/test_mls.rb +263 -3
  54. data/test/units/test_resource.rb +1 -0
  55. metadata +78 -57
  56. data/lib/mls/models/user.rb +0 -7
  57. data/lib/mls/version.rb +0 -3
@@ -0,0 +1,41 @@
1
+ require 'restclient'
2
+
3
+ class MLS::Flyer < MLS::Resource
4
+
5
+ property :id, Fixnum
6
+ property :subject_id, Fixnum
7
+ property :subject_type, String
8
+ property :created_at, DateTime
9
+ property :updated_at, DateTime
10
+ property :file_content_type, String
11
+ property :file_name, String
12
+ property :file_size, Fixnum
13
+ property :url, String
14
+
15
+ def self.create(attrs)
16
+ attrs[:file].rewind
17
+ url = MLS.url.dup
18
+ url.user = nil
19
+ url.path = "/api/flyers"
20
+
21
+ if attrs[:subject]
22
+ attrs[:subject_id] = attrs[:subject].id
23
+ attrs[:subject_type] = attrs[:subject].class.name.split("::").last
24
+ attrs.delete(:subject)
25
+ end
26
+ response = RestClient.post(url.to_s, {:flyer => attrs}, MLS.headers)
27
+ attrs[:file].close unless attrs[:file].closed?
28
+
29
+ MLS::Flyer::Parser.parse(response.body)
30
+ end
31
+
32
+ def self.find(id)
33
+ response = MLS.get("/flyers/#{id}")
34
+ MLS::Flyer::Parser.parse(response.body)
35
+ end
36
+
37
+ end
38
+
39
+ class MLS::Flyer::Parser < MLS::Parser
40
+
41
+ end
@@ -6,16 +6,19 @@ class MLS::Listing < MLS::Resource
6
6
  RATE_UNITS = ['ft^2/year', 'ft^2/month', 'desk/month']
7
7
  USES = ["Office", "Creative", "Loft", "Medical Office", "Flex Space", "R&D", "Office Showroom", "Industrial", "Retail"]
8
8
 
9
- property :id, Fixnum
10
- property :address_id, Fixnum
9
+ property :id, Fixnum, :serialize => :false
10
+ property :address_id, Fixnum, :serialize => :false
11
11
  property :use_id, Fixnum
12
- property :use, String
12
+ property :use, String, :serialize => :if_present
13
13
  property :account_id, Fixnum
14
- property :hidden, Boolean, :default => false
14
+ property :hidden, Boolean, :default => false
15
+ property :source, String
16
+ property :source_url, String
17
+ property :flyer_url, String, :serialize => false
15
18
 
16
19
  property :name, String
17
- property :kind, String, :default => 'lease'
18
- property :space_type, String, :default => 'unit'
20
+ property :kind, String, :default => 'lease'
21
+ property :space_type, String, :default => 'unit'
19
22
  property :unit, String
20
23
  property :floor, Fixnum
21
24
  property :comments, String
@@ -26,10 +29,12 @@ class MLS::Listing < MLS::Resource
26
29
 
27
30
  property :lease_type, String
28
31
  property :rate, Decimal
29
- property :rate_units, String, :default => 'ft^2/month'
30
- property :rate_per_month, Decimal
31
- property :rate_per_year, Decimal
32
- property :tenant_improvements, String
32
+ property :rate_units, String, :default => 'ft^2/month'
33
+ property :rate_per_month, Decimal, :serialize => :false # need to make write methods for these that set rate to the according rate units. not accepted on api
34
+ property :rate_per_year, Decimal, :serialize => :false
35
+ property :total_rate_per_month, Decimal, :serialize => :false
36
+ property :total_rate_per_year, Decimal, :serialize => :false
37
+ property :tenant_improvements, String, :serialize => :if_present
33
38
  property :nnn_expenses, Decimal
34
39
  property :sublease_expiration, DateTime
35
40
 
@@ -42,48 +47,189 @@ class MLS::Listing < MLS::Resource
42
47
  property :bathrooms, Fixnum
43
48
  property :desks, Fixnum
44
49
 
45
- property :kitchen, Boolean, :default => false
46
- property :showers, Boolean, :default => false
47
- property :bike_rack, Boolean, :default => false
48
- property :bikes_allowed, Boolean, :default => false
49
- property :server_room, Boolean, :default => false
50
- property :reception_area, Boolean, :default => false
51
- property :turnkey, Boolean, :default => false
52
- property :patio, Boolean, :default => false
53
- property :copy_room, Boolean, :default => false
54
- property :dog_friendly, Boolean, :default => false
55
- property :cabling, Boolean, :default => false
56
- property :ready_to_move_in, Boolean, :default => false
57
- property :recent_space_improvements, Boolean, :default => false
58
- property :printers, Boolean, :default => false
59
- property :furniture_available, Boolean, :default => false
50
+ property :kitchen, Boolean, :default => false
51
+ property :showers, Boolean, :default => false
52
+ property :bike_rack, Boolean, :default => false
53
+ property :bikes_allowed, Boolean, :default => false
54
+ property :server_room, Boolean, :default => false
55
+ property :reception_area, Boolean, :default => false
56
+ property :turnkey, Boolean, :default => false
57
+ property :patio, Boolean, :default => false
58
+ property :copy_room, Boolean, :default => false
59
+ property :dog_friendly, Boolean, :default => false
60
+ property :cabling, Boolean, :default => false
61
+ property :ready_to_move_in, Boolean, :default => false
62
+ property :recent_space_improvements, Boolean, :default => false
63
+ property :printers, Boolean, :default => false
64
+ property :furniture_available, Boolean, :default => false
65
+
66
+ property :kitchenette, Boolean, :default => false
67
+ property :natural_light, Boolean, :default => false
68
+ property :high_ceilings, Boolean, :default => false
69
+
70
+ property :shared_kitchen, Boolean, :default => false
71
+ property :shared_bike_storage, Boolean, :default => false
72
+ property :parking_available, Boolean, :default => false
73
+ property :shared_bathrooms, Boolean, :default => false
74
+ property :shared_showers, Boolean, :default => false
75
+
60
76
 
61
- property :created_at, DateTime
62
- property :updated_at, DateTime
77
+ property :created_at, DateTime, :serialize => :false
78
+ property :updated_at, DateTime, :serialize => :false
79
+ property :leased_on, DateTime
80
+
81
+ property :avatar_digest, String, :serialize => false
82
+ attr_accessor :address, :agents, :account, :photos#, :address_attributes, :agents_attributes, :photo_ids
83
+
84
+ def avatar(size='150x100', protocol='http')
85
+ if avatar_digest
86
+ "#{protocol}://#{MLS.asset_host}/photos/#{size}/#{avatar_digest}.jpg"
87
+ else
88
+ address.avatar(size, protocol)
89
+ end
90
+ end
91
+
92
+ def sublease?
93
+ kind == 'sublease'
94
+ end
95
+
96
+ def leased?
97
+ !leased_on.nil?
98
+ end
63
99
 
100
+ def space_name
101
+ return name if !name.nil?
102
+
103
+ case space_type
104
+ when 'unit'
105
+ "Unit #{unit || 'Lease'}"
106
+ when 'building'
107
+ "Entire Building"
108
+ when 'floor'
109
+ "Floor #{floor || 'Lease'}"
110
+ end
111
+ end
112
+
64
113
 
65
- attr_accessor :address, :agents
66
114
 
67
- def photos
68
- []
115
+ # Creates a tour request for the listing.
116
+ #
117
+ # Paramaters::
118
+ #
119
+ # * +account+ - A +Hash+ of the user account. Valid keys are:
120
+ # * +:name+ - Name of the User requesting the tour (Required)
121
+ # * +:email+ - Email of the User requesting the tour (Required)
122
+ # * +:phone+ - Phone of the User requesting the tour
123
+ # * +info+ - A optional +Hash+ of *company* info. Valid keys are:
124
+ # * +:message+ - Overrides the default message on the email sent to the broker
125
+ # * +:company+ - The name of the company that is interested in the space
126
+ # * +:population+ - The current number of employees at the company
127
+ # * +:growing+ - A boolean of weather or not the company is expecting to grow
128
+ #
129
+ # Examples:
130
+ #
131
+ # #!ruby
132
+ # listing = MLS::Listing.find(@id)
133
+ # info => {:company => 'name', :population => 10, :funding => 'string', :move_id => '2012-09-12'}
134
+ # listing.request_tour('name', 'email@address.com', info) # => #<MLS::TourRequest>
135
+ #
136
+ # listing.request_tour('', 'emai', info) # => #<MLS::TourRequest> will have errors on account
137
+ def request_tour(account, tour={})
138
+ params = {:account => account, :tour => tour}
139
+ MLS.post("/listings/#{id}/tour_requests", params, 400) do |response, code|
140
+ return MLS::TourRequest::Parser.parse(response.body)
141
+ end
69
142
  end
70
143
 
144
+
145
+ def create
146
+ MLS.post('/listings', {:listing => to_hash}, 201, 400) do |response, code|
147
+ raise MLS::Exception::UnexpectedResponse if ![201, 400].include?(code)
148
+ MLS::Listing::Parser.update(self, response.body)
149
+ end
150
+ end
151
+
152
+ def save
153
+ return create unless id
154
+ MLS.put("/listings/#{id}", {:listing => to_hash}, 400) do |response, code|
155
+ if code == 200 || code == 400
156
+ MLS::Listing::Parser.update(self, response.body)
157
+ code == 200
158
+ else
159
+ raise MLS::Exception::UnexpectedResponse, code
160
+ end
161
+ end
162
+ end
163
+
164
+ def to_hash
165
+ hash = super
166
+ hash[:address_attributes] = address.to_hash if address
167
+ hash[:agents_attributes] = agents.inject({}) { |acc, x| acc[acc.length] = x.to_hash; acc } if agents
168
+ hash[:photo_ids] = photos.map(&:id) if photos
169
+ hash
170
+ end
171
+
172
+ def to_param
173
+ [address.state, address.city, address.name, id.to_s].map(&:parameterize).join('/')
174
+ end
175
+
176
+ def import #TODO test me
177
+ result = :failure
178
+ MLS.post('/import', {:listing => to_hash}, 400) do |response, code|
179
+ case code
180
+ when 200
181
+ result = :duplicate
182
+ when 201
183
+ result = :created
184
+ when 202
185
+ result = :updated
186
+ when 400
187
+ result = :failure
188
+ else
189
+ raise MLS::Exception::UnexpectedResponse, code
190
+ end
191
+ MLS::Listing::Parser.update(self, response.body)
192
+ end
193
+ result
194
+ end
195
+
196
+ def url
197
+ "#{address.url}/#{id}"
198
+ end
199
+
71
200
  class << self
72
-
201
+
73
202
  def find(id)
74
203
  response = MLS.get("/listings/#{id}")
75
204
  MLS::Listing::Parser.parse(response.body)
76
205
  end
77
206
 
207
+ def all(filters = {}, limit = nil, order = nil)
208
+ response = MLS.get('/listings', :filters => filters, :limit => limit, :order => order)
209
+ MLS::Listing::Parser.parse_collection(response.body)
210
+ end
211
+
212
+ def import(attrs)
213
+ model = self.new(attrs)
214
+ {:status => model.import, :model => model}
215
+ end
216
+
217
+ def calculate(filters = {}, operation = nil, column = nil, group = nil)
218
+ response = MLS.get("/listings/calculate", :filters => filters, :operation => operation, :column => column, :group => group)
219
+ MLS::Parser.extract_attributes(response.body)[:listings]
220
+ end
221
+
78
222
  end
79
-
223
+
80
224
  end
81
225
 
82
226
 
83
227
  class MLS::Listing::Parser < MLS::Parser
84
228
 
85
229
  def photos=(photos)
86
- puts photos
230
+ @object.photos = photos.map do |p|
231
+ MLS::Photo.new(:digest => p[:digest], :id => p[:id].to_i)
232
+ end
87
233
  end
88
234
 
89
235
  def address=(address)
@@ -91,7 +237,7 @@ class MLS::Listing::Parser < MLS::Parser
91
237
  end
92
238
 
93
239
  def agents=(agents)
94
- puts agents
240
+ @object.agents = agents.map {|a| MLS::Account::Parser.build(a) }
95
241
  end
96
242
 
97
243
  end
@@ -1,11 +1,33 @@
1
+ require 'restclient'
2
+
1
3
  class MLS::Photo < MLS::Resource
2
4
 
3
- def initialize(digest)
4
- @digest = digest
5
- end
5
+ property :id, Fixnum
6
+ property :digest, String
7
+ property :created_at, DateTime
8
+ property :updated_at, DateTime
9
+ property :file_content_type, String
10
+ property :file_name, String
11
+ property :file_size, Fixnum
12
+ property :url_template, String
6
13
 
7
14
  def url(style='large', protocol='http')
8
15
  "#{protocol}://#{MLS.asset_host}/photos/#{style}/#{@digest}.jpg"
9
16
  end
10
17
 
18
+ def self.create(image_file)
19
+ image_file.rewind
20
+ url = MLS.url.dup
21
+ url.user = nil
22
+ url.path = "/api/photos"
23
+ response = RestClient.post(url.to_s, {:file => image_file}, MLS.headers)
24
+ image_file.close unless image_file.closed?
25
+
26
+ MLS::Photo::Parser.parse(response.body)
27
+ end
28
+
29
+ end
30
+
31
+ class MLS::Photo::Parser < MLS::Parser
32
+
11
33
  end
@@ -1,37 +1,24 @@
1
1
  class MLS::TourRequest < MLS::Resource
2
- property :message, String
2
+ property :message, String
3
3
 
4
- attr_accessor :listing
4
+ property :id, Fixnum
5
+ property :account_id, Fixnum
6
+ property :listing_id, Fixnum
7
+ property :message, String
8
+ property :company, String
9
+ property :population, String
10
+ property :growing, Boolean
11
+ property :created_at, DateTime, :serialize => :false
12
+ property :updated_at, DateTime, :serialize => :false
13
+
14
+ attr_accessor :account, :listing
5
15
 
6
16
  class << self
7
17
  def get_all_for_account
8
- MLS.get('/account/tour_requests') do |code, response|
9
- case code
10
- when 400
11
- @errors = MLS.parse(response.body)[:errors]
12
- return false
13
- else
14
- MLS.handle_response(response)
15
- return MLS::TourRequest::Parser.parse_collection(response.body)
16
- end
17
- end
18
+ response = MLS.get('/account/tour_requests')
19
+ MLS::TourRequest::Parser.parse_collection(response.body)
18
20
  end
19
21
 
20
- def create(listing_id, message, account_params={})
21
- params = account_params
22
- params[:id] = listing_id
23
- params[:message] = message
24
- MLS.post('/account/tour_requests', params) do |code, response|
25
- case code
26
- when 400
27
- @errors = MLS.parse(response.body)[:errors]
28
- return false
29
- else
30
- MLS.handle_response(response)
31
- return true
32
- end
33
- end
34
- end
35
22
  end
36
23
  end
37
24
 
@@ -40,8 +27,8 @@ class MLS::TourRequest::Parser < MLS::Parser
40
27
  def listing=(listing)
41
28
  @object.listing = MLS::Listing::Parser.build(listing)
42
29
  end
43
-
44
- def self.collection_root_element
45
- :tour_requests
30
+
31
+ def account=(account)
32
+ @object.account = MLS::Account::Parser.build(account)
46
33
  end
47
- end
34
+ end
data/lib/mls/parser.rb CHANGED
@@ -27,9 +27,10 @@ class MLS::Parser
27
27
  self.new.parse(data)
28
28
  end
29
29
 
30
- def self.parse_collection(data)
30
+ def self.parse_collection(data, options={})
31
+ root = options[:collection_root_element] || collection_root_element
31
32
  collection = []
32
- extract_attributes(data)[collection_root_element].each do |attrs|
33
+ extract_attributes(data)[root].each do |attrs|
33
34
  collection << build(attrs)
34
35
  end
35
36
  collection
@@ -44,7 +45,7 @@ class MLS::Parser
44
45
  end
45
46
 
46
47
  def method_missing(method, *args, &block)
47
- object.__send__(method, *args, &block)
48
+ object.__send__(method, *args, &block) if object.methods.include?(method)
48
49
  end
49
50
 
50
51
  def extract_attributes(data)
@@ -66,4 +67,4 @@ class MLS::Parser
66
67
  object_class.collection_root_element
67
68
  end
68
69
 
69
- end
70
+ end
@@ -5,14 +5,18 @@ class MLS::Property::DateTime < MLS::Property
5
5
  ::DateTime.iso8601(value)
6
6
  elsif value.nil? || value.is_a?(::DateTime)
7
7
  value
8
+ elsif value.is_a?(::Time) || value.is_a?(::Date)
9
+ value.to_datetime
8
10
  else
9
11
  raise 'unsupported date type'
10
12
  end
11
13
  end
12
14
 
13
15
  def dump(value)
14
- if value.is_a?(::DateTime)
16
+ if value.is_a?(::DateTime) || value.is_a?(::Time) || value.is_a?(::Date)
15
17
  value.iso8601
18
+ elsif value.nil?
19
+ nil
16
20
  else
17
21
  raise 'unsupported date type'
18
22
  end