/
member.rb
323 lines (287 loc) · 11.8 KB
/
member.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# == Schema Information
#
# Table name: members
#
# id :integer not null, primary key
# last_name :string(255)
# first_name :string(255)
# middle_name :string(255)
# name :string(255)
# country_id :integer
# emergency_contact_phone :string(255)
# emergency_contact_email :string(255)
# emergency_contact_name :string(255)
# phone_1 :string(255)
# phone_2 :string(255)
# email_1 :string(255)
# email_2 :string(255)
# location_id :integer
# location_detail :string(255)
# arrival_date :date
# departure_date :date
# receive_sms :boolean
# receive_email :boolean
# blood_donor :boolean
# bloodtype_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# phone_private :boolean
# email_private :boolean
# in_country :boolean
# comments :string(255)
# short_name :string(255)
# wife_id :integer
# sex :string(255)
#
############## JOSLINK ###################
class Member < ActiveRecord::Base
include NameHelper
require 'sessions_helper'
include SessionsHelper
include ApplicationHelper
extend ExportHelper
attr_accessible :arrival_date, :departure_date, :email_1, :email_2, :name, :first_name, :last_name, :middle_name,
:short_name, :location_detail, :location_id, :phone_1, :phone_2,
:receive_email, :receive_sms, :emergency_contact_phone, :emergency_contact_email, :emergency_contact_name,
:country_id, :blood_donor, :bloodtype_id, :bloodtype, :groups, :group_ids,
:in_country, :comments, :phone_private, :email_private, :country, :location, :wife, :wife_id
attr_accessor :role_cache_duration
has_and_belongs_to_many :groups
has_one :husband, :class_name => 'Member', :foreign_key => :wife_id
belongs_to :wife, :class_name => 'Member'
has_many :sent_messages
has_many :messages, :through => :sent_messages
has_many :authorizations
belongs_to :country
belongs_to :location
belongs_to :bloodtype
validates_uniqueness_of :name
validates_presence_of :name
before_save :format_phone_numbers
def initialize(*args)
@role_cache_duration = 60 # seconds
super
end
# *************** Class methods *************
# def self.authorized_for_create?
# false # or some test for current user
# end
def self.auto_update_all_in_country_statuses
do_updates = [1, '1', 'Yes', 'yes', true].include? SiteSetting.auto_update_in_country_status
self.all.each {|m| m.auto_update_in_country_status(do_updates)}
end
def self.find_with_name(name, conditions="true")
#puts "Find_with_name #{name}"
return [] if name.blank?
filtered = self.where(conditions)
result = filtered.where("first_name LIKE ?", name+"%") + filtered.where("last_name LIKE ?", name+"%") +
filtered.where("name LIKE ?", name+"%")
if name =~ /(.*),\s*(.*)/
last_name, first_name = $1, $2
elsif name =~ /(.*)\s(.*)/
last_name, first_name = $2, $1
else
last_name = first_name = nil
end
if last_name && first_name
result += filtered.where("last_name LIKE ? AND ((first_name LIKE ?) )",
last_name+"%", first_name+"%")
end
return result.uniq.compact
end
# Use a string like "Al Wright 0803-388-8888" to update Al Wright's data
# Return {:member => <some member>, updates => <update attribute hash>}
def self.parse_update_command(str)
s = str.gsub(/<mailto.*>/,'')
tokens = s.split
names = []
phones = []
emails = []
groups = []
updates = {}
tokens.each do |token|
case
when phone = token.phone_std
phones << phone
when token =~ /\A[^@ ]+@[^@ ]+\.[^@ ]+\Z/ # Very broad email address validator
emails << token
else
names << token if (phones + emails).empty?
end
end
(0..1).each do |i|
updates["phone_#{i+1}".to_sym] = phones[i] if phones[i]
updates["email_#{i+1}".to_sym] = emails[i] if emails[i]
end
name_string = names.join(' ')
member = Member.find_with_name(name_string) # may match none, one, or many members
return add_member_params(name_string, updates) if member.empty? # assume it's for adding a new member
#puts "**** member=#{member}, updates=#{updates}"
return {:members => member, :updates => updates}
end
def self.add_member_params(name, updates)
{:members => [], :updates => parse_namestring(name).merge(updates)}
end
def self.find_by_phone(phone_number)
target_phone = (phone_number[0] == '+' ? phone_number[1..20] : phone_number)
Member.where("phone_1 = ? OR phone_2 = ?",target_phone, target_phone).readonly(false).all
end
def self.find_by_email(email)
Member.where("email_1 = ? OR email_2 = ?", email, email).readonly(false).all
end
# This is a stub that can be filled in later to limit outgoing messages to people who are still
# in the country, on location, active, or whatever. Or we could simply define another column for
# members, since we don't plan to do travel-tracking in this version
def self.those_in_country
return self.where(:in_country => true)
end
#******** INSTANCE METHODS **************
def <=>(other)
self.name <=> other.name
end
# This stub helps bridge from the larger program that uses separate contact records. It would be best for clarity to change
# all "member.primary_contact." to "member" but this accomplishes the same thing.
def primary_contact
self
end
# Generate hash of contact info ready for display;
# * join multiple phones and emails
# * add "private" notice if needed
# e.g. {'Phone' => '0803-385-4175, 0816-297-4144 (private)', 'Email' => 'me@lemon.com'}
# {'Phone' => '*private*', 'Email' => 'me@dog.org'}
def contact_summary(params={})
phones = smart_join([phone_1, phone_2].map {|p| p.phone_format if p}, ", ")
emails = smart_join([email_1, email_2], ', ')
override_private = params[:override_private]
return {'Phone' => filter_private(phones, phone_private, override_private),
'Email' => filter_private(emails, email_private, override_private)
}
end
def contact_summary_text(params={})
prefix = params[:prefix] || ''
separator = params[:separator] || "\n"
include_blanks = params[:include_blanks]
fields = ['Phone', 'Email']
summary_hash = self.contact_summary
summary = fields.map do |f|
content = summary_hash[f]
"#{f}: #{content}" if (!content.blank? || include_blanks)
end
return prefix + summary.compact.join("#{separator}#{prefix}")
end
def filter_private(field, marked_as_private, override_private)
return field unless marked_as_private
return '*private*' unless override_private
return "#{field} (private)"
end
def country_name
Country.find(country_id).name if country_id
end
def primary_phone(options={:with_plus => false})
phone = phone_1 || phone_2
phone = phone[1..20] if phone && !options[:with_plus] && phone[0]=='+'
return phone
end
# Use the Departure and Arrival dates to calculate whether person is in country.
def calculate_in_country_status
today = Date.today
arr = arrival_date
dep = departure_date
original_status = [in_country, dep, arr]
new_status = case
when dep && arr && dep < arr # dates mark period when person will be out of country
case
when today > arr then [true, nil, arr] # person has already arrived
when today >= dep then [false, dep, arr] # person has departed and not arrived
else original_status # not yet departed; in_country should be true, but we'll leave it alone
end
when dep && arr && dep >= arr # dates mark period when person will be IN the country
case
when today > dep then [false, nil, nil] # person has already departed
when today >= arr then [true, dep, arr] # person has arrived and not departed
else original_status # not yet arrived; in_country should be false, but we'll leave it alone
end
when dep && arr.nil? # date when person will leave the country
today > dep ? [false, dep, nil] : original_status
when dep.nil? && arr # date when person will arrive in the country
today >= arr ? [true, nil, arr] : original_status
else
original_status
end
logger.info "**** #{self.shorter_name}:\t#{original_status[0]}=>#{new_status[0]}\t#{original_status[1]}=>#{new_status[1]}\t#{original_status[2]}=>#{new_status[2]}" unless new_status == original_status
AppLog.create(:code => 'Member.update', :description =>
"**** #{self.shorter_name}:\t#{original_status[0]}=>#{new_status[0]}\t#{original_status[1]}=>#{new_status[1]}\t#{original_status[2]}=>#{new_status[2]}") unless new_status == original_status
return new_status
end
def auto_update_in_country_status(do_updates)
self.in_country, self.departure_date, self_arrival_date = calculate_in_country_status
self.save if do_updates
end
def primary_email(options={})
return email_1 || email_2
end
# ToDo: The whole area of privileges/roles needs to be reworked -- getting cumbersome
# This is probably redundant and can just be replace with the direct calls
# def has_role?(role)
# case role
# when :administrator then return administrator?(self)
# when :moderator then return moderator?(self)
# when :member then return member?(self)
# when :limited then return limited?(self)
# end
# return nil
# end
# Use this member's groups to find the highest assigned role
# ToDo: This duplicates sessions_helper's highest_role(groups) method ... refactor to remove that one.
def recalc_highest_role
admin = mod = memb = limited = nil
#puts "**** Member.recalc_highest_role self.groups=#{self.groups}"
self.groups.each do |g|
admin ||= g.administrator
mod ||= g.moderator
memb ||= g.member
limited ||= g.limited
end
return :administrator if admin
return :moderator if mod
return :member if memb
return :limited if limited
return nil
end
def role
userkey = "user:#{self.id}"
# puts "**** Member.role $redis.hget(userkey, :role)=#{$redis.hget(userkey, :role)}"
unless myrole = $redis.hget(userkey, :role) # This is INTENTIONALLY an assignment, not a "==" comparison
myrole = recalc_highest_role
$redis.hset(userkey, :role, myrole) # Cache role so we don't have to check it a zillion times from the DB
$redis.expire(userkey, @role_cache_duration) # keep cached for 60 seconds
#puts "**** caching role for @role_cache_duration=#{@role_cache_duration} sec"
end
#puts "**** Member.role: user=[#{self.id}] #{self}, role=#{myrole}"
return myrole.nil? ? nil : myrole.downcase.to_sym
end
def roles_include?(queried_role)
return nil if queried_role.nil?
role_hierarchy = [:limited, :member, :moderator, :administrator]
self_index = role_hierarchy.index(self.role)
queried_index = role_hierarchy.index(queried_role)
return self_index && queried_index && (self_index >= queried_index)
end
def add_authorization_provider(auth_hash)
# Check if the provider already exists, so we don't add it twice
unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
authorizations.create :provider => auth_hash["provider"], :uid => auth_hash["uid"]
end
end
def format_phone_numbers
self.phone_1, self.phone_2 = std_phone(phone_1), std_phone(phone_2)
end
#Not working
# def authorized_for_read?
# false
# end
# def phone_1_authorized_for_read?
# $current_user.roles_include? :moderator
# false
# end
end