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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb4f1f49e4d47a1947018382340a4189a949fe96
4
- data.tar.gz: 413f3241e6392a275e93ffbcbdc2e8ee9f5da48c
3
+ metadata.gz: eb0e3846ad2100b2167d386911bf6e4d504ffb47
4
+ data.tar.gz: f413e79ba06c41ccb8d105d88cae096c338374de
5
5
  SHA512:
6
- metadata.gz: 7039ef61eedc1426662d6bbf3d52e7c308537f404a111c6e2f83fc1a8d36da2d328729e43c3678eb73a79a87070b2a3dcbf97d2b630d042733c1b76dac51be75
7
- data.tar.gz: 3ec113f6972d240cfe47f04f1b4998ad5eeaa370d3ad1e8cd93e15ceb025bb25b6e971dd006a27edbeff7879dc74a2eacabbe67eaa1d03f86cb76537f932fb83
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(*args)
47
- friendly = -> (arg) { arg.respond_to?(:to_i) && arg.to_i.to_s != arg.to_s }
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
- if args.count == 1 && friendly.call(args.first)
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 => 'Photo'
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]}/#{avatar_digest}.#{options[:format]}"
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
- require 'mls/photo'
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))
@@ -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
- has_many :emails, :dependent => :destroy do
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(:primary => true).first
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
 
@@ -1,6 +1,6 @@
1
1
  class Agency < MLS::Model
2
2
 
3
- belongs_to :subject, polymorphic: true
3
+ belongs_to :listing
4
4
  belongs_to :agent, :class_name => 'Account', :inverse_of => :agencies
5
5
 
6
6
  end
@@ -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
@@ -0,0 +1,12 @@
1
+ class CoworkingSpace < MLS::Model
2
+ include MLS::Slugger
3
+
4
+ belongs_to :unit
5
+
6
+ has_one :address
7
+ has_one :property, through: :unit
8
+
9
+ has_many :spaces
10
+ has_many :addresses
11
+
12
+ end
@@ -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
@@ -1,8 +1,46 @@
1
1
  class Email < MLS::Model
2
- self.inheritance_column = false
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
- TYPES = %w(work personal other)
38
+ def multipart?
39
+ body.keys.size > 1
40
+ end
5
41
 
6
- belongs_to :account
42
+ def parts
43
+ body.keys
44
+ end
7
45
 
8
46
  end
@@ -0,0 +1,8 @@
1
+ class EmailAddress < MLS::Model
2
+ self.inheritance_column = false
3
+
4
+ TYPES = %w(work personal other)
5
+
6
+ belongs_to :account
7
+
8
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ class EventAction < MLS::Model
2
+
3
+ belongs_to :event
4
+ belongs_to :action, polymorphic: true
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ class ImageOrdering < MLS::Model
2
+
3
+ belongs_to :subject, :counter_cache => :photos_count, :polymorphic => true
4
+ belongs_to :image
5
+
6
+ end
@@ -1,6 +1,11 @@
1
1
  class Inquiry < MLS::Model
2
2
 
3
+ has_many :emails
3
4
  belongs_to :lead
4
5
  belongs_to :subject, polymorphic: true
5
6
 
7
+ def property
8
+ subject.is_a? MLS::Model::Listing ? subject.property : subject
9
+ end
10
+
6
11
  end
@@ -3,7 +3,8 @@ class Lead < MLS::Model
3
3
  belongs_to :account
4
4
  belongs_to :agent, class_name: "Account"
5
5
 
6
- has_many :lead_properties
6
+ has_many :recommendations
7
+ # Removing lead_listings after recommendations release
7
8
  has_many :lead_listings
8
-
9
- end
9
+
10
+ end
@@ -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 CoworkingSpace)
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 :property
23
- belongs_to :floorplan
24
- belongs_to :flyer
20
+ belongs_to :flyer, :class_name => 'Document'
21
+ belongs_to :unit
25
22
 
26
- has_many :spaces
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) }, :as => :subject
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
- alias_method :default_contact, :contact
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
@@ -0,0 +1,10 @@
1
+ class Mistake < MLS::Model
2
+ self.inheritance_column = false
3
+
4
+ TYPES = %w(missing spelling incorrect other)
5
+
6
+ belongs_to :task
7
+ belongs_to :change
8
+ belongs_to :account
9
+
10
+ end
@@ -3,6 +3,7 @@ class Organization < MLS::Model
3
3
  include MLS::Slugger
4
4
 
5
5
  has_many :agents, :class_name => 'Account'
6
+ has_and_belongs_to_many :regions
6
7
 
7
8
  def name
8
9
  common_name || legal_name
@@ -0,0 +1,8 @@
1
+ class Phone < MLS::Model
2
+ self.inheritance_column = false
3
+
4
+ TYPES = ['mobile', 'work', 'home', 'main', 'home fax' 'work fax', 'other fax', 'pager', 'other', 'office']
5
+
6
+ belongs_to :account
7
+
8
+ end
@@ -3,13 +3,12 @@ class Property < MLS::Model
3
3
  include MLS::Slugger
4
4
  include MLS::Avatar
5
5
 
6
- belongs_to :contact, :class_name => 'Account'
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 :photos, -> { where(:type => "Photo").order(:order => :asc) }, :as => :subject, :inverse_of => :subject
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 default_contact
21
- @default_contact ||= listings.where(lease_state: :listed, ghost: false, authorized: true)
22
- .where({ type: ['Lease', 'Sublease']})
23
- .order(size: :desc)
24
- .first.try(:contact)
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
@@ -1,9 +1,7 @@
1
- class LeadListing < MLS::Model
1
+ class Recommendation < MLS::Model
2
2
 
3
3
  belongs_to :listing
4
- belongs_to :lead_property
5
4
  belongs_to :lead
6
- belongs_to :property
7
5
 
8
6
  has_many :comments, :as => :subject
9
7
 
@@ -0,0 +1,9 @@
1
+ class Reference < MLS::Model
2
+ self.inheritance_column = nil
3
+
4
+ belongs_to :subject, polymorphic: true
5
+
6
+ validates :type, presence: true
7
+
8
+ end
9
+
@@ -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 => 'Photo'
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
@@ -2,15 +2,15 @@ class Session < MLS::Model
2
2
 
3
3
  belongs_to :account
4
4
 
5
- # Authenticate with email and password.
5
+ # Authenticate with email_address and password.
6
6
  # Returns either the newly created session or nil
7
- def self.authenticate(email, password=nil)
8
- if email.is_a? Hash
9
- password = email[:password]
10
- email = email[:email]
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(:email => email, :password => password)
13
+ Session.create(:email_address => email_address, :password => password)
14
14
  rescue Sunstone::Exception::Unauthorized
15
15
  nil
16
16
  end
@@ -0,0 +1,11 @@
1
+ class Slug < MLS::Model
2
+
3
+ default_scope -> { order(:created_at => :desc) }
4
+
5
+ belongs_to :model, :polymorphic => true
6
+
7
+ def to_param
8
+ slug
9
+ end
10
+
11
+ end
@@ -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
@@ -1,3 +1,5 @@
1
1
  class Space < MLS::Model
2
2
 
3
+ belongs_to :coworking_space
4
+
3
5
  end
@@ -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
@@ -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
@@ -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
@@ -5,7 +5,7 @@ class Use < MLS::Model
5
5
 
6
6
  has_many :children, class_name: 'Use', foreign_key: 'parent_id', inverse_of: :parent
7
7
 
8
- has_and_belongs_to_many :listings
8
+ has_and_belongs_to_many :units
9
9
  # has_and_belongs_to_many :properties
10
10
 
11
11
  def descendants(uses = nil)
@@ -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
@@ -0,0 +1,7 @@
1
+ class Webpage < MLS::Model
2
+
3
+ has_one :task, as: :subject
4
+
5
+ validates :url, presence: true
6
+
7
+ end
@@ -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.0'
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.0
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: 2014-12-04 00:00:00.000000000 Z
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/floorplan.rb
201
- - lib/mls/flyer.rb
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/photo.rb
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.2.2
253
+ rubygems_version: 2.4.5
241
254
  signing_key:
242
255
  specification_version: 4
243
256
  summary: 42Floors MLS Client
@@ -1,11 +0,0 @@
1
- class Floorplan < MLS::Model
2
-
3
- include MLS::Avatar
4
-
5
- has_one :listing
6
-
7
- def url
8
- "http://#{MLS.asset_host}/floorplans/#{file_digest}/compressed/#{file_name}"
9
- end
10
-
11
- end
@@ -1,11 +0,0 @@
1
- class Flyer < MLS::Model
2
-
3
- include MLS::Avatar
4
-
5
- has_one :listing
6
-
7
- def url
8
- "http://#{MLS.asset_host}/flyers/#{file_digest}/compressed/#{file_name}"
9
- end
10
-
11
- end
@@ -1,10 +0,0 @@
1
- class LeadProperty < MLS::Model
2
-
3
- belongs_to :lead
4
- belongs_to :property
5
- belongs_to :creator, class_name: "Account"
6
-
7
- has_many :lead_listings
8
- has_many :listings, through: :lead_listings
9
-
10
- end
@@ -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