mls 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ .rvmrc
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 'mls/version'
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
- module MLS
14
+ class BigDecimal
15
+ old_to_s = instance_method :to_s
5
16
 
6
- class << self
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
- attr_accessor :environment
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
- def env
11
- @environment ||= 'development'
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
- def site
15
- env == 'production' ? 'http://mls.42floors.com' : 'http://staging.mls.42floors.com'
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
- def asset_host=(host)
19
- @asset_host = host
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
- def asset_host
23
- return @asset_host if @asset_host
24
- env == 'production' ? 'assets.42floors.com' : 's3.amazonaws.com/staging-assets.42floors.com'
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
- class Resource < ActiveResource::Base
30
- self.site = MLS.site
31
- self.prefix = '/api/'
32
- self.user = nil
33
- self.password = nil
139
+ def ping
140
+ get('/ping').body
141
+ end
34
142
 
35
- # def create
36
- # json = JSON.generate({self.class.element_name => JSON.parse(encode)})
37
- # connection.post(collection_path, json, self.class.headers).tap do |response|
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
- private
147
+ def default_logger
148
+ logger = Logger.new(STDOUT)
149
+ logger.level = Logger::INFO
150
+ logger
151
+ end
44
152
 
45
- def self.instantiate_collection(collection, prefix_options = {})
46
- collection[self.collection_name].collect! do |record|
47
- instantiate_record(record, prefix_options)
48
- end
49
- end
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/use'
56
- require 'mls/user'
57
- require 'mls/photo'
58
- require 'mls/address'
59
- require 'mls/listing'
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
@@ -0,0 +1,14 @@
1
+ class MLS::Exception < Exception
2
+ end
3
+
4
+ class MLS::BadRequest < MLS::Exception
5
+ end
6
+
7
+ class MLS::Unauthorized < MLS::Exception
8
+ end
9
+
10
+ class MLS::NotFound < MLS::Exception
11
+ end
12
+
13
+ class MLS::ApiVersionUnsupported < MLS::Exception
14
+ end
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,9 @@
1
+ class MLS::Property::Boolean < MLS::Property
2
+
3
+ def load(value) # from_json
4
+ !!value # !! booleanizes the value
5
+ end
6
+
7
+ alias :dump :load #only for primatives
8
+
9
+ 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
@@ -0,0 +1,16 @@
1
+ class MLS::Property::Fixnum < ::MLS::Property
2
+
3
+ def load(value) # from_json
4
+ if value.nil?
5
+ nil
6
+ elsif value.is_a?(::Fixnum)
7
+ value
8
+ else
9
+ value.to_i
10
+ end
11
+
12
+ end
13
+
14
+ alias :dump :load #only for primatives
15
+
16
+ end
@@ -0,0 +1,15 @@
1
+ class MLS::Property::String < MLS::Property
2
+
3
+ def load(value) # from_json
4
+ if value.nil?
5
+ nil
6
+ elsif value.is_a?(::String)
7
+ value
8
+ else
9
+ value.to_s
10
+ end
11
+ end
12
+
13
+ alias :dump :load #only for primatives
14
+
15
+ end
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module MLS
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
data/mls.gemspec CHANGED
@@ -25,4 +25,5 @@ Gem::Specification.new do |s|
25
25
  s.add_development_dependency 'minitest'
26
26
  s.add_development_dependency 'turn'
27
27
  s.add_runtime_dependency 'activeresource'
28
+ s.add_runtime_dependency 'yajl-ruby'
28
29
  end
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.1.2
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-06-04 00:00:00.000000000 Z
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: &70095576893220 !ruby/object:Gem::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: *70095576893220
24
+ version_requirements: *70151949432680
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rdoc
27
- requirement: &70095576892800 !ruby/object:Gem::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: *70095576892800
35
+ version_requirements: *70151949432160
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bundler
38
- requirement: &70095576892380 !ruby/object:Gem::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: *70095576892380
46
+ version_requirements: *70151949431660
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: minitest
49
- requirement: &70095576891960 !ruby/object:Gem::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: *70095576891960
57
+ version_requirements: *70151949431180
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: turn
60
- requirement: &70095576891540 !ruby/object:Gem::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: *70095576891540
68
+ version_requirements: *70151949430400
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: activeresource
71
- requirement: &70095576907480 !ruby/object:Gem::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: *70095576907480
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/address.rb
92
- - lib/mls/listing.rb
93
- - lib/mls/photo.rb
94
- - lib/mls/use.rb
95
- - lib/mls/user.rb
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
@@ -1,7 +0,0 @@
1
- class MLS::Address < MLS::Resource
2
-
3
- def self.query(q)
4
- get :query, :query => q
5
- end
6
-
7
- end
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