mls 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rvmrc +1 -0
- data/lib/mls.rb +171 -37
- data/lib/mls/errors.rb +14 -0
- data/lib/mls/model.rb +94 -0
- data/lib/mls/models/account.rb +150 -0
- data/lib/mls/models/address.rb +39 -0
- data/lib/mls/models/listing.rb +97 -0
- data/lib/mls/{photo.rb → models/photo.rb} +0 -0
- data/lib/mls/{use.rb → models/use.rb} +0 -0
- data/lib/mls/{user.rb → models/user.rb} +0 -0
- data/lib/mls/parser.rb +69 -0
- data/lib/mls/properties/boolean.rb +9 -0
- data/lib/mls/properties/datetime.rb +21 -0
- data/lib/mls/properties/decimal.rb +19 -0
- data/lib/mls/properties/fixnum.rb +16 -0
- data/lib/mls/properties/string.rb +15 -0
- data/lib/mls/property.rb +44 -0
- data/lib/mls/resource.rb +69 -0
- data/lib/mls/version.rb +1 -1
- data/mls.gemspec +1 -0
- metadata +42 -19
- data/lib/mls/address.rb +0 -7
- data/lib/mls/listing.rb +0 -95
data/.gitignore
CHANGED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@mls-gem --create
|
data/lib/mls.rb
CHANGED
@@ -1,59 +1,193 @@
|
|
1
|
-
require '
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'logger'
|
4
|
+
require 'net/https'
|
5
|
+
require 'singleton'
|
6
|
+
require 'yajl'
|
7
|
+
require 'bigdecimal'
|
8
|
+
require 'bigdecimal/util'
|
9
|
+
require 'active_support'
|
2
10
|
require 'active_resource'
|
11
|
+
require 'date'
|
12
|
+
require 'time'
|
3
13
|
|
4
|
-
|
14
|
+
class BigDecimal
|
15
|
+
old_to_s = instance_method :to_s
|
5
16
|
|
6
|
-
|
17
|
+
define_method :to_s do |param='F'|
|
18
|
+
old_to_s.bind(self).(param)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Decimal
|
23
|
+
end
|
24
|
+
class Boolean
|
25
|
+
end
|
7
26
|
|
8
|
-
|
27
|
+
class MLS
|
28
|
+
include Singleton
|
29
|
+
|
30
|
+
API_VERSION = '0.1.0'
|
31
|
+
|
32
|
+
attr_accessor :url, :api_key, :auth_key, :logger
|
33
|
+
|
34
|
+
def url=(uri)
|
35
|
+
@url = uri
|
36
|
+
|
37
|
+
uri = URI.parse(uri)
|
38
|
+
@api_key = CGI.unescape(uri.user)
|
39
|
+
@host = uri.host
|
40
|
+
@port = uri.port
|
41
|
+
end
|
9
42
|
|
10
|
-
|
11
|
-
|
43
|
+
def logger
|
44
|
+
@logger ||= default_logger
|
45
|
+
end
|
46
|
+
|
47
|
+
def connection
|
48
|
+
@connection ||= Net::HTTP.new(@host, @port)
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_headers(req)
|
52
|
+
req['Content-Type'] = 'application/json'
|
53
|
+
req['X-42Floors-API-Version'] = API_VERSION
|
54
|
+
req['X-42Floors-API-Key'] = api_key
|
55
|
+
req['X-42Floors-API-Auth-Key'] = auth_key if auth_key
|
56
|
+
end
|
57
|
+
|
58
|
+
def put(url, body={})
|
59
|
+
req = Net::HTTP::Put.new("/api#{url}")
|
60
|
+
req.body = Yajl::Encoder.encode(body)
|
61
|
+
add_headers(req)
|
62
|
+
|
63
|
+
response = connection.request(req)
|
64
|
+
if block_given?
|
65
|
+
yield(response.code.to_i, response)
|
66
|
+
else
|
67
|
+
handle_response(response)
|
12
68
|
end
|
69
|
+
end
|
13
70
|
|
14
|
-
|
15
|
-
|
71
|
+
def post(url, body={})
|
72
|
+
req = Net::HTTP::Post.new("/api#{url}")
|
73
|
+
req.body = Yajl::Encoder.encode(body)
|
74
|
+
add_headers(req)
|
75
|
+
|
76
|
+
response = connection.request(req)
|
77
|
+
if block_given?
|
78
|
+
yield(response.code.to_i, response)
|
79
|
+
else
|
80
|
+
handle_response(response)
|
16
81
|
end
|
82
|
+
end
|
17
83
|
|
18
|
-
|
19
|
-
|
84
|
+
def delete(url, body={})
|
85
|
+
req = Net::HTTP::Delete.new("/api#{url}")
|
86
|
+
req.body = Yajl::Encoder.encode(body)
|
87
|
+
add_headers(req)
|
88
|
+
|
89
|
+
response = connection.request(req)
|
90
|
+
if block_given?
|
91
|
+
yield(response.code.to_i, response)
|
92
|
+
else
|
93
|
+
handle_response(response)
|
20
94
|
end
|
95
|
+
end
|
21
96
|
|
22
|
-
|
23
|
-
|
24
|
-
|
97
|
+
def get(url, params={})
|
98
|
+
url = "/api#{url}?" + params.to_param
|
99
|
+
req = Net::HTTP::Get.new(url)
|
100
|
+
add_headers(req)
|
101
|
+
response = connection.request(req)
|
102
|
+
if block_given?
|
103
|
+
yield(response.code.to_i, response)
|
104
|
+
else
|
105
|
+
handle_response(response)
|
25
106
|
end
|
107
|
+
|
108
|
+
response
|
109
|
+
end
|
26
110
|
|
111
|
+
def handle_response(response)
|
112
|
+
if response['X-42Floors-API-Version-Deprecated']
|
113
|
+
logger.warn("DEPRECATION WARNING: API v#{API_VERSION} is being phased out")
|
114
|
+
end
|
115
|
+
|
116
|
+
raise(response.code, response.body)
|
117
|
+
response.body
|
118
|
+
end
|
119
|
+
|
120
|
+
def raise(error_code, message=nil)
|
121
|
+
case error_code.to_i
|
122
|
+
when 401
|
123
|
+
super Unauthorized, message
|
124
|
+
when 404, 410
|
125
|
+
super NotFound, message
|
126
|
+
when 422
|
127
|
+
super ApiVersionUnsupported, message
|
128
|
+
when 300...400
|
129
|
+
super MLS::Exception, error_code
|
130
|
+
when 400
|
131
|
+
super MLS::BadRequest, message
|
132
|
+
when 401...500
|
133
|
+
super MLS::Exception, error_code
|
134
|
+
when 500...600
|
135
|
+
super MLS::Exception, error_code
|
136
|
+
end
|
27
137
|
end
|
28
138
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
self.user = nil
|
33
|
-
self.password = nil
|
139
|
+
def ping
|
140
|
+
get('/ping').body
|
141
|
+
end
|
34
142
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# self.id = id_from_response(response)
|
39
|
-
# load_attributes_from_response(response)
|
40
|
-
# end
|
41
|
-
# end
|
143
|
+
def auth_ping
|
144
|
+
post('/ping').body
|
145
|
+
end
|
42
146
|
|
43
|
-
|
147
|
+
def default_logger
|
148
|
+
logger = Logger.new(STDOUT)
|
149
|
+
logger.level = Logger::INFO
|
150
|
+
logger
|
151
|
+
end
|
44
152
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
153
|
+
def self.method_missing(method, *args, &block)
|
154
|
+
instance.__send__(method, *args, &block)
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.parse(json)
|
158
|
+
Yajl::Parser.new(:symbolize_keys => true).parse(json)
|
51
159
|
end
|
52
160
|
|
53
161
|
end
|
54
162
|
|
55
|
-
require 'mls/
|
56
|
-
require 'mls/
|
57
|
-
require 'mls/
|
58
|
-
require 'mls/
|
59
|
-
require 'mls/
|
163
|
+
#require File.expand_path('../mls/errors', __FILE__)
|
164
|
+
#require File.expand_path('../mls/resource', __FILE__)
|
165
|
+
#require File.expand_path('../mls/model', __FILE__)
|
166
|
+
#require File.expand_path('../mls/property', __FILE__)
|
167
|
+
#require File.expand_path('../mls/parser', __FILE__)
|
168
|
+
|
169
|
+
require 'mls/errors'
|
170
|
+
require 'mls/resource'
|
171
|
+
require 'mls/model'
|
172
|
+
require 'mls/property'
|
173
|
+
require 'mls/parser'
|
174
|
+
|
175
|
+
#require File.expand_path('../mls/properties/fixnum', __FILE__)
|
176
|
+
#require File.expand_path('../mls/properties/boolean', __FILE__)
|
177
|
+
#require File.expand_path('../mls/properties/decimal', __FILE__)
|
178
|
+
#require File.expand_path('../mls/properties/datetime', __FILE__)
|
179
|
+
#require File.expand_path('../mls/properties/string', __FILE__)
|
180
|
+
|
181
|
+
require 'mls/properties/fixnum'
|
182
|
+
require 'mls/properties/boolean'
|
183
|
+
require 'mls/properties/decimal'
|
184
|
+
require 'mls/properties/datetime'
|
185
|
+
require 'mls/properties/string'
|
186
|
+
|
187
|
+
#require File.expand_path('../mls/models/account', __FILE__)
|
188
|
+
#require File.expand_path('../mls/models/listing', __FILE__)
|
189
|
+
#require File.expand_path('../mls/models/address', __FILE__)
|
190
|
+
|
191
|
+
require 'mls/models/account'
|
192
|
+
require 'mls/models/listing'
|
193
|
+
require 'mls/models/address'
|
data/lib/mls/errors.rb
ADDED
data/lib/mls/model.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module MLS::Model
|
2
|
+
|
3
|
+
def self.extended(model)
|
4
|
+
model.instance_variable_set(:@properties, {})
|
5
|
+
model.instance_variable_set(:@associations, {})
|
6
|
+
end
|
7
|
+
|
8
|
+
# Properties ===================================================================================================
|
9
|
+
|
10
|
+
def property(name, type, options = {})
|
11
|
+
klass = MLS::Property.determine_class(type)
|
12
|
+
raise NotImplementedError, "#{type} is not supported" unless klass
|
13
|
+
|
14
|
+
property = klass.new(name, options)
|
15
|
+
@properties[property.name] = property
|
16
|
+
|
17
|
+
create_reader_for(property)
|
18
|
+
create_writer_for(property)
|
19
|
+
end
|
20
|
+
|
21
|
+
def properties
|
22
|
+
@properties
|
23
|
+
end
|
24
|
+
|
25
|
+
def property_module
|
26
|
+
@property_module ||= begin
|
27
|
+
mod = Module.new
|
28
|
+
class_eval do
|
29
|
+
include mod
|
30
|
+
end
|
31
|
+
mod
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_reader_for(property)
|
36
|
+
reader_name = property.name.to_s
|
37
|
+
boolean_reader_name = "#{reader_name}?"
|
38
|
+
reader_visibility = property.reader_visibility
|
39
|
+
instance_variable_name = property.instance_variable_name
|
40
|
+
|
41
|
+
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
42
|
+
#{reader_visibility}
|
43
|
+
def #{reader_name}
|
44
|
+
return #{instance_variable_name} if defined?(#{instance_variable_name})
|
45
|
+
property = properties[:#{reader_name}]
|
46
|
+
#{instance_variable_name} = property ? property.default : nil
|
47
|
+
end
|
48
|
+
RUBY
|
49
|
+
|
50
|
+
if property.kind_of?(MLS::Property::Boolean)
|
51
|
+
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
52
|
+
#{reader_visibility}
|
53
|
+
def #{boolean_reader_name}
|
54
|
+
#{reader_name}
|
55
|
+
end
|
56
|
+
RUBY
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_writer_for(property)
|
61
|
+
name = property.name
|
62
|
+
writer_name = "#{name}="
|
63
|
+
writer_visibility = property.writer_visibility
|
64
|
+
instance_variable_name = property.instance_variable_name
|
65
|
+
|
66
|
+
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
67
|
+
#{writer_visibility}
|
68
|
+
def #{writer_name}(value)
|
69
|
+
property = self.class.properties[:#{name}]
|
70
|
+
#{instance_variable_name} = property.load(value)
|
71
|
+
end
|
72
|
+
RUBY
|
73
|
+
end
|
74
|
+
|
75
|
+
#== for rails form stuff
|
76
|
+
def model_name
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def param_key
|
81
|
+
root_element.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
# used for parser
|
85
|
+
|
86
|
+
def root_element
|
87
|
+
@root_element ||= ActiveSupport::Inflector.demodulize(self).downcase.to_sym
|
88
|
+
end
|
89
|
+
|
90
|
+
def collection_root_element
|
91
|
+
@collection_root_element ||= ActiveSupport::Inflector.demodulize(self).downcase.pluralize.to_sym
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
class MLS::Account < MLS::Resource
|
2
|
+
|
3
|
+
ROLES = %w(user broker property_manager landlord analyst assistant other)
|
4
|
+
DEFAULTS = {:role => 'user'}
|
5
|
+
|
6
|
+
property :id, Fixnum
|
7
|
+
property :role, String, :default => 'user'
|
8
|
+
property :name, String
|
9
|
+
property :email, String
|
10
|
+
property :password, String, :serialize => :if_present
|
11
|
+
property :password_confirmation, String, :serialize => :if_present
|
12
|
+
property :perishable_token, String
|
13
|
+
property :perishable_token_set_at, String
|
14
|
+
|
15
|
+
property :phone, String
|
16
|
+
property :company, String
|
17
|
+
property :license, String
|
18
|
+
property :linkedin, String
|
19
|
+
property :twitter, String
|
20
|
+
property :facebook, String
|
21
|
+
property :web, String
|
22
|
+
|
23
|
+
property :state, String
|
24
|
+
property :zip, String
|
25
|
+
|
26
|
+
property :auth_key, String
|
27
|
+
|
28
|
+
def update!
|
29
|
+
MLS.put('/account', to_hash) do |code, response|
|
30
|
+
case code
|
31
|
+
when 400
|
32
|
+
@errors = MLS.parse(response.body)[:errors]
|
33
|
+
return false
|
34
|
+
else
|
35
|
+
MLS.handle_response(response)
|
36
|
+
MLS::Account::Parser.update(self, response.body)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create!
|
42
|
+
Rails.logger.warn(to_hash)
|
43
|
+
MLS.post('/account', to_hash) do |code, response|
|
44
|
+
case code
|
45
|
+
when 400
|
46
|
+
@errors = MLS.parse(response.body)[:errors]
|
47
|
+
return false
|
48
|
+
else
|
49
|
+
MLS.handle_response(response)
|
50
|
+
MLS::Account::Parser.update(self, response.body)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def agent?
|
56
|
+
role != 'user' && role != ''
|
57
|
+
end
|
58
|
+
|
59
|
+
def favorites(page=nil, per_page=nil)
|
60
|
+
params_hash = {:page => page, :per_page => per_page}
|
61
|
+
Rails.logger.warn(params_hash)
|
62
|
+
MLS.get('/account/favorites', params_hash) do |code, response|
|
63
|
+
case code
|
64
|
+
when 400
|
65
|
+
@errors = MLS.parse(response.body)[:errors]
|
66
|
+
return false
|
67
|
+
else
|
68
|
+
MLS.handle_response(response)
|
69
|
+
return MLS::Listing::Parser.parse_collection(response.body)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def favorite(listing_id)
|
75
|
+
params_hash = {:id => listing_id}
|
76
|
+
Rails.logger.warn(params_hash)
|
77
|
+
MLS.post('/account/favorites', params_hash) do |code, response|
|
78
|
+
case code
|
79
|
+
when 400
|
80
|
+
@errors = MLS.parse(response.body)[:errors]
|
81
|
+
return false
|
82
|
+
else
|
83
|
+
MLS.handle_response(response)
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def unfavorite(listing_id)
|
90
|
+
MLS.delete("/account/favorites/#{listing_id}") do |code, response|
|
91
|
+
case code
|
92
|
+
when 400
|
93
|
+
@errors = MLS.parse(response.body)[:errors]
|
94
|
+
return false
|
95
|
+
else
|
96
|
+
MLS.handle_response(response)
|
97
|
+
return true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class << self
|
103
|
+
|
104
|
+
def current
|
105
|
+
response = MLS.get('/account')
|
106
|
+
MLS::Account::Parser.parse(response.body)
|
107
|
+
end
|
108
|
+
|
109
|
+
def authenticate(attrs_or_email, password=nil)
|
110
|
+
email = attrs_or_email.is_a?(Hash) ? attrs_or_email[:email] : attrs_or_email
|
111
|
+
password = attrs_or_email.is_a?(Hash) ? attrs_or_email[:password] : password
|
112
|
+
|
113
|
+
response = MLS.get('/account', {:email => email, :password => password})
|
114
|
+
MLS::Account::Parser.parse(response.body)
|
115
|
+
rescue MLS::Unauthorized => response
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def reset_password!(email)
|
120
|
+
params_hash = {:email => email}
|
121
|
+
Rails.logger.warn(params_hash)
|
122
|
+
MLS.post('/accounts/password/reset', params_hash) do |code, response|
|
123
|
+
case code
|
124
|
+
when 400
|
125
|
+
@errors = MLS.parse(response.body)[:errors]
|
126
|
+
return false
|
127
|
+
else
|
128
|
+
MLS.handle_response(response)
|
129
|
+
return true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def update_password!(params_hash)
|
135
|
+
Rails.logger.warn(params_hash)
|
136
|
+
response = MLS.put('/accounts/password', params_hash)
|
137
|
+
MLS::Account::Parser.parse(response)
|
138
|
+
rescue MLS::BadRequest => response
|
139
|
+
@errors = MLS.parse(response.message)
|
140
|
+
return false
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
class MLS::Account::Parser < MLS::Parser
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class MLS::Address < MLS::Resource
|
2
|
+
|
3
|
+
property :id, Fixnum
|
4
|
+
property :name, String
|
5
|
+
property :slug, String
|
6
|
+
|
7
|
+
property :latitude, Decimal
|
8
|
+
property :longitude, Decimal
|
9
|
+
|
10
|
+
property :formatted_address, String
|
11
|
+
property :street_number, String
|
12
|
+
property :street, String
|
13
|
+
property :neighborhood, String
|
14
|
+
property :city, String
|
15
|
+
property :county, String
|
16
|
+
property :state, String
|
17
|
+
property :country, String
|
18
|
+
property :postal_code, String
|
19
|
+
|
20
|
+
class << self
|
21
|
+
|
22
|
+
def query(q)
|
23
|
+
response = MLS.get('/addresses/query', :query => q)
|
24
|
+
MLS::Address::Parser.parse_collection(response.body)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Bounds is passed as 'n,e,s,w' or [n, e, s, w]
|
28
|
+
def box_cluster(bounds, zoom, filters={})
|
29
|
+
response = MLS.get('/addresses/box_cluster', :bounds => bounds, :zoom => zoom, :filters => filters)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class MLS::Address::Parser < MLS::Parser
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class MLS::Listing < MLS::Resource
|
2
|
+
|
3
|
+
KINDS = %w(lease sublease coworking)
|
4
|
+
SPACE_TYPES = %w(unit floor building)
|
5
|
+
LEASE_TYPES = ['Full Service', 'NNN', 'Gross', 'Industrial Gross', 'Modified Gross', 'Triple Net', 'Modified Net']
|
6
|
+
RATE_UNITS = ['ft^2/year', 'ft^2/month', 'desk/month']
|
7
|
+
USES = ["Office", "Creative", "Loft", "Medical Office", "Flex Space", "R&D", "Office Showroom", "Industrial", "Retail"]
|
8
|
+
|
9
|
+
property :id, Fixnum
|
10
|
+
property :address_id, Fixnum
|
11
|
+
property :use_id, Fixnum
|
12
|
+
property :use, String
|
13
|
+
property :account_id, Fixnum
|
14
|
+
property :hidden, Boolean, :default => false
|
15
|
+
|
16
|
+
property :name, String
|
17
|
+
property :kind, String, :default => 'lease'
|
18
|
+
property :space_type, String, :default => 'unit'
|
19
|
+
property :unit, String
|
20
|
+
property :floor, Fixnum
|
21
|
+
property :comments, String
|
22
|
+
|
23
|
+
property :total_size, Fixnum
|
24
|
+
property :maximum_contiguous_size, Fixnum
|
25
|
+
property :minimum_divisable_size, Fixnum
|
26
|
+
|
27
|
+
property :lease_type, String
|
28
|
+
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
|
33
|
+
property :nnn_expenses, Decimal
|
34
|
+
property :sublease_expiration, DateTime
|
35
|
+
|
36
|
+
property :available_on, DateTime
|
37
|
+
property :maximum_term_length, Fixnum
|
38
|
+
property :minimum_term_length, Fixnum
|
39
|
+
|
40
|
+
property :offices, Fixnum
|
41
|
+
property :conference_rooms, Fixnum
|
42
|
+
property :bathrooms, Fixnum
|
43
|
+
property :desks, Fixnum
|
44
|
+
|
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
|
60
|
+
|
61
|
+
property :created_at, DateTime
|
62
|
+
property :updated_at, DateTime
|
63
|
+
|
64
|
+
|
65
|
+
attr_accessor :address, :agents
|
66
|
+
|
67
|
+
def photos
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
|
73
|
+
def find(id)
|
74
|
+
response = MLS.get("/listings/#{id}")
|
75
|
+
MLS::Listing::Parser.parse(response.body)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
class MLS::Listing::Parser < MLS::Parser
|
84
|
+
|
85
|
+
def photos=(photos)
|
86
|
+
puts photos
|
87
|
+
end
|
88
|
+
|
89
|
+
def address=(address)
|
90
|
+
@object.address = MLS::Address::Parser.build(address)
|
91
|
+
end
|
92
|
+
|
93
|
+
def agents=(agents)
|
94
|
+
puts agents
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
data/lib/mls/parser.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
class MLS::Parser
|
2
|
+
|
3
|
+
attr_reader :object
|
4
|
+
|
5
|
+
def initialize(object=nil, persisted=true)
|
6
|
+
@object = object || self.class.object_class.new({}, persisted)
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse(data)
|
10
|
+
attributes = extract_attributes(data)[self.class.root_element]
|
11
|
+
update_attributes(attributes)
|
12
|
+
object
|
13
|
+
end
|
14
|
+
|
15
|
+
def build(attributes)
|
16
|
+
update_attributes(attributes)
|
17
|
+
object
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_attributes(attributes)
|
21
|
+
attributes.each do |k,v|
|
22
|
+
self.send("#{k}=".to_sym, v)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.parse(data)
|
27
|
+
self.new.parse(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_collection(data)
|
31
|
+
collection = []
|
32
|
+
extract_attributes(data)[collection_root_element].each do |attrs|
|
33
|
+
collection << build(attrs)
|
34
|
+
end
|
35
|
+
collection
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.build(attributes)
|
39
|
+
self.new.build(attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.update(object, data)
|
43
|
+
self.new(object).parse(data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(method, *args, &block)
|
47
|
+
object.__send__(method, *args, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_attributes(data)
|
51
|
+
Yajl::Parser.new(:symbolize_keys => true).parse(data)
|
52
|
+
end
|
53
|
+
def self.extract_attributes(data)
|
54
|
+
Yajl::Parser.new(:symbolize_keys => true).parse(data)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.object_class
|
58
|
+
@object_class ||= ActiveSupport::Inflector.constantize(self.to_s.sub('::Parser',''))
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.root_element
|
62
|
+
object_class.root_element
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.collection_root_element
|
66
|
+
object_class.collection_root_element
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class MLS::Property::DateTime < MLS::Property
|
2
|
+
|
3
|
+
def load(value) # from_json
|
4
|
+
if value.is_a?(::String)
|
5
|
+
::DateTime.iso8601(value)
|
6
|
+
elsif value.nil? || value.is_a?(::DateTime)
|
7
|
+
value
|
8
|
+
else
|
9
|
+
raise 'unsupported date type'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def dump(value)
|
14
|
+
if value.is_a?(::DateTime)
|
15
|
+
value.iso8601
|
16
|
+
else
|
17
|
+
raise 'unsupported date type'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class MLS::Property::Decimal < MLS::Property
|
2
|
+
|
3
|
+
def load(value) # from_json
|
4
|
+
if value.is_a?(BigDecimal)
|
5
|
+
value
|
6
|
+
else
|
7
|
+
BigDecimal.new(value.to_s)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def dump(value)
|
12
|
+
if value.is_a?(BigDecimal)
|
13
|
+
value.to_s
|
14
|
+
else
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/mls/property.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
class MLS::Property
|
2
|
+
DEFAULT_OPTIONS = { :serialize => :always }
|
3
|
+
|
4
|
+
attr_reader :model, :name, :instance_variable_name, :options, :default
|
5
|
+
attr_reader :reader_visibility, :writer_visibility
|
6
|
+
|
7
|
+
def initialize(name, options={})
|
8
|
+
@name = name
|
9
|
+
@instance_variable_name = "@#{@name}".freeze
|
10
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
11
|
+
|
12
|
+
set_default_value
|
13
|
+
determine_visibility
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_default_value
|
17
|
+
@default = @options[:default]
|
18
|
+
end
|
19
|
+
|
20
|
+
def determine_visibility # default :public
|
21
|
+
@reader_visibility = @options[:reader] || :public
|
22
|
+
@writer_visibility = @options[:writer] || :public
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.determine_class(type)
|
26
|
+
return type if type < MLS::Property
|
27
|
+
find_class(::ActiveSupport::Inflector.demodulize(type))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.inherited(descendant)
|
31
|
+
demodulized_names[::ActiveSupport::Inflector.demodulize(descendant.name)] ||= descendant
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.demodulized_names
|
35
|
+
@demodulized_names ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_class(name)
|
39
|
+
klass = demodulized_names[name]
|
40
|
+
klass ||= const_get(name) if const_defined?(name)
|
41
|
+
klass
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/lib/mls/resource.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
class MLS::Resource
|
2
|
+
|
3
|
+
attr_reader :errors, :persisted
|
4
|
+
|
5
|
+
def self.inherited(subclass)
|
6
|
+
subclass.extend(MLS::Model)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(attributes = {}, persisted = false)
|
10
|
+
@persisted = persisted
|
11
|
+
@errors = {}
|
12
|
+
|
13
|
+
set_default_values
|
14
|
+
update_attributes(attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_record?
|
18
|
+
!@persisted
|
19
|
+
end
|
20
|
+
|
21
|
+
def persisted?
|
22
|
+
@persisted
|
23
|
+
end
|
24
|
+
|
25
|
+
def save
|
26
|
+
new_record? ? create! : update!
|
27
|
+
end
|
28
|
+
|
29
|
+
# Properties ===================================================================================================
|
30
|
+
|
31
|
+
def properties
|
32
|
+
self.class.properties
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_default_values
|
36
|
+
properties.each do |name, property|
|
37
|
+
self.send("#{name}=".to_sym, property.default) if property.default
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_attributes(attrs)
|
42
|
+
attrs.each { |k, v| self.send("#{k}=".to_sym, v) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Combo Breaker ================================================================================================
|
46
|
+
|
47
|
+
def to_hash
|
48
|
+
hash = {}
|
49
|
+
|
50
|
+
properties.each do |name, property|
|
51
|
+
serialize = property.options[:serialize] || :always
|
52
|
+
case serialize
|
53
|
+
when :always
|
54
|
+
hash[name] = property.dump(self.send(name))
|
55
|
+
when :if_present
|
56
|
+
hash[name] = property.dump(self.send(name)) if self.send(name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
hash
|
61
|
+
end
|
62
|
+
|
63
|
+
#== for rails form stuff
|
64
|
+
|
65
|
+
def to_key
|
66
|
+
persisted? ? [id] : nil
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/lib/mls/version.rb
CHANGED
data/mls.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70151949432680 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70151949432680
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rdoc
|
27
|
-
requirement: &
|
27
|
+
requirement: &70151949432160 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70151949432160
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: bundler
|
38
|
-
requirement: &
|
38
|
+
requirement: &70151949431660 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70151949431660
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: minitest
|
49
|
-
requirement: &
|
49
|
+
requirement: &70151949431180 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70151949431180
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: turn
|
60
|
-
requirement: &
|
60
|
+
requirement: &70151949430400 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70151949430400
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: activeresource
|
71
|
-
requirement: &
|
71
|
+
requirement: &70151949429880 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,18 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70151949429880
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: yajl-ruby
|
82
|
+
requirement: &70151949429420 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70151949429420
|
80
91
|
description: Ruby library for integrating with the 42Floors MLS
|
81
92
|
email:
|
82
93
|
- james@42floors.com
|
@@ -85,14 +96,26 @@ extensions: []
|
|
85
96
|
extra_rdoc_files: []
|
86
97
|
files:
|
87
98
|
- .gitignore
|
99
|
+
- .rvmrc
|
88
100
|
- Gemfile
|
89
101
|
- Rakefile
|
90
102
|
- lib/mls.rb
|
91
|
-
- lib/mls/
|
92
|
-
- lib/mls/
|
93
|
-
- lib/mls/
|
94
|
-
- lib/mls/
|
95
|
-
- lib/mls/
|
103
|
+
- lib/mls/errors.rb
|
104
|
+
- lib/mls/model.rb
|
105
|
+
- lib/mls/models/account.rb
|
106
|
+
- lib/mls/models/address.rb
|
107
|
+
- lib/mls/models/listing.rb
|
108
|
+
- lib/mls/models/photo.rb
|
109
|
+
- lib/mls/models/use.rb
|
110
|
+
- lib/mls/models/user.rb
|
111
|
+
- lib/mls/parser.rb
|
112
|
+
- lib/mls/properties/boolean.rb
|
113
|
+
- lib/mls/properties/datetime.rb
|
114
|
+
- lib/mls/properties/decimal.rb
|
115
|
+
- lib/mls/properties/fixnum.rb
|
116
|
+
- lib/mls/properties/string.rb
|
117
|
+
- lib/mls/property.rb
|
118
|
+
- lib/mls/resource.rb
|
96
119
|
- lib/mls/version.rb
|
97
120
|
- mls.gemspec
|
98
121
|
homepage: http://mls.42floors.com
|
data/lib/mls/address.rb
DELETED
data/lib/mls/listing.rb
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
class MLS::Listing < MLS::Resource
|
2
|
-
|
3
|
-
KINDS = %w(lease sublease coworking)
|
4
|
-
SPACE_TYPES = %w(unit floor building)
|
5
|
-
LEASE_TYPES = ['Full Service', 'NNN', 'Gross', 'Industrial Gross', 'Modified Gross', 'Triple Net', 'Modified Net']
|
6
|
-
|
7
|
-
|
8
|
-
validates :total_size, :presence => true, :numericality => true
|
9
|
-
validates :maximum_contiguous_size, :presence => true, :numericality => true
|
10
|
-
validates :minimum_divisable_size, :presence => true, :numericality => true
|
11
|
-
validates :rate, :numericality => { :allow_nil => true }
|
12
|
-
validates :sublease_expiration, :presence => { :if => proc { |l| l.sublease? }}
|
13
|
-
validates :nnn_expenses, :numericality => { :allow_nil => true }
|
14
|
-
validates :available_on, :presence => true
|
15
|
-
validates :floor, :numericality => { :allow_nil => true }
|
16
|
-
validates :offices, :numericality => { :allow_nil => true }
|
17
|
-
validates :conference_rooms, :numericality => { :allow_nil => true }
|
18
|
-
validates :bathrooms, :numericality => { :allow_nil => true }
|
19
|
-
validates :maximum_term_length, :numericality => { :allow_nil => true, :only_integer => true }
|
20
|
-
validates :minimum_term_length, :numericality => { :allow_nil => true, :only_integer => true }
|
21
|
-
validates :kind, :presence => true, :inclusion => {:in => KINDS}
|
22
|
-
validates :space_type, :presence => true, :inclusion => {:in => SPACE_TYPES}, :if => Proc.new{ |l| l.kind == 'lease' }
|
23
|
-
validates :lease_type, :inclusion => {:in => LEASE_TYPES}, :allow_nil => true, :if => Proc.new{ |l| l.kind == 'lease' }
|
24
|
-
validates :rate_units, :presence => true, :inclusion => { :in => ['ft^2/year', 'ft^2/month', 'desk/month'] }
|
25
|
-
|
26
|
-
def property
|
27
|
-
attributes[:property] ||= attributes[:property_id].nil? ? nil : MLS::Property.find(property_id)
|
28
|
-
end
|
29
|
-
|
30
|
-
schema do
|
31
|
-
# attribute :use, :string
|
32
|
-
attribute :total_size, :integer
|
33
|
-
attribute :sublease, :boolean
|
34
|
-
attribute :maximum_contiguous_size, :integer
|
35
|
-
attribute :minimum_divisable_size, :integer
|
36
|
-
attribute :available_on, :date
|
37
|
-
attribute :unit, :string
|
38
|
-
attribute :space_type, :string
|
39
|
-
attribute :lease, :string
|
40
|
-
attribute :lease_type, :string
|
41
|
-
attribute :rate, :decimal, :precision => 8, :scale => 2
|
42
|
-
attribute :rate_per_month, :decimal, :precision => 8, :scale => 2
|
43
|
-
attribute :rate_per_year, :decimal, :precision => 8, :scale => 2
|
44
|
-
attribute :sublease_expiration, :date
|
45
|
-
attribute :nnn_expenses, :decimal
|
46
|
-
attribute :floor, :integer
|
47
|
-
attribute :tenant_improvement, :string
|
48
|
-
attribute :commnets, :text
|
49
|
-
attribute :kitchen, :boolean, :default => false
|
50
|
-
attribute :offices, :integer
|
51
|
-
attribute :conference_rooms, :integer
|
52
|
-
attribute :bathrooms, :integer
|
53
|
-
attribute :showers, :boolean, :default => false
|
54
|
-
attribute :bike_rake, :boolean, :default => false
|
55
|
-
attribute :server_room, :boolean, :default => false
|
56
|
-
attribute :reception_area, :boolean, :default => false
|
57
|
-
attribute :turnkey, :boolean, :default => false
|
58
|
-
attribute :patio, :boolean, :default => false
|
59
|
-
attribute :created_at, :datetime
|
60
|
-
attribute :updated_at, :datetime
|
61
|
-
attribute :deleted_at, :datetime
|
62
|
-
attribute :copy_room, :boolean
|
63
|
-
attribute :term_length, :integer
|
64
|
-
attribute :bikes_allowed, :boolean
|
65
|
-
attribute :dog_friendly, :boolean
|
66
|
-
attribute :furniture_available, :boolean
|
67
|
-
attribute :cabling, :boolean
|
68
|
-
attribute :ready_to_move_in, :boolean
|
69
|
-
attribute :recent_space_improvements, :boolean
|
70
|
-
attribute :maximum_term_length, :integer
|
71
|
-
attribute :minimum_term_length, :integer
|
72
|
-
attribute :minimum_rate, :decimal, :precision => 8, :scale => 2
|
73
|
-
attribute :maximum_rate, :decimal, :precision => 8, :scale => 2
|
74
|
-
attribute :kind, :string
|
75
|
-
attribute :rate_units, :string
|
76
|
-
end
|
77
|
-
|
78
|
-
def available_on
|
79
|
-
Date.parse(attributes[:available_on])
|
80
|
-
end
|
81
|
-
|
82
|
-
def sublease_expiration
|
83
|
-
Date.parse(attributes[:sublease_expiration]) if attributes[:sublease_expiration]
|
84
|
-
end
|
85
|
-
|
86
|
-
def photos
|
87
|
-
return @photos if @photos
|
88
|
-
@photos = []
|
89
|
-
attributes[:photos].each do |digest|
|
90
|
-
@photos << MLS::Photo.new(digest)
|
91
|
-
end
|
92
|
-
@photos
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|