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