Class: Identifier
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Identifier
- Includes:
- Housekeeping, Shared::DualAnnotator, Shared::IsData, Shared::Labels, Shared::PolymorphicAnnotator
- Defined in:
- app/models/identifier.rb
Overview
An Identifier is the information that can be use to differentiate concepts. If an identifier differentiates individuals of all types it is “Global”. If an identifier differentiates individuals of one type, within a specific subset of that type, it is “Local”.
Local identifiers have a namespace, a string that preceeds the variable portion of the identifier.
Note this definition is presently very narrow, and that an identifier can in practice be used for a lot more than differentiation (i.e. it can often be resolved etc.).
!! Identifiers should always be created in the context of the the object they identify, see spec/lib/identifier_spec.rb for examples !!
See `build_cached_numeric_identifier`.
This does account for identifiers like:
123,123
123,123.12
123.12
.12
This does not account for identifiers like (though this could be hacked in if it becomes necessary by ordering alphanumerics into decimal additions to the float):
123,123a
123a
123.123a
Defined Under Namespace
Classes: Global, Local, Unknown
Constant Summary
Constants included from Shared::DualAnnotator
Shared::DualAnnotator::ALWAYS_COMMUNITY
Instance Attribute Summary collapse
-
#cached ⇒ String
The full identifier, for display, i.e.
-
#cached_numeric_identifier ⇒ Float?
If ‘identifier` contains a numeric string, then record this as a float.
-
#identifier ⇒ String
The string identifying the object.
-
#identifier_object_id ⇒ String
The type of the identified object, used in a polymorphic relationship.
-
#namespace_id ⇒ Integer
The Namespace for this identifier.
-
#project_id ⇒ Integer
The project ID.
-
#type ⇒ String
The Rails STI subclass of this identifier.
Class Method Summary collapse
Instance Method Summary collapse
-
#build_cached ⇒ Object
protected
See subclasses.
- #build_cached_numeric_identifier ⇒ Object protected
- #is_global? ⇒ Boolean
- #is_local? ⇒ Boolean
- #set_cached ⇒ Object protected
- #type_name ⇒ String
Methods included from Shared::IsData
#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_destroyable?, #is_editable?, #is_in_use?, #is_in_users_projects?, #metamorphosize, #similar
Methods included from Shared::Labels
Methods included from Housekeeping
#has_polymorphic_relationship?
Methods included from Shared::PolymorphicAnnotator
#annotated_object_is_persisted?
Methods inherited from ApplicationRecord
Instance Attribute Details
#cached ⇒ String
The full identifier, for display, i.e. namespace + identifier (local), or identifier (global).
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/identifier.rb', line 58 class Identifier < ApplicationRecord acts_as_list scope: [:project_id, :identifier_object_type, :identifier_object_id ], add_new_at: :top include Shared::DualAnnotator include Shared::PolymorphicAnnotator polymorphic_annotates('identifier_object') include Housekeeping # TODO: potential circular dependency constraint when this is before above. include Shared::Labels include Shared::IsData after_save :set_cached, unless: Proc.new {|n| errors.any? } belongs_to :namespace, inverse_of: :identifiers # only applies to Identifier::Local, here for create purposes validates_presence_of :type, :identifier validates :identifier, presence: true # TODO: DRY to IsData? Test. scope :with_type_string, -> (base_string) {where('type LIKE ?', "#{base_string}")} scope :prefer, -> (type) { order(Arel.sql(<<~SQL)) } CASE WHEN identifiers.type = '#{type}' THEN 1 \ WHEN identifiers.type != '#{type}' THEN 2 END ASC, \ position ASC SQL scope :visible, -> (project_id) { where("identifiers.project_id = ? OR identifiers.type ILIKE 'Identifier::Global%'", project_id) } scope :local, -> {where("identifiers.type ILIKE 'Identifier::Local%'") } scope :global, -> {where("identifiers.type ILIKE 'Identifier::Global%'") } # @return [String, Identifer] def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end # @return [String] def type_name self.class.name.demodulize.downcase end def is_local? false end def is_global? false end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#cached_numeric_identifier ⇒ Float?
Returns If ‘identifier` contains a numeric string, then record this as a float. !! This should never be exposed, it’s used for internal next/previous options only.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/identifier.rb', line 58 class Identifier < ApplicationRecord acts_as_list scope: [:project_id, :identifier_object_type, :identifier_object_id ], add_new_at: :top include Shared::DualAnnotator include Shared::PolymorphicAnnotator polymorphic_annotates('identifier_object') include Housekeeping # TODO: potential circular dependency constraint when this is before above. include Shared::Labels include Shared::IsData after_save :set_cached, unless: Proc.new {|n| errors.any? } belongs_to :namespace, inverse_of: :identifiers # only applies to Identifier::Local, here for create purposes validates_presence_of :type, :identifier validates :identifier, presence: true # TODO: DRY to IsData? Test. scope :with_type_string, -> (base_string) {where('type LIKE ?', "#{base_string}")} scope :prefer, -> (type) { order(Arel.sql(<<~SQL)) } CASE WHEN identifiers.type = '#{type}' THEN 1 \ WHEN identifiers.type != '#{type}' THEN 2 END ASC, \ position ASC SQL scope :visible, -> (project_id) { where("identifiers.project_id = ? OR identifiers.type ILIKE 'Identifier::Global%'", project_id) } scope :local, -> {where("identifiers.type ILIKE 'Identifier::Local%'") } scope :global, -> {where("identifiers.type ILIKE 'Identifier::Global%'") } # @return [String, Identifer] def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end # @return [String] def type_name self.class.name.demodulize.downcase end def is_local? false end def is_global? false end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#identifier ⇒ String
The string identifying the object. Must be unique within the Namespace if provided. Same as rs.tdwg.org/dwc/terms/catalogNumber, but broadened in scope to be used for any data.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/identifier.rb', line 58 class Identifier < ApplicationRecord acts_as_list scope: [:project_id, :identifier_object_type, :identifier_object_id ], add_new_at: :top include Shared::DualAnnotator include Shared::PolymorphicAnnotator polymorphic_annotates('identifier_object') include Housekeeping # TODO: potential circular dependency constraint when this is before above. include Shared::Labels include Shared::IsData after_save :set_cached, unless: Proc.new {|n| errors.any? } belongs_to :namespace, inverse_of: :identifiers # only applies to Identifier::Local, here for create purposes validates_presence_of :type, :identifier validates :identifier, presence: true # TODO: DRY to IsData? Test. scope :with_type_string, -> (base_string) {where('type LIKE ?', "#{base_string}")} scope :prefer, -> (type) { order(Arel.sql(<<~SQL)) } CASE WHEN identifiers.type = '#{type}' THEN 1 \ WHEN identifiers.type != '#{type}' THEN 2 END ASC, \ position ASC SQL scope :visible, -> (project_id) { where("identifiers.project_id = ? OR identifiers.type ILIKE 'Identifier::Global%'", project_id) } scope :local, -> {where("identifiers.type ILIKE 'Identifier::Local%'") } scope :global, -> {where("identifiers.type ILIKE 'Identifier::Global%'") } # @return [String, Identifer] def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end # @return [String] def type_name self.class.name.demodulize.downcase end def is_local? false end def is_global? false end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#identifier_object_id ⇒ String
The type of the identified object, used in a polymorphic relationship.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/identifier.rb', line 58 class Identifier < ApplicationRecord acts_as_list scope: [:project_id, :identifier_object_type, :identifier_object_id ], add_new_at: :top include Shared::DualAnnotator include Shared::PolymorphicAnnotator polymorphic_annotates('identifier_object') include Housekeeping # TODO: potential circular dependency constraint when this is before above. include Shared::Labels include Shared::IsData after_save :set_cached, unless: Proc.new {|n| errors.any? } belongs_to :namespace, inverse_of: :identifiers # only applies to Identifier::Local, here for create purposes validates_presence_of :type, :identifier validates :identifier, presence: true # TODO: DRY to IsData? Test. scope :with_type_string, -> (base_string) {where('type LIKE ?', "#{base_string}")} scope :prefer, -> (type) { order(Arel.sql(<<~SQL)) } CASE WHEN identifiers.type = '#{type}' THEN 1 \ WHEN identifiers.type != '#{type}' THEN 2 END ASC, \ position ASC SQL scope :visible, -> (project_id) { where("identifiers.project_id = ? OR identifiers.type ILIKE 'Identifier::Global%'", project_id) } scope :local, -> {where("identifiers.type ILIKE 'Identifier::Local%'") } scope :global, -> {where("identifiers.type ILIKE 'Identifier::Global%'") } # @return [String, Identifer] def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end # @return [String] def type_name self.class.name.demodulize.downcase end def is_local? false end def is_global? false end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#namespace_id ⇒ Integer
The Namespace for this identifier.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/identifier.rb', line 58 class Identifier < ApplicationRecord acts_as_list scope: [:project_id, :identifier_object_type, :identifier_object_id ], add_new_at: :top include Shared::DualAnnotator include Shared::PolymorphicAnnotator polymorphic_annotates('identifier_object') include Housekeeping # TODO: potential circular dependency constraint when this is before above. include Shared::Labels include Shared::IsData after_save :set_cached, unless: Proc.new {|n| errors.any? } belongs_to :namespace, inverse_of: :identifiers # only applies to Identifier::Local, here for create purposes validates_presence_of :type, :identifier validates :identifier, presence: true # TODO: DRY to IsData? Test. scope :with_type_string, -> (base_string) {where('type LIKE ?', "#{base_string}")} scope :prefer, -> (type) { order(Arel.sql(<<~SQL)) } CASE WHEN identifiers.type = '#{type}' THEN 1 \ WHEN identifiers.type != '#{type}' THEN 2 END ASC, \ position ASC SQL scope :visible, -> (project_id) { where("identifiers.project_id = ? OR identifiers.type ILIKE 'Identifier::Global%'", project_id) } scope :local, -> {where("identifiers.type ILIKE 'Identifier::Local%'") } scope :global, -> {where("identifiers.type ILIKE 'Identifier::Global%'") } # @return [String, Identifer] def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end # @return [String] def type_name self.class.name.demodulize.downcase end def is_local? false end def is_global? false end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#project_id ⇒ Integer
The project ID.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/identifier.rb', line 58 class Identifier < ApplicationRecord acts_as_list scope: [:project_id, :identifier_object_type, :identifier_object_id ], add_new_at: :top include Shared::DualAnnotator include Shared::PolymorphicAnnotator polymorphic_annotates('identifier_object') include Housekeeping # TODO: potential circular dependency constraint when this is before above. include Shared::Labels include Shared::IsData after_save :set_cached, unless: Proc.new {|n| errors.any? } belongs_to :namespace, inverse_of: :identifiers # only applies to Identifier::Local, here for create purposes validates_presence_of :type, :identifier validates :identifier, presence: true # TODO: DRY to IsData? Test. scope :with_type_string, -> (base_string) {where('type LIKE ?', "#{base_string}")} scope :prefer, -> (type) { order(Arel.sql(<<~SQL)) } CASE WHEN identifiers.type = '#{type}' THEN 1 \ WHEN identifiers.type != '#{type}' THEN 2 END ASC, \ position ASC SQL scope :visible, -> (project_id) { where("identifiers.project_id = ? OR identifiers.type ILIKE 'Identifier::Global%'", project_id) } scope :local, -> {where("identifiers.type ILIKE 'Identifier::Local%'") } scope :global, -> {where("identifiers.type ILIKE 'Identifier::Global%'") } # @return [String, Identifer] def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end # @return [String] def type_name self.class.name.demodulize.downcase end def is_local? false end def is_global? false end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#type ⇒ String
The Rails STI subclass of this identifier.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/models/identifier.rb', line 58 class Identifier < ApplicationRecord acts_as_list scope: [:project_id, :identifier_object_type, :identifier_object_id ], add_new_at: :top include Shared::DualAnnotator include Shared::PolymorphicAnnotator polymorphic_annotates('identifier_object') include Housekeeping # TODO: potential circular dependency constraint when this is before above. include Shared::Labels include Shared::IsData after_save :set_cached, unless: Proc.new {|n| errors.any? } belongs_to :namespace, inverse_of: :identifiers # only applies to Identifier::Local, here for create purposes validates_presence_of :type, :identifier validates :identifier, presence: true # TODO: DRY to IsData? Test. scope :with_type_string, -> (base_string) {where('type LIKE ?', "#{base_string}")} scope :prefer, -> (type) { order(Arel.sql(<<~SQL)) } CASE WHEN identifiers.type = '#{type}' THEN 1 \ WHEN identifiers.type != '#{type}' THEN 2 END ASC, \ position ASC SQL scope :visible, -> (project_id) { where("identifiers.project_id = ? OR identifiers.type ILIKE 'Identifier::Global%'", project_id) } scope :local, -> {where("identifiers.type ILIKE 'Identifier::Local%'") } scope :global, -> {where("identifiers.type ILIKE 'Identifier::Global%'") } # @return [String, Identifer] def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end # @return [String] def type_name self.class.name.demodulize.downcase end def is_local? false end def is_global? false end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
Class Method Details
.prototype_identifier(project_id, created_by_id) ⇒ String, Identifer
93 94 95 96 |
# File 'app/models/identifier.rb', line 93 def self.prototype_identifier(project_id, created_by_id) identifiers = Identifier.where(project_id:, created_by_id:).limit(1) identifiers.empty? ? '12345678' : identifiers.last.identifier end |
Instance Method Details
#build_cached ⇒ Object (protected)
See subclasses
114 115 116 |
# File 'app/models/identifier.rb', line 114 def build_cached nil end |
#build_cached_numeric_identifier ⇒ Object (protected)
118 119 120 121 122 123 124 125 126 |
# File 'app/models/identifier.rb', line 118 def build_cached_numeric_identifier return nil if is_global? if a = identifier.match(/\A[\d\.\,]+\z/) b = a.to_s.gsub(',', '') b.to_f else nil end end |
#is_global? ⇒ Boolean
107 108 109 |
# File 'app/models/identifier.rb', line 107 def is_global? false end |
#is_local? ⇒ Boolean
103 104 105 |
# File 'app/models/identifier.rb', line 103 def is_local? false end |
#set_cached ⇒ Object (protected)
128 129 130 131 132 133 |
# File 'app/models/identifier.rb', line 128 def set_cached update_columns( cached: build_cached, cached_numeric_identifier: build_cached_numeric_identifier ) end |
#type_name ⇒ String
99 100 101 |
# File 'app/models/identifier.rb', line 99 def type_name self.class.name.demodulize.downcase end |