app/models/user.rb
require 'digest/sha2'
class User < CouchRest::Model::Base
use_database :user
include RapidFTR::Model
include RapidFTR::CouchRestRailsBackward
property :full_name
property :user_name
property :verified, TrueClass, :default => true
property :crypted_password
property :salt
property :phone
property :email
property :organisation
property :position
property :location
property :disabled, TrueClass, :default => false
property :mobile_login_history, [MobileLoginEvent]
property :role_ids, :type => [String]
property :time_zone, :default => 'UTC'
property :locale
property :share_contact_info, TrueClass, :default => false
attr_accessor :password_confirmation, :password
ADMIN_ASSIGNABLE_ATTRIBUTES = [:role_ids]
timestamps!
design do
view :by_user_name
view :by_full_name,
:map => "function(doc) {
if ((doc['couchrest-type'] == 'User') && doc['full_name'])
{
emit(doc['full_name'],doc);
}
}"
view :by_user_name_filter_view,
:map => "function(doc) {
if ((doc['couchrest-type'] == 'User') && doc['user_name'])
{
emit(['all',doc['user_name']],doc);
if(doc['disabled'] == 'false' || doc['disabled'] == false)
emit(['active',doc['user_name']],doc);
}
}"
view :by_full_name_filter_view,
:map => "function(doc) {
if ((doc['couchrest-type'] == 'User') && doc['full_name'])
{
emit(['all',doc['full_name']],doc);
if(doc['disabled'] == 'false' || doc['disabled'] == false)
emit(['active',doc['full_name']],doc);
}
}"
view :by_unverified,
:map => "function(doc) {
if (doc['couchrest-type'] == 'User' && (doc['verified'] == false || doc['verified'] == 'false'))
{
emit(doc);
}
}"
view :by_share_contact_info,
:map => "function(doc) {
if (doc['couchrest-type'] == 'User'
&& doc['share_contact_info']
&& doc['verified']
&& !doc['disabled']) {
emit(doc);
}
}"
end
before_save :make_user_name_lowercase, :encrypt_password
after_save :save_devices
before_update :if => :disabled? do |user|
Session.delete_for user
end
validates :full_name, :presence => {:message => I18n.t('errors.models.user.full_name')}
validates :password_confirmation, :presence => {:message => I18n.t('errors.models.user.password_confirmation'), :if => :password_required?}
validates :role_ids, :presence => {:message => I18n.t('errors.models.user.role_ids'), :if => proc { |user| user.verified }}
validates :organisation, :presence => {:message => I18n.t('errors.models.user.organisation')}
validates :user_name, :format => {:with => /\A[^ ]+\z/, :message => I18n.t('errors.models.user.user_name')}
validates :email, :format => {:with => /\A([^@\s]+)@((?:[-a-zA-Z0-9]+\.)+[a-zA-Z]{2,})$\z/,
:if => :email_entered?,
:message => I18n.t('errors.models.user.email')}
validates :password, :confirmation => {:if => :password_required? && :password_confirmation_entered?,
:message => I18n.t('errors.models.user.password_mismatch')}
# FIXME: 409s randomly...destroying user records before test as a temp
validate :unique_user_name
before_save :generate_id
@current_user = nil
class << self
attr_accessor :current_user
end
# In order to track changes on attributes declared as attr_accessor and
# trigger the callbacks we need to use attribute_will_change! method.
# check lib/couchrest/model/extended_attachments.rb in source code.
# So, override the method for password in order to track changes.
def password=(value)
attribute_will_change!('password') if use_dirty? && @password != value
@password = value
end
attr_reader :password
def self.all_unverified
User.by_unverified
end
def self.find_by_user_name(user_name)
User.by_user_name(:key => user_name.downcase).first
end
def initialize(args = {}, args1 = {})
self['mobile_login_history'] = []
super args, args1
end
def email_entered?
!email.blank?
end
def unique_user_name
user = User.find_by_user_name(user_name)
return true if user.nil? || id == user.id
errors.add(:user_name, I18n.t('errors.models.user.user_name_uniqueness'))
end
def authenticate(check)
if new?
fail Exception.new, I18n.t('errors.models.user.authenticate')
end
!disabled? && crypted_password == self.class.encrypt(check, salt)
end
def roles
@roles ||= role_ids.map { |id| Role.get(id) }.flatten
end
def has_permission?(permission)
permissions && permissions.include?(permission)
end
def has_any_permission?(*any_of_permissions)
(any_of_permissions.flatten - permissions).count < any_of_permissions.flatten.count
end
def permissions
roles.compact.map(&:permissions).flatten
end
def add_mobile_login_event(imei, mobile_number)
mobile_login_history << MobileLoginEvent.new(:imei => imei, :mobile_number => mobile_number)
if Device.all.none? { |device| device.imei == imei }
device = Device.new(:imei => imei, :blacklisted => false, :user_name => user_name)
device.save!
end
end
def devices
Device.all.select { |device| device.user_name == user_name }
end
def devices=(device_hashes)
all_devices = Device.all
# attr_accessor devices field change.
attribute_will_change!('devices')
@devices = device_hashes.map do |device_hash|
device = all_devices.find { |d| d.imei == device_hash['imei'] }
device.blacklisted = device_hash['blacklisted'] == 'true'
device
end
end
def localize_date(date_time, format = '%d %B %Y at %H:%M (%Z)')
DateTime.parse(date_time).in_time_zone(self[:time_zone]).strftime(format)
end
def has_role_ids?(attributes)
ADMIN_ASSIGNABLE_ATTRIBUTES.any? { |e| attributes.keys.include? e }
end
private
def save_devices
@devices.map(&:save!) if @devices
true
end
def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("--#{Clock.now}--#{user_name}--") if new_record?
self.crypted_password = self.class.encrypt(password, salt)
end
def self.encrypt(password, salt)
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end
def password_required?
crypted_password.blank? || !password.blank? || !password_confirmation.blank?
end
def password_confirmation_entered?
!password_confirmation.blank?
end
def make_user_name_lowercase
user_name.downcase!
end
def generate_id
self['_id'] ||= "user-#{user_name}".parameterize.dasherize
end
end