mls 0.2.2 → 0.2.3

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 (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