2016-02-12 23:52:43 +08:00
|
|
|
class Organization < ActiveRecord::Base
|
|
|
|
# Not really MVC-compliant, but we just use it for logger
|
|
|
|
# output in space_taken related functions
|
|
|
|
include ActionView::Helpers::NumberHelper
|
|
|
|
|
2016-09-21 21:54:12 +08:00
|
|
|
auto_strip_attributes :name, :description, nullify: false
|
2016-02-12 23:52:43 +08:00
|
|
|
validates :name,
|
2016-10-05 23:45:20 +08:00
|
|
|
length: { minimum: Constants::NAME_MIN_LENGTH,
|
|
|
|
maximum: Constants::NAME_MAX_LENGTH }
|
|
|
|
validates :description, length: { maximum: Constants::TEXT_MAX_LENGTH }
|
2016-09-16 17:39:37 +08:00
|
|
|
validates :space_taken, presence: true
|
2016-02-12 23:52:43 +08:00
|
|
|
|
|
|
|
belongs_to :created_by, :foreign_key => 'created_by_id', :class_name => 'User'
|
|
|
|
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User'
|
|
|
|
has_many :user_organizations, inverse_of: :organization, dependent: :destroy
|
|
|
|
has_many :users, through: :user_organizations
|
|
|
|
has_many :samples, inverse_of: :organization
|
|
|
|
has_many :sample_groups, inverse_of: :organization
|
|
|
|
has_many :sample_types, inverse_of: :organization
|
|
|
|
has_many :logs, inverse_of: :organization
|
|
|
|
has_many :projects, inverse_of: :organization
|
|
|
|
has_many :custom_fields, inverse_of: :organization
|
2016-07-21 19:11:15 +08:00
|
|
|
has_many :protocols, inverse_of: :organization, dependent: :destroy
|
|
|
|
has_many :protocol_keywords, inverse_of: :organization, dependent: :destroy
|
2016-02-12 23:52:43 +08:00
|
|
|
|
|
|
|
# Based on file's extension opens file (used for importing)
|
|
|
|
def self.open_spreadsheet(file)
|
|
|
|
filename = file.original_filename
|
|
|
|
file_path = file.path
|
|
|
|
|
|
|
|
if file.class == Paperclip::Attachment and file.is_stored_on_s3?
|
|
|
|
fa = file.fetch
|
|
|
|
file_path = fa.path
|
|
|
|
end
|
|
|
|
|
|
|
|
case File.extname(filename)
|
|
|
|
when ".csv" then
|
|
|
|
Roo::CSV.new(file_path, extension: :csv)
|
|
|
|
when ".tdv" then
|
|
|
|
Roo::CSV.new(file_path, nil, :ignore, csv_options: {col_sep: "\t"})
|
|
|
|
when ".txt" then
|
|
|
|
# This assumption is based purely on biologist's habits
|
|
|
|
Roo::CSV.new(file_path, csv_options: {col_sep: "\t"})
|
|
|
|
when ".xls" then
|
|
|
|
Roo::Excel.new(file_path)
|
|
|
|
when ".xlsx" then
|
|
|
|
Roo::Excelx.new(file_path)
|
|
|
|
else
|
|
|
|
raise TypeError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-09-16 17:39:37 +08:00
|
|
|
# Writes to user log
|
2016-02-12 23:52:43 +08:00
|
|
|
def log(message)
|
|
|
|
final = "[%s] %s" % [Time.current.to_s, message]
|
|
|
|
logs.create(message: final)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Imports samples into db
|
|
|
|
# -1 == sample_name,
|
|
|
|
# -2 == sample_type,
|
|
|
|
# -3 == sample_group
|
|
|
|
# TODO: use constants
|
|
|
|
def import_samples(sheet, mappings, user)
|
|
|
|
errors = []
|
|
|
|
nr_of_added = 0
|
|
|
|
total_nr = 0
|
|
|
|
|
|
|
|
# First let's query for all custom_fields we're refering to
|
|
|
|
custom_fields = []
|
|
|
|
sname_index = -1
|
|
|
|
stype_index = -1
|
|
|
|
sgroup_index = -1
|
|
|
|
mappings.each.with_index do |(k, v), i|
|
|
|
|
if v == "-1"
|
|
|
|
# Fill blank space, so our indices stay the same
|
|
|
|
custom_fields << nil
|
|
|
|
sname_index = i
|
|
|
|
elsif v == "-2"
|
|
|
|
custom_fields << nil
|
|
|
|
stype_index = i
|
|
|
|
elsif v == "-3"
|
|
|
|
custom_fields << nil
|
|
|
|
sgroup_index = i
|
|
|
|
else
|
|
|
|
cf = CustomField.find_by_id(v)
|
|
|
|
|
|
|
|
# Even if doesn't exist we add nil value in order not to destroy our
|
|
|
|
# indices
|
|
|
|
custom_fields << cf
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Now we can iterate through sample data and save stuff into db
|
|
|
|
(2..sheet.last_row).each do |i|
|
|
|
|
error = []
|
|
|
|
total_nr += 1
|
|
|
|
|
|
|
|
sample = Sample.new(
|
|
|
|
name: sheet.row(i)[sname_index],
|
|
|
|
organization_id: id,
|
|
|
|
user: user
|
|
|
|
)
|
|
|
|
|
|
|
|
if sample.save
|
|
|
|
sheet.row(i).each.with_index do |value, index|
|
|
|
|
# We need to have sample saved before messing with custom fields (they
|
|
|
|
# need sample id)
|
|
|
|
if index == stype_index
|
|
|
|
stype = SampleType.where(name: value, organization_id: id).take();
|
|
|
|
|
|
|
|
if stype
|
|
|
|
sample.sample_type = stype
|
|
|
|
else
|
|
|
|
sample.create_sample_type(
|
|
|
|
name: value,
|
|
|
|
organization_id: id
|
|
|
|
)
|
|
|
|
end
|
|
|
|
sample.save
|
|
|
|
elsif index == sgroup_index
|
|
|
|
sgroup = SampleGroup.where(name: value, organization_id: id).take();
|
|
|
|
|
|
|
|
if sgroup
|
|
|
|
sample.sample_group = sgroup
|
|
|
|
else
|
|
|
|
sample.create_sample_group(
|
|
|
|
name: value,
|
|
|
|
organization_id: id
|
|
|
|
)
|
|
|
|
end
|
|
|
|
sample.save
|
|
|
|
elsif value and mappings[index.to_s].strip.present? and index != sname_index
|
|
|
|
if custom_fields[index]
|
|
|
|
# we're working with CustomField
|
|
|
|
scf = SampleCustomField.new(
|
|
|
|
sample_id: sample.id,
|
|
|
|
custom_field_id: custom_fields[index].id,
|
|
|
|
value: value
|
|
|
|
)
|
|
|
|
|
|
|
|
if !scf.save
|
|
|
|
error << scf.errors.messages
|
|
|
|
end
|
|
|
|
else
|
|
|
|
# This custom_field does not exist
|
|
|
|
error << {"#{mappings[index]}": "Does not exists"}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
error << sample.errors.messages
|
|
|
|
end
|
|
|
|
if error.present?
|
|
|
|
errors << { "#{i}": error}
|
|
|
|
else
|
|
|
|
nr_of_added += 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if errors.count > 0 then
|
|
|
|
return {
|
|
|
|
status: :error,
|
|
|
|
errors: errors,
|
|
|
|
nr_of_added: nr_of_added,
|
|
|
|
total_nr: total_nr
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return {
|
|
|
|
status: :ok,
|
|
|
|
nr_of_added: nr_of_added,
|
|
|
|
total_nr: total_nr
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_csv(samples, headers)
|
|
|
|
require "csv"
|
|
|
|
|
|
|
|
# Parse headers (magic numbers should be refactored - see
|
|
|
|
# sample-datatable.js)
|
|
|
|
header_names = []
|
|
|
|
headers.each do |header|
|
|
|
|
if header == "-1"
|
|
|
|
header_names << I18n.t("samples.table.sample_name")
|
|
|
|
elsif header == "-2"
|
|
|
|
header_names << I18n.t("samples.table.sample_type")
|
|
|
|
elsif header == "-3"
|
|
|
|
header_names << I18n.t("samples.table.sample_group")
|
|
|
|
elsif header == "-4"
|
|
|
|
header_names << I18n.t("samples.table.added_by")
|
|
|
|
elsif header == "-5"
|
|
|
|
header_names << I18n.t("samples.table.added_on")
|
|
|
|
else
|
|
|
|
cf = CustomField.find_by_id(header)
|
|
|
|
|
|
|
|
if cf
|
|
|
|
header_names << cf.name
|
|
|
|
else
|
|
|
|
header_names << nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
CSV.generate do |csv|
|
|
|
|
csv << header_names
|
|
|
|
samples.each do |sample|
|
|
|
|
sample_row = []
|
|
|
|
headers.each do |header|
|
|
|
|
if header == "-1"
|
|
|
|
sample_row << sample.name
|
|
|
|
elsif header == "-2"
|
|
|
|
sample_row << (sample.sample_type.nil? ? I18n.t("samples.table.no_type") : sample.sample_type.name)
|
|
|
|
elsif header == "-3"
|
|
|
|
sample_row << (sample.sample_group.nil? ? I18n.t("samples.table.no_group") : sample.sample_group.name)
|
|
|
|
elsif header == "-4"
|
|
|
|
sample_row << sample.user.full_name
|
|
|
|
elsif header == "-5"
|
|
|
|
sample_row << I18n.l(sample.created_at, format: :full)
|
|
|
|
else
|
|
|
|
scf = SampleCustomField.where(
|
|
|
|
custom_field_id: header,
|
|
|
|
sample_id: sample.id
|
|
|
|
).take
|
|
|
|
|
|
|
|
if scf
|
|
|
|
sample_row << scf.value
|
|
|
|
else
|
|
|
|
sample_row << nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
csv << sample_row
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Get all header fields for samples (used in importing for mappings - dropdowns)
|
|
|
|
def get_available_sample_fields
|
|
|
|
fields = {};
|
|
|
|
|
|
|
|
# First and foremost add sample name
|
|
|
|
fields["-1"] = I18n.t("samples.table.sample_name")
|
|
|
|
fields["-2"] = I18n.t("samples.table.sample_type")
|
|
|
|
fields["-3"] = I18n.t("samples.table.sample_group")
|
|
|
|
|
|
|
|
# Add all other custom fields
|
|
|
|
CustomField.where(organization_id: id).order(:created_at).each do |cf|
|
|
|
|
fields[cf.id] = cf.name
|
|
|
|
end
|
|
|
|
|
|
|
|
fields
|
|
|
|
end
|
|
|
|
|
|
|
|
# (re)calculate the space taken by this organization
|
|
|
|
def calculate_space_taken
|
|
|
|
st = 0
|
|
|
|
projects.includes(
|
2016-08-30 19:06:49 +08:00
|
|
|
experiments: {
|
|
|
|
my_modules: {
|
|
|
|
protocols: { steps: :assets },
|
|
|
|
results: { result_asset: :asset }
|
|
|
|
}
|
|
|
|
}
|
2016-02-12 23:52:43 +08:00
|
|
|
).find_each do |project|
|
2016-07-22 21:36:48 +08:00
|
|
|
project.project_my_modules.find_each do |my_module|
|
2016-07-21 19:11:15 +08:00
|
|
|
my_module.protocol.steps.find_each do |step|
|
2016-02-12 23:52:43 +08:00
|
|
|
step.assets.find_each { |asset| st += asset.estimated_size }
|
|
|
|
end
|
|
|
|
my_module.results.find_each do |result|
|
|
|
|
if result.is_asset then
|
|
|
|
st += result.asset.estimated_size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-08-09 20:26:08 +08:00
|
|
|
# project.experiments.each |experiment|
|
2016-10-05 23:45:20 +08:00
|
|
|
self.space_taken = [st, Constants::MINIMAL_ORGANIZATION_SPACE_TAKEN].max
|
2016-02-12 23:52:43 +08:00
|
|
|
Rails::logger.info "Organization #{self.id}: " +
|
|
|
|
"space (re)calculated to: " +
|
|
|
|
"#{self.space_taken}B (#{number_to_human_size(self.space_taken)})"
|
|
|
|
end
|
|
|
|
|
|
|
|
# Take specified amount of bytes
|
|
|
|
def take_space(space)
|
|
|
|
orig_space = self.space_taken
|
|
|
|
self.space_taken += space
|
|
|
|
Rails::logger.info "Organization #{self.id}: " +
|
|
|
|
"space taken: " +
|
|
|
|
"#{orig_space}B + #{space}B = " +
|
|
|
|
"#{self.space_taken}B (#{number_to_human_size(self.space_taken)})"
|
|
|
|
end
|
|
|
|
|
|
|
|
# Release specified amount of bytes
|
|
|
|
def release_space(space)
|
|
|
|
orig_space = self.space_taken
|
2016-10-05 23:45:20 +08:00
|
|
|
self.space_taken = [space_taken - space,
|
|
|
|
Constants::MINIMAL_ORGANIZATION_SPACE_TAKEN].max
|
2016-02-12 23:52:43 +08:00
|
|
|
Rails::logger.info "Organization #{self.id}: " +
|
|
|
|
"space released: " +
|
|
|
|
"#{orig_space}B - #{space}B = " +
|
|
|
|
"#{self.space_taken}B (#{number_to_human_size(self.space_taken)})"
|
|
|
|
end
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
def protocol_keywords_list
|
|
|
|
ProtocolKeyword.where(organization: self).pluck(:name)
|
|
|
|
end
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|