mls 1.4.0 → 1.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mls.rb +16 -40
- data/lib/mls/account.rb +33 -2
- data/lib/mls/agency.rb +1 -1
- data/lib/mls/change.rb +40 -0
- data/lib/mls/coworking_space.rb +12 -0
- data/lib/mls/document.rb +157 -0
- data/lib/mls/email.rb +41 -3
- data/lib/mls/email_address.rb +8 -0
- data/lib/mls/event.rb +20 -0
- data/lib/mls/event_action.rb +6 -0
- data/lib/mls/image_ordering.rb +6 -0
- data/lib/mls/inquiry.rb +5 -0
- data/lib/mls/lead.rb +4 -3
- data/lib/mls/listing.rb +9 -41
- data/lib/mls/mistake.rb +10 -0
- data/lib/mls/organization.rb +1 -0
- data/lib/mls/phone.rb +8 -0
- data/lib/mls/property.rb +42 -10
- data/lib/mls/{lead_listing.rb → recommendation.rb} +1 -3
- data/lib/mls/reference.rb +9 -0
- data/lib/mls/region.rb +43 -1
- data/lib/mls/session.rb +6 -6
- data/lib/mls/slug.rb +11 -0
- data/lib/mls/source.rb +15 -0
- data/lib/mls/space.rb +2 -0
- data/lib/mls/task.rb +52 -0
- data/lib/mls/time_log.rb +14 -0
- data/lib/mls/unit.rb +38 -0
- data/lib/mls/use.rb +1 -1
- data/lib/mls/vendor.rb +13 -0
- data/lib/mls/webpage.rb +7 -0
- data/mls.gemspec +1 -1
- metadata +21 -8
- data/lib/mls/floorplan.rb +0 -11
- data/lib/mls/flyer.rb +0 -11
- data/lib/mls/lead_property.rb +0 -10
- data/lib/mls/photo.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb0e3846ad2100b2167d386911bf6e4d504ffb47
|
4
|
+
data.tar.gz: f413e79ba06c41ccb8d105d88cae096c338374de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9d84713798402d9da3f892e09c036918432c965ffdd2030ccc35286033b0dec2022419d65e2ea049355c41c5aa6c816afce9e31ff3243f6b8891881b96fa5a0
|
7
|
+
data.tar.gz: ce4f8326d2255448f5513fffc31a8438bae12d7c68ff6e781831e523634a9bc51c871fb1ac95161471efc7742f0d5528cb7190a4b5486da42438ce76349d8307
|
data/lib/mls.rb
CHANGED
@@ -31,6 +31,13 @@ module MLS
|
|
31
31
|
Thread.current[:sunstone_cookie_store] = nil
|
32
32
|
end
|
33
33
|
|
34
|
+
def self.with_api_key(key, &block)
|
35
|
+
Thread.current[:sunstone_api_key] = key
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
Thread.current[:sunstone_api_key] = nil
|
39
|
+
end
|
40
|
+
|
34
41
|
end
|
35
42
|
|
36
43
|
class MLS::Model < ActiveRecord::Base
|
@@ -43,14 +50,11 @@ module MLS::Slugger
|
|
43
50
|
|
44
51
|
module ClassMethods
|
45
52
|
|
46
|
-
def find(*
|
47
|
-
friendly = -> (
|
53
|
+
def find(*ids)
|
54
|
+
friendly = -> (id) { id.respond_to?(:to_i) && id.to_i.to_s != id.to_s }
|
55
|
+
return super if ids.size > 1 || !ids.all? { |x| friendly.call(x) }
|
48
56
|
|
49
|
-
|
50
|
-
find_by_slug!(args)
|
51
|
-
else
|
52
|
-
super
|
53
|
-
end
|
57
|
+
find_by_slug!(ids)
|
54
58
|
end
|
55
59
|
|
56
60
|
end
|
@@ -66,11 +70,11 @@ module MLS::Avatar
|
|
66
70
|
extend ActiveSupport::Concern
|
67
71
|
|
68
72
|
included do
|
69
|
-
belongs_to :avatar, :class_name => '
|
73
|
+
belongs_to :avatar, :class_name => 'Image'
|
70
74
|
end
|
71
75
|
|
72
76
|
def avatar_url(options={})
|
73
|
-
|
77
|
+
|
74
78
|
options.reverse_merge!({
|
75
79
|
:style => nil,
|
76
80
|
:bg => nil,
|
@@ -86,8 +90,8 @@ module MLS::Avatar
|
|
86
90
|
else options[:protocol]
|
87
91
|
result = "#{options[:protocol]}://"
|
88
92
|
end
|
89
|
-
|
90
|
-
result += "#{options[:host]}/#{
|
93
|
+
|
94
|
+
result += "#{options[:host]}/#{avatar_hash_key}.#{options[:format]}"
|
91
95
|
result += "?#{url_params.to_param}" if url_params.size > 0
|
92
96
|
|
93
97
|
result
|
@@ -95,32 +99,4 @@ module MLS::Avatar
|
|
95
99
|
|
96
100
|
end
|
97
101
|
|
98
|
-
|
99
|
-
require 'mls/account'
|
100
|
-
require 'mls/email'
|
101
|
-
require 'mls/organization'
|
102
|
-
require 'mls/property'
|
103
|
-
require 'mls/region'
|
104
|
-
require 'mls/listing'
|
105
|
-
require 'mls/space'
|
106
|
-
require 'mls/lead'
|
107
|
-
require 'mls/lead_listing'
|
108
|
-
require 'mls/lead_property'
|
109
|
-
require 'mls/address'
|
110
|
-
require 'mls/locality'
|
111
|
-
require 'mls/flyer'
|
112
|
-
require 'mls/inquiry'
|
113
|
-
require 'mls/agency'
|
114
|
-
require 'mls/session'
|
115
|
-
require 'mls/floorplan'
|
116
|
-
require 'mls/use'
|
117
|
-
require 'mls/comment'
|
118
|
-
|
119
|
-
# Models
|
120
|
-
# # Helpers
|
121
|
-
# class MLS
|
122
|
-
#
|
123
|
-
# def current_account
|
124
|
-
# end
|
125
|
-
#
|
126
|
-
# end
|
102
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'mls', '*.rb'), &method(:require))
|
data/lib/mls/account.rb
CHANGED
@@ -7,10 +7,41 @@ class Account < MLS::Model
|
|
7
7
|
|
8
8
|
belongs_to :organization
|
9
9
|
|
10
|
+
has_many :tasks
|
11
|
+
has_many :sources
|
10
12
|
has_many :agencies, :inverse_of => :agent, :foreign_key => :agent_id
|
11
|
-
|
13
|
+
|
14
|
+
has_and_belongs_to_many :regions, :foreign_key => :agent_id
|
15
|
+
|
16
|
+
has_many :email_addresses, :dependent => :destroy do
|
17
|
+
def primary
|
18
|
+
# For cases where the number is not primary we order
|
19
|
+
order(:primary => :desc).first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
has_many :phones, dependent: :destroy do
|
24
|
+
|
12
25
|
def primary
|
13
|
-
where
|
26
|
+
# For cases where the number is not primary we order
|
27
|
+
order(:primary => :desc).first
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def email_address
|
33
|
+
if email_addresses.loaded?
|
34
|
+
email_addresses.to_a.find{|p| p.primary }.try(:address)
|
35
|
+
else
|
36
|
+
email_addresses.primary.try(:address)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def phone
|
41
|
+
if phones.loaded?
|
42
|
+
phones.to_a.find{|p| p.primary }.try(:number)
|
43
|
+
else
|
44
|
+
phones.primary.try(:number)
|
14
45
|
end
|
15
46
|
end
|
16
47
|
|
data/lib/mls/agency.rb
CHANGED
data/lib/mls/change.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class Change < MLS::Model
|
2
|
+
self.inheritance_column = nil
|
3
|
+
|
4
|
+
belongs_to :subject, :polymorphic => true
|
5
|
+
has_many :event_actions, :as => :action
|
6
|
+
has_many :mistakes
|
7
|
+
|
8
|
+
# Returns the association instance for the given name, instantiating it if it doesn't already exist
|
9
|
+
def association(name) #:nodoc:
|
10
|
+
association = super
|
11
|
+
|
12
|
+
return association if name.to_s != 'subject'
|
13
|
+
|
14
|
+
association.instance_exec do
|
15
|
+
def klass
|
16
|
+
type = owner[reflection.foreign_type]
|
17
|
+
type.presence && type.constantize
|
18
|
+
rescue NameError
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def reader(force_reload=false)
|
23
|
+
if klass
|
24
|
+
if force_reload
|
25
|
+
klass.uncached { reload }
|
26
|
+
elsif !loaded? || stale_target?
|
27
|
+
reload
|
28
|
+
end
|
29
|
+
|
30
|
+
target
|
31
|
+
else
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
association
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/lib/mls/document.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
class Document < MLS::Model
|
2
|
+
|
3
|
+
def self.create(file)
|
4
|
+
if doc = find_matching(file)
|
5
|
+
doc
|
6
|
+
else
|
7
|
+
data, headers = Multipart::Post.prepare_query("document[file]" => file)
|
8
|
+
|
9
|
+
req = Net::HTTP::Post.new("/documents")
|
10
|
+
req.body = data
|
11
|
+
req['Content-Type'] = headers['Content-Type']
|
12
|
+
|
13
|
+
res = Document.connection.instance_variable_get(:@connection).send_request(req)
|
14
|
+
instantiate(JSON.parse(res.body).select{|k,v| column_names.include?(k.to_s) })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def url(style=:original)
|
19
|
+
MLS.config['document_host'] + path(style)
|
20
|
+
end
|
21
|
+
|
22
|
+
def path(style=:original)
|
23
|
+
"/documents/#{hash_key}/#{style}#{File.extname(filename)}"
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def width
|
28
|
+
return nil if !dimensions
|
29
|
+
return dimensions.split('x')[0].to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
def height
|
33
|
+
return nil if !dimensions
|
34
|
+
return dimensions.split('x')[1].to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def aspect_ratio
|
38
|
+
return nil if !width || !height
|
39
|
+
return width.to_f / height.to_f
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.find_matching(file)
|
43
|
+
filename = file.original_filename if file.respond_to?(:original_filename)
|
44
|
+
filename ||= File.basename(file.path)
|
45
|
+
|
46
|
+
# If we can tell the possible mime-type from the filename, use the
|
47
|
+
# first in the list; otherwise, use "application/octet-stream"
|
48
|
+
content_type = file.content_type if file.respond_to?(:content_type)
|
49
|
+
content_type ||= (MIME::Types.type_for(filename)[0] || MIME::Types["application/octet-stream"][0]).simplified
|
50
|
+
|
51
|
+
matching_docs = Document.where(:filename => filename, :content_type => content_type, :size => file.size)
|
52
|
+
if matching_docs.count > 0
|
53
|
+
matching_docs = matching_docs.where(:md5 => Digest::MD5.file(file.path).hexdigest)
|
54
|
+
end
|
55
|
+
|
56
|
+
matching_docs.first
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class Image < Document
|
62
|
+
|
63
|
+
def url(options={})
|
64
|
+
options.reverse_merge!({
|
65
|
+
:style => nil,
|
66
|
+
:bg => nil,
|
67
|
+
:protocol => 'https',
|
68
|
+
:format => "jpg",
|
69
|
+
:host => MLS.image_host
|
70
|
+
});
|
71
|
+
|
72
|
+
url_params = {s: options[:style], bg: options[:bg]}.select{ |k, v| v }
|
73
|
+
|
74
|
+
if options[:protocol] == :relative # Protocol Relative
|
75
|
+
result = '//'
|
76
|
+
else options[:protocol]
|
77
|
+
result = "#{options[:protocol]}://"
|
78
|
+
end
|
79
|
+
|
80
|
+
result += "#{options[:host]}/#{hash_key}.#{options[:format]}"
|
81
|
+
result += "?#{url_params.to_param}" if url_params.size > 0
|
82
|
+
|
83
|
+
result
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class PDF < Document
|
89
|
+
include MLS::Avatar
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
# Takes a hash of string and file parameters and returns a string of text
|
95
|
+
# formatted to be sent as a multipart form post.
|
96
|
+
module Multipart
|
97
|
+
# Formats a given hash as a multipart form post
|
98
|
+
# If a hash value responds to :string or :read messages, then it is
|
99
|
+
# interpreted as a file and processed accordingly; otherwise, it is assumed
|
100
|
+
# to be a string
|
101
|
+
class Post
|
102
|
+
|
103
|
+
def self.prepare_query(params)
|
104
|
+
boundary = "-----RubyMultipartClient~#{SecureRandom.hex(32)}"
|
105
|
+
|
106
|
+
parts = params.map do |k, v|
|
107
|
+
if v.respond_to?(:path) && v.respond_to?(:read)
|
108
|
+
FileParam.new(k, v)
|
109
|
+
else
|
110
|
+
StringParam.new(k, v)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
query = parts.map {|p| "--#{boundary}\r\n#{p.to_multipart}" }.join('') + "--#{boundary}--"
|
115
|
+
return query, { "Content-Type" => "multipart/form-data; boundary=#{boundary}" }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# Formats a basic string key/value pair for inclusion with a multipart post
|
122
|
+
class StringParam
|
123
|
+
attr_accessor :k, :v
|
124
|
+
|
125
|
+
def initialize(k, v)
|
126
|
+
@k = k
|
127
|
+
@v = v
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_multipart
|
131
|
+
return "Content-Disposition: form-data; name=\"#{@k}\"\r\n\r\n#{v}\r\n"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Formats the contents of a file or string for inclusion with a multipart
|
136
|
+
# form post
|
137
|
+
class FileParam
|
138
|
+
|
139
|
+
def initialize(k, file)
|
140
|
+
@k = k
|
141
|
+
@file = file
|
142
|
+
|
143
|
+
@filename = file.original_filename if file.respond_to?(:original_filename)
|
144
|
+
@filename ||= File.basename(file.path)
|
145
|
+
|
146
|
+
# If we can tell the possible mime-type from the filename, use the
|
147
|
+
# first in the list; otherwise, use "application/octet-stream"
|
148
|
+
@content_type = file.content_type if file.respond_to?(:content_type)
|
149
|
+
@content_type ||= (MIME::Types.type_for(@filename)[0] || MIME::Types["application/octet-stream"][0]).simplified
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_multipart
|
153
|
+
return "Content-Disposition: form-data; name=\"#{@k}\"; filename=\"#{ @filename }\"\r\n" +
|
154
|
+
"Content-Type: #{ @content_type }\r\n\r\n#{ @file.read }\r\n"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/lib/mls/email.rb
CHANGED
@@ -1,8 +1,46 @@
|
|
1
1
|
class Email < MLS::Model
|
2
|
-
|
2
|
+
|
3
|
+
belongs_to :source
|
4
|
+
has_and_belongs_to_many :attachments, :class_name => 'Document'
|
5
|
+
|
6
|
+
def from
|
7
|
+
if from_name
|
8
|
+
"\"#{from_name}\" <#{from_address}>"
|
9
|
+
else
|
10
|
+
from_address
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
from
|
16
|
+
end
|
17
|
+
|
18
|
+
def to
|
19
|
+
to_names.zip(to_addresses).map{|t| t[0] ? "\"#{t[0]}\" <#{t[1]}>" : t[1] }.join(', ')
|
20
|
+
end
|
21
|
+
|
22
|
+
def sender
|
23
|
+
headers['Sender']
|
24
|
+
end
|
25
|
+
|
26
|
+
def cc
|
27
|
+
cc_names.zip(cc_addresses).map{|t| t[0] ? "\"#{t[0]}\" <#{t[1]}>" : t[1] }.join(', ')
|
28
|
+
end
|
29
|
+
|
30
|
+
def bcc
|
31
|
+
bcc_names.zip(bcc_addresses).map{|t| t[0] ? "\"#{t[0]}\" <#{t[1]}>" : t[1] }.join(', ')
|
32
|
+
end
|
33
|
+
|
34
|
+
def reply_to
|
35
|
+
reply_to_names.zip(reply_to_addresses).map{|t| t[0] ? "\"#{t[0]}\" <#{t[1]}>" : t[1] }.join(', ')
|
36
|
+
end
|
3
37
|
|
4
|
-
|
38
|
+
def multipart?
|
39
|
+
body.keys.size > 1
|
40
|
+
end
|
5
41
|
|
6
|
-
|
42
|
+
def parts
|
43
|
+
body.keys
|
44
|
+
end
|
7
45
|
|
8
46
|
end
|
data/lib/mls/event.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class Event < MLS::Model
|
2
|
+
|
3
|
+
SOURCE_TYPES = %w(call email website)
|
4
|
+
|
5
|
+
belongs_to :account
|
6
|
+
belongs_to :task
|
7
|
+
|
8
|
+
has_many :event_actions, :dependent => :destroy
|
9
|
+
|
10
|
+
has_many :regards, :dependent => :destroy
|
11
|
+
|
12
|
+
def actions
|
13
|
+
event_actions.map(&:action)
|
14
|
+
end
|
15
|
+
|
16
|
+
def regarding
|
17
|
+
regards.map(&:thing)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/mls/inquiry.rb
CHANGED
data/lib/mls/lead.rb
CHANGED
data/lib/mls/listing.rb
CHANGED
@@ -2,11 +2,9 @@ class Listing < MLS::Model
|
|
2
2
|
self.inheritance_column = nil
|
3
3
|
|
4
4
|
include MLS::Slugger
|
5
|
-
include MLS::Avatar
|
6
5
|
|
7
|
-
LEASE_STATES = %w(listed leased)
|
8
6
|
SPACE_TYPES = %w(unit floor building)
|
9
|
-
TYPES = %w(Sale Lease Sublease
|
7
|
+
TYPES = %w(Sale Lease Sublease)
|
10
8
|
TERMS = ['Full Service', 'Net Lease', 'NN', 'NNN', 'Absolute NNN', 'Gross Lease', 'Modified Gross', 'Industrial Gross', 'Absolute Gross', 'Ground Lease', 'Other']
|
11
9
|
SALE_TERMS = ['Cash to Seller', 'Purchase Money Mtg.', 'Owner Financing', 'Build-to-Suit', 'Sale/Leaseback', 'Other']
|
12
10
|
RATE_UNITS = {
|
@@ -19,20 +17,19 @@ class Listing < MLS::Model
|
|
19
17
|
AMENITIES = %W(kitchen showers outdoor_space reception turnkey build_to_suit furniture
|
20
18
|
natural_light high_ceilings plug_and_play additional_storage storefront)
|
21
19
|
|
22
|
-
belongs_to :
|
23
|
-
belongs_to :
|
24
|
-
belongs_to :flyer
|
20
|
+
belongs_to :flyer, :class_name => 'Document'
|
21
|
+
belongs_to :unit
|
25
22
|
|
26
|
-
|
23
|
+
has_one :property, :through => :unit
|
27
24
|
|
28
25
|
has_many :photos, -> { order(:order => :asc) }, :as => :subject, :inverse_of => :subject
|
29
26
|
|
30
|
-
has_many :agencies, -> { order(:order) }
|
27
|
+
has_many :agencies, -> { order(:order) }
|
31
28
|
has_many :agents, -> { order('agencies.order') }, :through => :agencies, :source => :agent
|
32
29
|
|
33
30
|
has_one :address
|
34
31
|
has_many :addresses
|
35
|
-
|
32
|
+
|
36
33
|
# has_many :comments
|
37
34
|
# has_many :regions
|
38
35
|
# has_many :agents
|
@@ -42,16 +39,12 @@ class Listing < MLS::Model
|
|
42
39
|
# has_many :inquiries, :as => :subject, :inverse_of => :subject
|
43
40
|
# has_many :agencies, -> { order('"order"') }, :dependent => :destroy, :inverse_of => :subject, :as => :subject
|
44
41
|
# has_many :agents, -> { order('agencies.order') }, :through => :agencies, :inverse_of => :listings, :source => :agent
|
45
|
-
# has_many :email_proxies, :as => :subject, :inverse_of => :subject
|
46
42
|
# has_many :lead_listings, :dependent => :delete_all
|
47
43
|
|
48
|
-
has_and_belongs_to_many :uses
|
49
|
-
|
50
44
|
def contact
|
51
45
|
@contact ||= agents.first
|
52
46
|
end
|
53
|
-
|
54
|
-
|
47
|
+
|
55
48
|
def rate(units=nil)
|
56
49
|
return nil if !read_attribute(:rate)
|
57
50
|
units ||= rate_units
|
@@ -123,37 +116,12 @@ class Listing < MLS::Model
|
|
123
116
|
type == 'Sublease'
|
124
117
|
end
|
125
118
|
|
126
|
-
def coworking? # TODO: test me
|
127
|
-
type == 'CoworkingSpace'
|
128
|
-
end
|
129
|
-
|
130
119
|
def sale?
|
131
120
|
type == 'Sale'
|
132
121
|
end
|
133
122
|
|
134
123
|
def name
|
135
124
|
return read_attribute(:name) if read_attribute(:name)
|
136
|
-
|
137
|
-
case space_type
|
138
|
-
when 'unit'
|
139
|
-
if unit
|
140
|
-
"Unit #{unit}"
|
141
|
-
elsif floor
|
142
|
-
"#{floor.ordinalize} Floor Unit"
|
143
|
-
else
|
144
|
-
"Unit Lease"
|
145
|
-
end
|
146
|
-
when 'building'
|
147
|
-
"Entire Building"
|
148
|
-
when 'floor'
|
149
|
-
if floor
|
150
|
-
"#{floor.ordinalize} Floor"
|
151
|
-
elsif unit
|
152
|
-
"Unit #{unit}"
|
153
|
-
else
|
154
|
-
"Floor Lease"
|
155
|
-
end
|
156
|
-
end
|
125
|
+
unit.name
|
157
126
|
end
|
158
|
-
|
159
|
-
end
|
127
|
+
end
|
data/lib/mls/mistake.rb
ADDED
data/lib/mls/organization.rb
CHANGED
data/lib/mls/phone.rb
ADDED
data/lib/mls/property.rb
CHANGED
@@ -3,13 +3,12 @@ class Property < MLS::Model
|
|
3
3
|
include MLS::Slugger
|
4
4
|
include MLS::Avatar
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
has_many :listings
|
6
|
+
has_many :units
|
7
|
+
has_many :references, as: :subject
|
8
|
+
has_many :listings, :through => :units
|
9
9
|
has_many :localities
|
10
10
|
has_many :regions, :through => :localities
|
11
|
-
has_many :
|
12
|
-
has_many :internal_photos, -> { order(:order => :asc) }, :as => :subject, :inverse_of => :subject
|
11
|
+
has_many :image_orderings, as: :subject
|
13
12
|
|
14
13
|
has_many :addresses do
|
15
14
|
def primary
|
@@ -17,11 +16,44 @@ class Property < MLS::Model
|
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
def photos
|
20
|
+
image_orderings.sort_by(&:order).map(&:image)
|
21
|
+
end
|
22
|
+
|
23
|
+
def contact
|
24
|
+
@contact ||= listings.where(leased_at: nil, authorized: true, type: ['Lease', 'Sublease'])
|
25
|
+
.order(size: :desc).first.try(:contact)
|
25
26
|
end
|
26
27
|
|
28
|
+
def address
|
29
|
+
addresses.find(&:primary)
|
30
|
+
end
|
31
|
+
|
32
|
+
def neighborhood_region
|
33
|
+
params = {:query => address.neighborhood} if address.try(:neighborhood)
|
34
|
+
params ||= {:query => neighborhood} if neighborhood
|
35
|
+
params ||= {:type => "Neighborhood"}
|
36
|
+
fetch_region(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def city_region
|
40
|
+
fetch_region(:type => "City")
|
41
|
+
end
|
42
|
+
|
43
|
+
def market
|
44
|
+
fetch_region(:is_market => true)
|
45
|
+
end
|
46
|
+
|
47
|
+
def target_region
|
48
|
+
fetch_region(:target => true)
|
49
|
+
end
|
50
|
+
|
51
|
+
def fetch_region(params)
|
52
|
+
if regions.loaded?
|
53
|
+
params = params.map{|k,v| [k, v]}
|
54
|
+
regions.to_a.find{|r| r[params[0][0]] == params[0][1]}
|
55
|
+
else
|
56
|
+
regions.where(params).first
|
57
|
+
end
|
58
|
+
end
|
27
59
|
end
|
data/lib/mls/region.rb
CHANGED
@@ -4,10 +4,52 @@ class Region < MLS::Model
|
|
4
4
|
|
5
5
|
self.inheritance_column = nil
|
6
6
|
|
7
|
-
belongs_to :cover_photo, :class_name => '
|
7
|
+
belongs_to :cover_photo, :class_name => 'Image'
|
8
|
+
belongs_to :market, :class_name => 'Region'
|
8
9
|
|
9
10
|
has_and_belongs_to_many :parents, :join_table => 'regions_regions', :class_name => 'Region', :foreign_key => 'child_id', :association_foreign_key => 'parent_id'
|
10
11
|
has_and_belongs_to_many :children, :join_table => 'regions_regions', :class_name => 'Region', :foreign_key => 'parent_id', :association_foreign_key => 'child_id'
|
11
12
|
|
13
|
+
def name
|
14
|
+
if common_name.try(:[], 'eng')
|
15
|
+
common_name['eng'].is_a?(Array) ? common_name['eng'].first : common_name['eng']
|
16
|
+
else
|
17
|
+
official_name['eng'].is_a?(Array) ? official_name['eng'].first : official_name['eng']
|
18
|
+
end
|
19
|
+
end
|
12
20
|
|
21
|
+
def target
|
22
|
+
return @target if @target
|
23
|
+
@target = children.where(:target => true).first
|
24
|
+
@target ||= market.try(:target)
|
25
|
+
@target ||= market
|
26
|
+
@target ||= self
|
27
|
+
|
28
|
+
@target
|
29
|
+
end
|
30
|
+
|
31
|
+
def cover_photo_url(options={})
|
32
|
+
|
33
|
+
options.reverse_merge!({
|
34
|
+
:style => nil,
|
35
|
+
:bg => nil,
|
36
|
+
:protocol => 'https',
|
37
|
+
:format => "jpg",
|
38
|
+
:host => MLS.image_host
|
39
|
+
});
|
40
|
+
|
41
|
+
url_params = { s: options[:style], bg: options[:bg] }.select{ |k, v| v }
|
42
|
+
|
43
|
+
if options[:protocol] == :relative # Protocol Relative
|
44
|
+
result = '//'
|
45
|
+
else options[:protocol]
|
46
|
+
result = "#{options[:protocol]}://"
|
47
|
+
end
|
48
|
+
|
49
|
+
result += "#{options[:host]}/#{cover_photo_digest}.#{options[:format]}"
|
50
|
+
result += "?#{url_params.to_param}" if url_params.size > 0
|
51
|
+
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
13
55
|
end
|
data/lib/mls/session.rb
CHANGED
@@ -2,15 +2,15 @@ class Session < MLS::Model
|
|
2
2
|
|
3
3
|
belongs_to :account
|
4
4
|
|
5
|
-
# Authenticate with
|
5
|
+
# Authenticate with email_address and password.
|
6
6
|
# Returns either the newly created session or nil
|
7
|
-
def self.authenticate(
|
8
|
-
if
|
9
|
-
password =
|
10
|
-
|
7
|
+
def self.authenticate(email_address, password=nil)
|
8
|
+
if email_address.is_a? Hash
|
9
|
+
password = email_address[:password]
|
10
|
+
email_address = email_address[:email_address]
|
11
11
|
end
|
12
12
|
|
13
|
-
Session.create(:
|
13
|
+
Session.create(:email_address => email_address, :password => password)
|
14
14
|
rescue Sunstone::Exception::Unauthorized
|
15
15
|
nil
|
16
16
|
end
|
data/lib/mls/slug.rb
ADDED
data/lib/mls/source.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class Source < MLS::Model
|
2
|
+
|
3
|
+
has_many :tasks, :as => :subject
|
4
|
+
has_many :emails
|
5
|
+
|
6
|
+
belongs_to :account
|
7
|
+
belongs_to :organization
|
8
|
+
belongs_to :upload, class_name: 'Flyer'
|
9
|
+
|
10
|
+
def name
|
11
|
+
return email_address if email_address.present?
|
12
|
+
url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
data/lib/mls/space.rb
CHANGED
data/lib/mls/task.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
class Task < MLS::Model
|
2
|
+
self.inheritance_column = nil
|
3
|
+
|
4
|
+
belongs_to :subject, :polymorphic => true
|
5
|
+
belongs_to :account
|
6
|
+
|
7
|
+
has_many :events
|
8
|
+
has_many :mistakes
|
9
|
+
has_many :time_logs
|
10
|
+
has_many :reviews, -> { where(:type => "review") }, :class_name => "Task", :as => :subject, :inverse_of => :subject
|
11
|
+
has_many :fixes, -> { where(:type => "fix") }, :class_name => "Task", :as => :subject, :inverse_of => :subject
|
12
|
+
|
13
|
+
def for_source?
|
14
|
+
subject_type == "Source"
|
15
|
+
end
|
16
|
+
|
17
|
+
def for_task?
|
18
|
+
subject_type == "Taks"
|
19
|
+
end
|
20
|
+
|
21
|
+
def review?
|
22
|
+
type == "review"
|
23
|
+
end
|
24
|
+
|
25
|
+
def fix?
|
26
|
+
type == "fix"
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse?
|
30
|
+
type == "parse"
|
31
|
+
end
|
32
|
+
|
33
|
+
def duration
|
34
|
+
time_logs.where(:started_at => true, :stopped_at => true).sum("duration")
|
35
|
+
end
|
36
|
+
|
37
|
+
def pause
|
38
|
+
log = time_logs.where(:started_at => true, :stopped_at => false).first
|
39
|
+
if log
|
40
|
+
log.update(:stopped_at => Time.now)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def resume
|
45
|
+
time_logs << TimeLog.create(:started_at => Time.now)
|
46
|
+
end
|
47
|
+
|
48
|
+
def paused?
|
49
|
+
!started_at.nil? && completed_at.nil? && time_logs.where(:started_at => true, :stopped_at => false).length == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/mls/time_log.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class TimeLog < MLS::Model
|
2
|
+
|
3
|
+
belongs_to :task
|
4
|
+
belongs_to :account
|
5
|
+
|
6
|
+
scope :completed, -> { where('started_at IS NOT NULL AND stopped_at IS NOT NULL') }
|
7
|
+
scope :incompleted, -> { where('started_at IS NOT NULL AND stopped_at IS NULL') }
|
8
|
+
|
9
|
+
def duration
|
10
|
+
return nil if !(started_at && stopped_at)
|
11
|
+
stopped_at - started_at
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/lib/mls/unit.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
class Unit < MLS::Model
|
2
|
+
self.inheritance_column = nil
|
3
|
+
|
4
|
+
include MLS::Slugger
|
5
|
+
include MLS::Avatar
|
6
|
+
|
7
|
+
belongs_to :property
|
8
|
+
belongs_to :floorplan, :class_name => 'Document'
|
9
|
+
|
10
|
+
has_many :listings
|
11
|
+
has_many :image_orderings, as: :subject, dependent: :destroy
|
12
|
+
has_many :photos, through: :image_orderings, source: :image
|
13
|
+
# has_many :photos, -> { order(:order => :asc) }, :as => :subject, :inverse_of => :subject
|
14
|
+
|
15
|
+
has_and_belongs_to_many :uses
|
16
|
+
|
17
|
+
def tags
|
18
|
+
read_attribute(:tags) || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
name = ""
|
23
|
+
case self.type
|
24
|
+
when 'unit'
|
25
|
+
name += "Unit"
|
26
|
+
name += " #{self.number}" if self.number
|
27
|
+
name += " (Floor #{self.floor})" if self.floor
|
28
|
+
when 'floor'
|
29
|
+
name += "Floor"
|
30
|
+
name += " #{self.floor}" if self.floor
|
31
|
+
name += " (Unit #{self.number})" if self.number
|
32
|
+
when 'building'
|
33
|
+
"Entire Building"
|
34
|
+
end
|
35
|
+
name
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/mls/use.rb
CHANGED
data/lib/mls/vendor.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class Vendor < MLS::Model
|
2
|
+
self.inheritance_column = nil
|
3
|
+
|
4
|
+
include MLS::Slugger
|
5
|
+
include MLS::Avatar
|
6
|
+
|
7
|
+
has_and_belongs_to_many :regions
|
8
|
+
belongs_to :account
|
9
|
+
|
10
|
+
has_many :image_orderings, as: :subject, dependent: :destroy
|
11
|
+
has_many :photos, through: :image_orderings, source: :image
|
12
|
+
|
13
|
+
end
|
data/lib/mls/webpage.rb
ADDED
data/mls.gemspec
CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "mls"
|
6
|
-
s.version = '1.4.
|
6
|
+
s.version = '1.4.3'
|
7
7
|
s.authors = ["Jon Bracy", "James R. Bracy"]
|
8
8
|
s.email = ["jon@42floors.com", "james@42floors.com"]
|
9
9
|
s.homepage = "http://mls.42floors.com"
|
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: 1.4.
|
4
|
+
version: 1.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Bracy
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-06-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -195,23 +195,36 @@ files:
|
|
195
195
|
- lib/mls/account.rb
|
196
196
|
- lib/mls/address.rb
|
197
197
|
- lib/mls/agency.rb
|
198
|
+
- lib/mls/change.rb
|
198
199
|
- lib/mls/comment.rb
|
200
|
+
- lib/mls/coworking_space.rb
|
201
|
+
- lib/mls/document.rb
|
199
202
|
- lib/mls/email.rb
|
200
|
-
- lib/mls/
|
201
|
-
- lib/mls/
|
203
|
+
- lib/mls/email_address.rb
|
204
|
+
- lib/mls/event.rb
|
205
|
+
- lib/mls/event_action.rb
|
206
|
+
- lib/mls/image_ordering.rb
|
202
207
|
- lib/mls/inquiry.rb
|
203
208
|
- lib/mls/lead.rb
|
204
|
-
- lib/mls/lead_listing.rb
|
205
|
-
- lib/mls/lead_property.rb
|
206
209
|
- lib/mls/listing.rb
|
207
210
|
- lib/mls/locality.rb
|
211
|
+
- lib/mls/mistake.rb
|
208
212
|
- lib/mls/organization.rb
|
209
|
-
- lib/mls/
|
213
|
+
- lib/mls/phone.rb
|
210
214
|
- lib/mls/property.rb
|
215
|
+
- lib/mls/recommendation.rb
|
216
|
+
- lib/mls/reference.rb
|
211
217
|
- lib/mls/region.rb
|
212
218
|
- lib/mls/session.rb
|
219
|
+
- lib/mls/slug.rb
|
220
|
+
- lib/mls/source.rb
|
213
221
|
- lib/mls/space.rb
|
222
|
+
- lib/mls/task.rb
|
223
|
+
- lib/mls/time_log.rb
|
224
|
+
- lib/mls/unit.rb
|
214
225
|
- lib/mls/use.rb
|
226
|
+
- lib/mls/vendor.rb
|
227
|
+
- lib/mls/webpage.rb
|
215
228
|
- mls.gemspec
|
216
229
|
- test/fixtures/flyer.pdf
|
217
230
|
- test/mls/attribute_test.rb
|
@@ -237,7 +250,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
237
250
|
version: '0'
|
238
251
|
requirements: []
|
239
252
|
rubyforge_project: mls
|
240
|
-
rubygems_version: 2.
|
253
|
+
rubygems_version: 2.4.5
|
241
254
|
signing_key:
|
242
255
|
specification_version: 4
|
243
256
|
summary: 42Floors MLS Client
|
data/lib/mls/floorplan.rb
DELETED
data/lib/mls/flyer.rb
DELETED
data/lib/mls/lead_property.rb
DELETED
data/lib/mls/photo.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
class Photo < MLS::Model
|
2
|
-
|
3
|
-
belongs_to :subject, :polymorphic => true, :inverse_of => :photos
|
4
|
-
|
5
|
-
def url(options={})
|
6
|
-
options.reverse_merge!({
|
7
|
-
:style => nil,
|
8
|
-
:bg => nil,
|
9
|
-
:protocol => 'https',
|
10
|
-
:format => "jpg",
|
11
|
-
:host => MLS.image_host
|
12
|
-
});
|
13
|
-
|
14
|
-
url_params = {s: options[:style], bg: options[:bg]}.select{ |k, v| v }
|
15
|
-
|
16
|
-
if options[:protocol] == :relative # Protocol Relative
|
17
|
-
result = '//'
|
18
|
-
else options[:protocol]
|
19
|
-
result = "#{options[:protocol]}://"
|
20
|
-
end
|
21
|
-
|
22
|
-
result += "#{options[:host]}/#{digest}.#{options[:format]}"
|
23
|
-
result += "?#{url_params.to_param}" if url_params.size > 0
|
24
|
-
|
25
|
-
result
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
class InternalPhoto < Photo; end
|