app/models/identifier/local.rb
# The identifier that is generated for local use, i.e. no signficant effort (countering example DOIs) was
# made to ensure global uniqueness. While most identifiers are intended to be unique globally, few
# consider mechanisms for ensuring this.
#
# Local identifiers of the same type may be stacked on a single record without defining the relation
# between each identifier (see conceptual difference in Identfier::Global).
#
# Local identifiers require a namespace. See Namespace.
#
# Multiple local identfiers of the same namespace can be applied to the same object, while this is rarely useful in real life
# it does have physical-world analogs, see in particular Accession numbers on Collecting Events linked to Specimens that are in the process of
# being accessioned.
#
# Foo 123 (CatalogNumber)
# Foo 345 (CatalogNumber)
#
# You can also do this on the same object:
# Foo 123 (CatalogNumber)
# Bar 123 (CatalogNumber)
#
# In addition, identifiers of a certain type (subclass) must be unique across namespaces within a project.
#
class Identifier::Local < Identifier
# This must exist, rather than namespace: true, because we don't have database side not null in the model. We also don't accept nested
# namespaces in this belongs_to.
validates :namespace_id, presence: true
validates_uniqueness_of :identifier, scope: [:namespace_id, :project_id, :type], message: lambda { |error, attributes| "#{attributes[:value]} already taken"}
# @return boolean
# We don't have to inspect the namespace because it's appended to cached
# This cuts down a query in many places
def is_virtual?
identifier.present? && identifier == cached
end
# Exact match on identifier + namespace
# @param [String, String]
# @return [Scope]
def with_namespaced_identifier(namespace_name, identifier)
ret_val = includes(:identifiers).where(identifiers: {namespace: {name: namespace_name}}, identifier: identifier).references(:identifiers)
if ret_val.count == 0
ret_val = includes(:identifiers).where(identifiers: {namespace: {short_name: namespace_name}}, identifier: identifier).references(:identifiers)
end
ret_val
end
# Update cached values for all local identifiers within namespace (if needed)
# @param [Namespace]
def self.update_cached(namespace)
where(namespace: namespace).update_all(
cached: Arel::Nodes::NamedFunction.new('concat',
[
Arel::Nodes.build_quoted(build_cached_prefix(namespace)),
Identifier::Local.arel_table[:identifier]
]
)
) if [:short_name, :verbatim_short_name, :delimiter, :is_virtual].detect { |a| namespace.saved_change_to_attribute?(a) }
end
def is_local?
true
end
def increment_identifier
write_attribute(:identifier, ::Utilities::Strings.increment_contained_integer(identifier))
end
protected
def build_cached
if namespace.is_virtual
identifier
else
Identifier::Local.build_cached_prefix(namespace) + identifier.to_s
end
end
def self.build_cached_prefix(namespace)
if namespace.is_virtual?
''
else
delimiter = namespace.read_attribute(:delimiter) || ' '
delimiter = '' if delimiter == 'NONE'
[namespace&.verbatim_short_name, namespace&.short_name, ''].compact.first + delimiter
end
end
end