mls 1.4.0 → 1.4.3

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