Class: Droom::User

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/droom/user.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#defer_confirmationObject

People are often invited into the system in batches or after offline contact. set user.defer_confirmation to a true or call user.defer_confirmation! before saving if you want to create a user account without sending out any messages yet.

When you do want to invite that person, call user.resend_confirmation_token or set the send_confirmation flag on a save.



32
33
34
# File 'app/models/droom/user.rb', line 32

def defer_confirmation
  @defer_confirmation
end

#send_confirmationObject

People are often invited into the system in batches or after offline contact. set user.defer_confirmation to a true or call user.defer_confirmation! before saving if you want to create a user account without sending out any messages yet.

When you do want to invite that person, call user.resend_confirmation_token or set the send_confirmation flag on a save.



32
33
34
# File 'app/models/droom/user.rb', line 32

def send_confirmation
  @send_confirmation
end

Class Method Details

.for_selectionObject

For select box



325
326
327
# File 'app/models/droom/user.rb', line 325

def self.for_selection
  self.published.map{|p| [p.name, p.id] }
end

.vcards_for(users = []) ⇒ Object



450
451
452
# File 'app/models/droom/user.rb', line 450

def self.vcards_for(users=[])
  users.map(&:vcf).join("\n")
end

Instance Method Details

#active_for_authentication?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'app/models/droom/user.rb', line 56

def active_for_authentication?
  true
end

#add_personal_folders(folders = []) ⇒ Object



241
242
243
# File 'app/models/droom/user.rb', line 241

def add_personal_folders(folders=[])
  self.folders << folders if folders
end

#admit_to(groups) ⇒ Object



177
178
179
180
181
182
# File 'app/models/droom/user.rb', line 177

def admit_to(groups)
  groups = [groups].flatten
  groups.each do |group|
    memberships.where(group_id: group.id).first_or_create
  end
end

#as_json_for_coca(options = {}) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'app/models/droom/user.rb', line 146

def as_json_for_coca(options={})
  ensure_uid!
  ensure_authentication_token
  {
    uid: uid,
    authentication_token: authentication_token,
    title: title,
    given_name: given_name,
    family_name: family_name,
    chinese_name: chinese_name,
    email: email,
    permissions: permission_codes.join(','),
    image: thumbnail
  }
end

#as_search_resultObject



313
314
315
316
317
318
319
320
# File 'app/models/droom/user.rb', line 313

def as_search_result
  {
    :type => 'person',
    :prompt => name,
    :value => name,
    :id => id
  }
end

#as_suggestionObject



304
305
306
307
308
309
310
311
# File 'app/models/droom/user.rb', line 304

def as_suggestion
  {
    :type => 'person',
    :prompt => formal_name,
    :value => formal_name,
    :id => id
  }
end

#authenticate_token(token) ⇒ Object

Auth tokens

Are no longer native to devise but we use them for domain-cookie auth.



86
87
88
# File 'app/models/droom/user.rb', line 86

def authenticate_token(token)
  Devise.secure_compare(self.authentication_token, token)
end

#clear_session_id!Object



78
79
80
# File 'app/models/droom/user.rb', line 78

def clear_session_id!
  self.update_column(:session_id, "")
end

#colloquial_nameObject

This is our best shot at a representation of the usual third person form of this person’s name. It combines the informal name (which includes some logic to show chinese, anglo and mixed names correctly) with the title, if the title is not ordinary.



409
410
411
# File 'app/models/droom/user.rb', line 409

def colloquial_name
  [title_if_it_matters, informal_name, honours].compact.join(' ')
end

#confirmed=(value) ⇒ Object



103
104
105
# File 'app/models/droom/user.rb', line 103

def confirmed=(value)
  self.confirmed_at = Time.now if value.present? and value != "false"
end

#data_room_user?Boolean

Returns:

  • (Boolean)


530
531
532
# File 'app/models/droom/user.rb', line 530

def data_room_user?
  admin? || permitted?('droom.login')
end

#database_authenticatableObject

Authentication



13
14
15
16
17
18
19
# File 'app/models/droom/user.rb', line 13

devise :database_authenticatable,
:cookie_authenticatable,
:recoverable,
:trackable,
:confirmable,
:rememberable,
:reconfirmable => false

#defer_confirmation!Object



34
35
36
# File 'app/models/droom/user.rb', line 34

def defer_confirmation!
  self.defer_confirmation = true
end

#defer_confirmation?Boolean

Returns:

  • (Boolean)


38
39
40
# File 'app/models/droom/user.rb', line 38

def defer_confirmation?
  !!self.defer_confirmation
end

#documentsObject



253
254
255
# File 'app/models/droom/user.rb', line 253

def documents
  Document.visible_to(self)
end

#dropbox_clientObject



270
271
272
# File 'app/models/droom/user.rb', line 270

def dropbox_client
  dropbox_token.dropbox_client if dropbox_token
end

#dropbox_tokenObject



263
264
265
266
267
268
# File 'app/models/droom/user.rb', line 263

def dropbox_token
  unless @dropbox_token
    @dropbox_token = dropbox_tokens.by_date.last || 'nope'
  end
  @dropbox_token unless @dropbox_token == 'nope'
end

#dropbox_tokensObject

Dropbox links



260
# File 'app/models/droom/user.rb', line 260

has_many :dropbox_tokens, :foreign_key => "created_by_id"

#ensure_authentication_tokenObject



97
98
99
100
101
# File 'app/models/droom/user.rb', line 97

def ensure_authentication_token
  if authentication_token.blank?
    self.authentication_token = generate_authentication_token
  end
end

#expel_from(group) ⇒ Object



184
185
186
# File 'app/models/droom/user.rb', line 184

def expel_from(group)
  memberships.of_group(group).destroy_all
end

#formal_nameObject

This shuold be a reasonable formal first-person form of address.



401
402
403
# File 'app/models/droom/user.rb', line 401

def formal_name
  [title, family_name].join(' ')
end

#full_nameObject

### Completeness

For record-keeping purposes we show the whole name: Chan Tai Wan, Jimmy.



417
418
419
# File 'app/models/droom/user.rb', line 417

def full_name
  [family_name, given_name].compact.join(' ')
end

#has_folder?(folder) ⇒ Boolean

Returns:

  • (Boolean)


249
250
251
# File 'app/models/droom/user.rb', line 249

def has_folder?(folder)
  folder && personal_folders.of_folder(folder).any?
end

#iconObject



290
291
292
# File 'app/models/droom/user.rb', line 290

def icon
  image.url(:icon) if image?
end

#imageObject

Mugshot



276
277
278
279
280
281
282
# File 'app/models/droom/user.rb', line 276

has_attached_file :image,
:default_url => ActionController::Base.helpers.image_path("droom/missing/:style.png"),
:styles => {
  :standard => "520x520#",
  :icon => "32x32#",
  :thumb => "130x130#"
}

#informal_nameObject

Names

With Anglo-Chinese Hong Kong names it is difficult to be sure of the right presentation.

We hold the name in three fields: title, given name and family name. People with both a Chinese and an English forename are encouraged to enter their given name in the form Tai Wan, Jimmy. The family_name should always be a single, usually Chinese, surname: Chan or Smith.

When a comma is found in the given name, we assume that they have followed the chinese, english format. If not, we assume the whole name is Chinese.

### Polite informality

People with an English forename would normally be addressed as Jimmy Chan. People with only a Chinese forename should be addressed as Chan Tai Wan.



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'app/models/droom/user.rb', line 346

def informal_name
  # The standard form of the given name is Tai Wan, Ray
  chinese, english = given_name.split(/,\s*/)
  # But some people are known only as Ray.
  # Here we can't tell the difference between people with one chinese given name and one anglo given name
  # but the order of names is reversed in the latter case. For now we assume that the presence of a chinese
  # name indicates that the chinese word ordering should be used.
  unless chinese_name?
    english ||= chinese.strip.split(/\s+/).first
  end
  if english
    # People with an english name are called Ray Chan, by default
    [english, family_name].join(' ')
  else
    # People without are called Chan Tai Wan
    [family_name, chinese].join(' ')
  end
end

#invitation_to(event) ⇒ Object



220
221
222
# File 'app/models/droom/user.rb', line 220

def invitation_to(event)
  invitations.to_event(event).first
end

#invitationsObject

Event invitations



205
# File 'app/models/droom/user.rb', line 205

has_many :invitations, :dependent => :destroy

#invite_to(event) ⇒ Object



208
209
210
# File 'app/models/droom/user.rb', line 208

def invite_to(event)
  invitations.where(event_id: event.id).first_or_create if event
end

#invited_to?(event) ⇒ Boolean

Returns:

  • (Boolean)


216
217
218
# File 'app/models/droom/user.rb', line 216

def invited_to?(event)
  event && !!invitation_to(event)
end

#member_of?(group) ⇒ Boolean

Returns:

  • (Boolean)


188
189
190
# File 'app/models/droom/user.rb', line 188

def member_of?(group)
  group && memberships.of_group(group).any?
end

#membership_of(group) ⇒ Object



192
193
194
# File 'app/models/droom/user.rb', line 192

def membership_of(group)
  memberships.find_by(group_id: group.id)
end

#membershipsObject

Group memberships



169
# File 'app/models/droom/user.rb', line 169

has_many :memberships, :dependent => :destroy

#nameObject



365
366
367
368
369
370
371
372
373
374
375
# File 'app/models/droom/user.rb', line 365

def name
  chinese, english = given_name.split(/,\s*/)
  unless chinese_name?
    english ||= chinese.split(/\s+/).first
  end
  if english
    [english, family_name].join(' ')
  else
    [family_name, chinese].join(' ')
  end
end

#official_nameObject

### Compatibility

An HKID card will normally show only the translitered Chinese name: Chan Tai Wan



425
426
427
428
# File 'app/models/droom/user.rb', line 425

def official_name
  chinese, english = given_name.split(/,\s*/)
  [family_name, chinese].join(' ')
end

#organisationObject

Organisation affiliation



164
# File 'app/models/droom/user.rb', line 164

belongs_to :organisation

#password_match?Boolean

Returns:

  • (Boolean)


107
108
109
110
111
112
# File 'app/models/droom/user.rb', line 107

def password_match?
  self.errors[:password] << "can't be blank" if password.blank?
  self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
  self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
  password == password_confirmation && !password.blank?
end

#password_required?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'app/models/droom/user.rb', line 60

def password_required?
  confirmed? && (!password.blank?)
end

#password_set?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'app/models/droom/user.rb', line 64

def password_set?
  encrypted_password?
end

#permission_codesObject



514
515
516
# File 'app/models/droom/user.rb', line 514

def permission_codes
  permissions.map(&:slug).compact.uniq
end

#permission_codes=(codes) ⇒ Object



518
519
520
# File 'app/models/droom/user.rb', line 518

def permission_codes=(codes)
  #TODO
end

#permissions_elsewhere?Boolean

Returns:

  • (Boolean)


526
527
528
# File 'app/models/droom/user.rb', line 526

def permissions_elsewhere?
  permission_codes.select{|pc| pc !~ /droom/}.any?
end

#permitted?(key) ⇒ Boolean

Returns:

  • (Boolean)


522
523
524
# File 'app/models/droom/user.rb', line 522

def permitted?(key)
  permission_codes.include?(key)
end

#personal_foldersObject

Folder permissions

To simplify the business of showing and listing documents, we have adopted the convention that all documents live in a folder. User accounts have links to those folders through the very thin PersonalFolder joining class, and at the view level we only ever show folder and subfolder lists. The only place we ever need a list of all the documents visible to this person is when searching, and for that we use the Document.visible_to scope, usually by way of the #documents method defined below.

Personal folders are created and destroyed along with invitations and memberships.



238
# File 'app/models/droom/user.rb', line 238

has_many :personal_folders

#pref(key) ⇒ Object

Preferences

User settings are held as an association with Preference objects, which are simple key:value pairs. The keys are usually colon:separated for namespacing purposes, eg:

current_user.pref("email:enabled?")
current_user.pref("dropbox:enabled?")

Default settings are defined in Droom.user_defaults and can be defined in an initializer if the default droom defaults are not right for your application.

‘User#pref(key)` returns the value of the preference (whether set or default) for the given key. It is intended for use in views:

- if current_user.pref("dropbox:enabled?")
  = link_to "copy to dropbox", dropbox_folder_url(folder)


472
473
474
475
476
477
478
# File 'app/models/droom/user.rb', line 472

def pref(key)
  if pref = preferences.find_by(key: key)
    pref.value
  else
    Droom.user_default(key)
  end
end

#pref?(key) ⇒ Boolean

Returns:

  • (Boolean)


480
481
482
# File 'app/models/droom/user.rb', line 480

def pref?(key)
  !!pref(key)
end

#preference(key) ⇒ Object

‘User#preference(key)` always returns a preference object and is used to build control panels. If no preference is saved for the given key, we return a new (unsaved) one with that key and the default value.



487
488
489
490
491
# File 'app/models/droom/user.rb', line 487

def preference(key)
  pref = preferences.find_or_initialize_by_key(key)
  pref.value = Droom.user_default(key) unless pref.persisted?
  pref
end

#remove_personal_folders(folders = []) ⇒ Object



245
246
247
# File 'app/models/droom/user.rb', line 245

def remove_personal_folders(folders=[])
  self.folders.delete(folders) if folders
end

#reset_authentication_token!Object



90
91
92
93
94
95
# File 'app/models/droom/user.rb', line 90

def reset_authentication_token!
  $cache.delete "/api/authenticate/#{authentication_token}" if $cache && authentication_token
  token = generate_authentication_token
  self.update_column(:authentication_token, token)
  token
end

#reset_session_id!Object

Session ID

Allows us to invalidate a session by remote control if someone signs out on a satellite site.



72
73
74
75
76
# File 'app/models/droom/user.rb', line 72

def reset_session_id!
  token = generate_authentication_token
  self.update_column(:session_id, token)
  token
end

#scrapsObject

Other ownership



536
# File 'app/models/droom/user.rb', line 536

has_many :scraps, :foreign_key => "created_by_id"

#send_confirmation?Boolean

Called after save by our own late-confirmation mechanism. If the send_confirmation flag has been set, we confirm.

Returns:

  • (Boolean)


45
46
47
# File 'app/models/droom/user.rb', line 45

def send_confirmation?
  !!self.send_confirmation
end

#send_confirmation_notification?Boolean

Called on create by devise’s immediate confirmation mechanism. If the defer_confirmation flag has been set, we postpone.

Returns:

  • (Boolean)


52
53
54
# File 'app/models/droom/user.rb', line 52

def send_confirmation_notification?
  super && !defer_confirmation?
end

#set_pref(key, value) ⇒ Object

Setting preferences is normally handled either by the PreferencesController or by nesting preferences in a user form. ‘User#set_pref` is a convenient console method but not otherwise used much.

Preferences are set in a simple key:value way, where key usually includes some namespacing prefixes:

user.set_pref("email:enabled", true)


500
501
502
# File 'app/models/droom/user.rb', line 500

def set_pref(key, value)
  preferences.where(key: key).first_or_create.set(value)
end

#thumbnailObject



286
287
288
# File 'app/models/droom/user.rb', line 286

def thumbnail
  image.url(:thumb) if image?
end

#titleObject



391
392
393
394
395
396
397
# File 'app/models/droom/user.rb', line 391

def title
  title = read_attribute(:title)
  if title.blank?
    title = (gender == 'f') ? 'Ms' : 'Mr'
  end
  title
end

#title_if_it_mattersObject



387
388
389
# File 'app/models/droom/user.rb', line 387

def title_if_it_matters
  title unless title_ordinary?
end

#title_ordinary?Boolean

### Formality

The family name is held separately becaose for most purposes we will address people using the relatively reliable ‘Dr Chan’ or ‘Mr Smith’.

Returns:

  • (Boolean)


383
384
385
# File 'app/models/droom/user.rb', line 383

def title_ordinary?
  ['Mr', 'Ms', 'Mrs', '', nil].include?(title)
end

#to_vcfObject



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'app/models/droom/user.rb', line 430

def to_vcf
  @vcard ||= Vcard::Vcard::Maker.make2 do |maker|
    maker.add_name do |n|
      n.given = name || ""
    end
    maker.add_addr {|a| 
      a.location = 'home' # until we do this properly with multiple contact sets
      a.country = post_country || ""
      a.region = post_region || ""
      a.locality = post_city || ""
      a.street = "#{post_line1}, #{post_line2}"
      a.postalcode = post_code || ""
    }
    maker.add_tel phone { |t| t.location = 'home' } unless phone.blank?
    maker.add_tel mobile { |t| t.location = 'cell' } unless mobile.blank?
    maker.add_email email { |e| t.location = 'home' }
  end
  @vcard.to_s
end

#uninvite_from(event) ⇒ Object



212
213
214
# File 'app/models/droom/user.rb', line 212

def uninvite_from(event)
  invitations.to_event(event).destroy_all
end

#user_permissionsObject

Permissions

Permissions are usually assigned by way of group membership, but the effect of this is to create a user-permission object. Additional user-permission objects can be created: all we need to do here is return that set.



511
# File 'app/models/droom/user.rb', line 511

has_many :user_permissions

#valid_password?(password) ⇒ Boolean

Our old user accounts store passwords as salted sha512 digests. Current best practice uses BCrypt so we migrate user accounts across in this rescue block whenever we hear BCrypt grumbling about the old hash.

Returns:

  • (Boolean)


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'app/models/droom/user.rb', line 117

def valid_password?(password)
  begin
    super(password)
  rescue BCrypt::Errors::InvalidHash
    Rails.logger.warn "...trying sha512 on password input"
    stretches = 10
    salt = self.password_salt
    pepper = nil
    old_digest = Devise::Encryptable::Encryptors::Sha512.digest(password, stretches, salt, pepper)
    if old_digest == self.encrypted_password   
      self.password = password
      self.save
      return true
    else
      # Doesn't match the old format either: password is just wrong.
      return false
    end
  end
end