# frozen_string_literal: true
# == Schema Information
#
# Table name: orgs
#
# id :integer not null, primary key
# name :string
# abbreviation :string
# target_url :string
# created_at :datetime not null
# updated_at :datetime not null
# is_other :boolean default("false"), not null
# sort_name :string
# banner_text :text
# region_id :integer
# language_id :integer
# logo_uid :string
# logo_name :string
# contact_email :string
# org_type :integer default("0"), not null
# links :text
# contact_name :string
# feedback_enabled :boolean default("false")
# feedback_email_subject :string
# feedback_email_msg :text
# active :boolean default("true")
#
# Indexes
#
# orgs_language_id_idx (language_id)
# orgs_region_id_idx (region_id)
#
class Org < ActiveRecord::Base
include ValidationMessages
include ValidationValues
include FeedbacksHelper
include GlobalHelpers
include FlagShihTzu
extend Dragonfly::Model::Validations
validates_with OrgLinksValidator
LOGO_FORMATS = %w[jpeg png gif jpg bmp].freeze
HUMANIZED_ATTRIBUTES = {
feedback_email_msg: _('Feedback email message')
}
# Stores links as an JSON object:
# { org: [{"link":"www.example.com","text":"foo"}, ...] }
# The links are validated against custom validator allocated at
# validators/template_links_validator.rb
serialize :links, JSON
# ================
# = Associations =
# ================
belongs_to :language
belongs_to :region
has_many :guidance_groups, dependent: :destroy
has_many :templates
has_many :users
has_many :annotations
has_and_belongs_to_many :token_permission_types,
join_table: "org_token_permissions",
unique: true
has_many :org_identifiers
has_many :identifier_schemes, through: :org_identifiers
has_many :departments
has_many :madmp_schemas
# ===============
# = Validations =
# ===============
validates :name, presence: { message: PRESENCE_MESSAGE },
uniqueness: { message: UNIQUENESS_MESSAGE }
validates :abbreviation, presence: { message: PRESENCE_MESSAGE }
validates :is_other, inclusion: { in: BOOLEAN_VALUES,
message: INCLUSION_MESSAGE }
validates :language, presence: { message: PRESENCE_MESSAGE }
validates :contact_name, presence: { message: PRESENCE_MESSAGE,
if: :feedback_enabled }
validates :contact_email, email: { allow_nil: true },
presence: { message: PRESENCE_MESSAGE,
if: :feedback_enabled }
validates :org_type, presence: { message: PRESENCE_MESSAGE }
validates :feedback_enabled, inclusion: { in: BOOLEAN_VALUES,
message: INCLUSION_MESSAGE }
validates :feedback_email_subject, presence: { message: PRESENCE_MESSAGE,
if: :feedback_enabled }
validates :feedback_email_msg, presence: { message: PRESENCE_MESSAGE,
if: :feedback_enabled }
validates_property :format, of: :logo, in: LOGO_FORMATS,
message: _("must be one of the following formats: " +
"jpeg, jpg, png, gif, bmp")
validates_size_of :logo,
maximum: 500.kilobytes,
message: _("can't be larger than 500KB")
# allow validations for logo upload
dragonfly_accessor :logo do
after_assign :resize_image
end
##
# Define Bit Field values
# Column org_type
has_flags 1 => :institution,
2 => :funder,
3 => :organisation,
4 => :research_institute,
5 => :project,
6 => :school,
column: "org_type"
# Predefined queries for retrieving the managain organisation and funders
scope :managing_orgs, -> do
where(abbreviation: Branding.fetch(:organisation, :abbreviation))
end
scope :search, -> (term) {
search_pattern = "%#{term}%"
where("lower(orgs.name) LIKE lower(?) OR " +
"lower(orgs.contact_email) LIKE lower(?)",
search_pattern, search_pattern)
}
# Scope used in several controllers
scope :with_template_and_user_counts, -> {
joins("LEFT OUTER JOIN templates ON orgs.id = templates.org_id")
.joins("LEFT OUTER JOIN users ON orgs.id = users.org_id")
.group("orgs.id")
.select("orgs.*,
count(distinct templates.family_id) as template_count,
count(users.id) as user_count")
}
before_validation :set_default_feedback_email_subject
before_validation :check_for_missing_logo_file
after_create :create_guidance_group
# EVALUATE CLASS AND INSTANCE METHODS BELOW
#
# What do they do? do they do it efficiently, and do we need them?
# Update humanized attributes with HUMANIZED_ATTRIBUTES
def self.human_attribute_name(attr, options = {})
HUMANIZED_ATTRIBUTES[attr.to_sym] || super
end
# Determines the locale set for the organisation
#
# Returns String
# Returns nil
def get_locale
if !self.language.nil?
self.language.abbreviation
else
nil
end
end
# TODO: Should these be hardcoded? Also, an Org can currently be multiple org_types at
# one time. For example you can do: funder = true; project = true; school = true
#
# Calling type in the above scenario returns "Funder" which is a bit misleading
# Is FlagShihTzu's Bit flag the appropriate structure here or should we use an enum?
# Tests are setup currently to work with this issue.
#
# Returns String
def org_type_to_s
ret = []
ret << "Institution" if self.institution?
ret << "Funder" if self.funder?
ret << "Organisation" if self.organisation?
ret << "Research Institute" if self.research_institute?
ret << "Project" if self.project?
ret << "School" if self.school?
(ret.length > 0 ? ret.join(", ") : "None")
end
def funder_only?
self.org_type == Org.org_type_values_for(:funder).min
end
##
# The name of the organisation
#
# Returns String
def to_s
name
end
##
# The abbreviation for the organisation if it exists, or the name if not
#
# Returns String
def short_name
if abbreviation.nil? then
name
else
abbreviation
end
end
##
# All published templates belonging to the organisation
#
# Returns ActiveRecord::Relation
def published_templates
templates.where("published = ?", true)
end
def org_admins
User.joins(:perms).where("users.org_id = ? AND perms.name IN (?)", self.id,
["grant_permissions", "modify_templates", "modify_guidance", "change_org_details"])
end
def plans
plan_ids = Role.administrator
.where(user_id: self.users.pluck(:id), active: true)
.pluck(:plan_id).uniq
Plan.includes(:template, :phases, :roles, :users)
.where(id: plan_ids)
end
def grant_api!(token_permission_type)
self.token_permission_types << token_permission_type unless
self.token_permission_types.include? token_permission_type
end
private
##
# checks size of logo and resizes if necessary
#
def resize_image
unless logo.nil?
if logo.height != 100
self.logo = logo.thumb("x100") # resize height and maintain aspect ratio
end
end
end
# If the physical logo file is no longer on disk we do not want it to prevent the
# model from saving. This typically happens when you copy the database to another
# environment. The orgs.logo_uid stores the path to the physical logo file that is
# stored in the Dragonfly data store (default is: public/system/dragonfly/[env]/)
def check_for_missing_logo_file
if self.logo_uid.present?
data_store_path = Dragonfly.app.datastore.root_path
if !File.exist?("#{data_store_path}#{self.logo_uid}")
# Attempt to locate the file by name. If it exists update the uid
logo = Dir.glob("#{data_store_path}/**/*#{self.logo_name}")
if !logo.empty?
self.logo_uid = logo.first.gsub(data_store_path, "")
else
# Otherwise the logo is missing so clear it to prevent save failures
self.logo = nil
end
end
end
end
def set_default_feedback_email_subject
if self.feedback_enabled? && !self.feedback_email_subject.present?
self.feedback_email_subject = feedback_confirmation_default_subject
end
end
# creates a dfefault Guidance Group on create on the Org
def create_guidance_group
GuidanceGroup.create!(
name: abbreviation? ? self.abbreviation : self.name,
org: self,
optional_subset: false,
published: false,
)
end
end