app/models/trade_restriction.rb
# == Schema Information
#
# Table name: trade_restrictions
#
# id :integer not null, primary key
# is_current :boolean default(TRUE)
# start_date :datetime
# end_date :datetime
# geo_entity_id :integer
# quota :float
# publication_date :datetime
# notes :text
# type :string(255)
# unit_id :integer
# taxon_concept_id :integer
# public_display :boolean default(TRUE)
# url :text
# created_at :datetime not null
# updated_at :datetime not null
# start_notification_id :integer
# end_notification_id :integer
# excluded_taxon_concepts_ids :string
# original_id :integer
# updated_by_id :integer
# created_by_id :integer
# internal_notes :text
# nomenclature_note_en :text
# nomenclature_note_es :text
# nomenclature_note_fr :text
# applies_to_import :boolean default(FALSE), not null
#
require 'digest/sha1'
require 'csv'
class TradeRestriction < ApplicationRecord
extend Mobility
include TrackWhoDoesIt
# Migrated to controller (Strong Parameters)
# attr_accessible :end_date, :geo_entity_id, :is_current,
# :notes, :publication_date, :purpose_ids, :quota, :type,
# :source_ids, :start_date, :term_ids, :unit_id, :internal_notes,
# :nomenclature_note_en, :nomenclature_note_es, :nomenclature_note_fr,
# :created_by_id, :updated_by_id, :url,
# :taxon_concept_id
belongs_to :taxon_concept, optional: true
belongs_to :m_taxon_concept, :foreign_key => :taxon_concept_id, optional: true
belongs_to :unit, :class_name => 'TradeCode', optional: true
has_many :trade_restriction_terms, :dependent => :destroy
has_many :terms, :through => :trade_restriction_terms
has_many :trade_restriction_sources, :dependent => :destroy
has_many :sources, :through => :trade_restriction_sources
has_many :trade_restriction_purposes, :dependent => :destroy
has_many :purposes, :through => :trade_restriction_purposes
belongs_to :geo_entity, optional: true
validates :publication_date, :presence => true
validate :valid_dates
translates :nomenclature_note
after_save :touch_descendants, unless: -> { taxon_concept.nil? }
before_destroy :touch_descendants, unless: -> { taxon_concept.nil? }
after_save :touch_taxa_with_applicable_distribution, if: -> { taxon_concept.nil? }
before_destroy :touch_taxa_with_applicable_distribution, if: -> { taxon_concept.nil? }
def valid_dates
if !(start_date.nil? || end_date.nil?) && (start_date > end_date)
self.errors.add(:start_date, ' has to be before end date.')
end
end
def publication_date_formatted
publication_date ? publication_date.strftime('%d/%m/%Y') : ''
end
def year
start_date ? start_date.strftime('%Y') : ''
end
def party
geo_entity_id ? geo_entity.name_en : ''
end
def unit_name
unit_id ? unit.name_en : ''
end
def self.export(filters)
return false unless export_query(filters).any?
path = "public/downloads/#{self.to_s.tableize}/"
latest = self.order("updated_at DESC").
limit(1).first.updated_at.strftime("%d%m%Y-%H%M%S")
public_file_name = "#{self.to_s.downcase}s_#{latest}_#{filters[:csv_separator]}_separated.csv"
file_name = Digest::SHA1.hexdigest(
filters.merge(:latest_date => latest).
to_hash.
symbolize_keys!.sort.
to_s
) + "_cites_#{self.to_s.downcase}s.csv"
if !File.file?(path + file_name)
self.to_csv(path + file_name, filters)
end
[
path + file_name,
{ :filename => public_file_name, :type => 'text/csv' }
]
end
def self.export_query(filters)
self.joins(:geo_entity).
joins(<<-SQL
LEFT JOIN taxon_concepts ON taxon_concepts.id = trade_restrictions.taxon_concept_id
LEFT JOIN taxon_concepts_mview ON taxon_concepts_mview.id = trade_restrictions.taxon_concept_id
SQL
).
filter_is_current(filters["set"]).
filter_geo_entities(filters).
filter_years(filters).
filter_taxon_concepts(filters).
where(public_display: true).
order(
'taxon_concepts.name_status': :asc,
'taxon_concepts_mview.taxonomic_position': :asc,
start_date: :desc,
'geo_entities.name_en': :asc,
notes: :asc
)
end
# Gets the display text for each CSV_COLUMNS
def self.csv_columns_headers
self::CSV_COLUMNS.map do |b|
Array(b).first
end.flatten
end
def self.to_csv(file_path, filters)
limit = 1000
offset = 0
csv_separator_char =
case filters[:csv_separator]
when :semicolon then ';'
else ','
end
CSV.open(file_path, 'wb', col_sep: csv_separator_char) do |csv|
csv << Species::RestrictionsExport::TAXONOMY_COLUMN_NAMES +
['Remarks'] + self.csv_columns_headers
ids = []
until (objs = export_query(filters).limit(limit).
offset(offset)).empty?
objs.each do |q|
row = []
row += Species::RestrictionsExport.fill_taxon_columns(q)
self::CSV_COLUMNS.each do |c|
if c.is_a?(Array)
row << q.send(c[1])
elsif c == :notes
row << [q.send(c), q.send(:nomenclature_note_en)].reject(&:blank?).join('; ')
else
row << q.send(c)
end
end
csv << row
end
offset += limit
end
end
end
def self.filter_is_current(set)
if set == "current"
return where(:is_current => true)
end
all
end
def self.filter_geo_entities(filters)
if filters.key?("geo_entities_ids")
geo_entities_ids = GeoEntity.nodes_and_descendants(
filters["geo_entities_ids"]
).map(&:id)
return where(:geo_entity_id => geo_entities_ids)
end
all
end
def self.filter_taxon_concepts(filters)
if filters.key?("taxon_concepts_ids")
conds_str = <<-SQL
ARRAY[
taxon_concepts_mview.id, taxon_concepts_mview.family_id,
taxon_concepts_mview.order_id, taxon_concepts_mview.class_id,
taxon_concepts_mview.phylum_id, taxon_concepts_mview.kingdom_id
] && ARRAY[?]
OR trade_restrictions.taxon_concept_id IS NULL
SQL
return where(conds_str, filters["taxon_concepts_ids"].map(&:to_i))
end
all
end
def self.filter_years(filters)
if filters.key?("years")
return where('EXTRACT(YEAR FROM trade_restrictions.start_date)::INTEGER IN (?)',
filters["years"].map(&:to_i))
end
all
end
private
def touch_taxa_with_applicable_distribution
update_stmt = TaxonConcept.send(:sanitize_sql_array, [
"UPDATE taxon_concepts
SET dependents_updated_at = CURRENT_TIMESTAMP, dependents_updated_by_id = :updated_by_id
FROM distributions
WHERE distributions.taxon_concept_id = taxon_concepts.id
AND distributions.geo_entity_id IN (:geo_entity_id)",
updated_by_id: updated_by_id,
geo_entity_id: [
# Rails 5.1 to 5.2
# DEPRECATION WARNING: The behavior of `attribute_was` inside of after callbacks will be changing in the next version of Rails.
# The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now).
# To maintain the current behavior, use `attribute_before_last_save` instead.
#
# DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after callbacks will be changing in the next version of Rails.
# The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now).
# To maintain the current behavior, use `saved_change_to_attribute?` instead.
#
# DEPRECATION WARNING: The behavior of `changed_attributes` inside of after callbacks will be changing in the next version of Rails.
# The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now).
# To maintain the current behavior, use `saved_changes.transform_values(&:first)` instead.
#
# == Original code ==
# geo_entity_id, geo_entity_id_was
# == Changed to fix deprecation warnings ==
geo_entity_id, geo_entity_id_before_last_save
].compact.uniq
])
TaxonConcept.connection.execute update_stmt
end
def touch_descendants
update_stmt = TaxonConcept.send(:sanitize_sql_array, [
"UPDATE taxon_concepts
SET dependents_updated_at = CURRENT_TIMESTAMP, dependents_updated_by_id = :updated_by_id
WHERE data IS NOT NULL
AND ARRAY[
(data->'species_id')::INT,
(data->'genus_id')::INT,
(data->'subfamily_id')::INT,
(data->'family_id')::INT,
(data->'order_id')::INT
] && ARRAY[:taxon_concept_id] ",
updated_by_id: updated_by_id,
taxon_concept_id: [
# Rails 5.1 to 5.2
# DEPRECATION WARNING: The behavior of `attribute_was` inside of after callbacks will be changing in the next version of Rails.
# The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now).
# To maintain the current behavior, use `attribute_before_last_save` instead.
#
# DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after callbacks will be changing in the next version of Rails.
# The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now).
# To maintain the current behavior, use `saved_change_to_attribute?` instead.
#
# DEPRECATION WARNING: The behavior of `changed_attributes` inside of after callbacks will be changing in the next version of Rails.
# The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now).
# To maintain the current behavior, use `saved_changes.transform_values(&:first)` instead.
#
# == Original code ==
# taxon_concept_id, taxon_concept_id_was
# == Changed to fix deprecation warnings ==
taxon_concept_id, taxon_concept_id_before_last_save
].compact.uniq
])
TaxonConcept.connection.execute(update_stmt)
end
end