Class: Identifier
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Identifier
- Includes:
- Housekeeping, Shared::BatchByFilterScope, 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 ⇒ Integer
The id of the identified object, used in a polymorphic relationship.
-
#identifier_object_type ⇒ 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
- .namespaces_for_types_from_query(identifier_types, query) ⇒ Object
- .process_batch_by_filter_scope(batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false) ⇒ Object
- .prototype_identifier(project_id, created_by_id) ⇒ String, Identifer
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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( cached:, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#identifier_object_id ⇒ Integer
The id 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( cached:, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
#identifier_object_type ⇒ 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# 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::BatchByFilterScope 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 :type, :identifier, presence: true 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 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end protected # See subclasses def build_cached nil end def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( cached:, cached_numeric_identifier: build_cached_numeric_identifier ) end end |
Class Method Details
.namespaces_for_types_from_query(identifier_types, query) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'app/models/identifier.rb', line 112 def self.namespaces_for_types_from_query(identifier_types, query) q = ::Queries::Query::Filter.instantiated_base_filter(query) @namespaces = q.all .joins(identifiers: :namespace) .where(identifiers: {type: identifier_types}) .select('namespaces.short_name, namespaces.verbatim_short_name') .order('namespaces.short_name') .distinct .pluck('namespaces.id', 'namespaces.short_name', 'namespaces.verbatim_short_name', 'is_virtual') .map { |id, short_name, verbatim_short_name, is_virtual| { id:, short_name:, verbatim_short_name:, is_virtual: }} end |
.process_batch_by_filter_scope(batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'app/models/identifier.rb', line 126 def self.process_batch_by_filter_scope( batch_response: nil, query: nil, hash_query: nil, mode: nil, params: nil, async: nil, project_id: nil, user_id: nil, called_from_async: false ) # Don't call async from async (the point is we do the same processing in # async and not in async, and this function handles both that processing and # making the async call, so it's this much janky). async = false if called_from_async == true r = batch_response # TODO: make sure params.identifier_types are actually allowed on klass if params[:namespace_id].nil? r.errors['replacement namespace id not provided'] = 1 return elsif params[:identifier_types].empty? r.errors['no identifier types provided'] = 1 return r elsif params[:virtual_namespace_prefix] && Namespace.find(params[:namespace_id]).is_virtual r.errors["Can't currently delete virtual prefix and change to virtual namespace"] = 1 return r elsif params[:namespaces_to_replace].empty? r.errors['no namespaces to replace'] = 1 return r end case mode.to_sym when :replace if params[:namespace_id].nil? r.errors['no replacement namespace id provided'] = 1 return r.to_json end if async && !called_from_async BatchByFilterScopeJob.perform_later( klass: self.name, hash_query: hash_query, mode:, params:, project_id:, user_id: ) else prefix = params[:virtual_namespace_prefix] Identifier .with(a: query.select(:id)) .joins('JOIN a ON identifiers.identifier_object_id = a.id AND ' \ "identifiers.identifier_object_type = '#{query.klass}'") .where(type: params[:identifier_types]) .joins(:namespace) .where(namespaces: { id: params[:namespaces_to_replace] }) .select('identifiers.*, namespaces.is_virtual AS is_virtual') #.where.not(namespace_id: params.namespace_id) .distinct .find_each do |o| if params[:virtual_namespace_prefix] if ( o.is_virtual && o.identifier.start_with?(prefix) && o.identifier.length > prefix.length ) o.identifier = o.identifier.delete_prefix(prefix) o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end else # this identifier is excluded by virtual_namespace_prefix r.not_updated.push o.id end else # no virtual worries, just namespace_id replacement o.namespace_id = params[:namespace_id] if o.save r.updated.push o.id else r.not_updated.push o.id end end end end end r.total_attempted = r.updated.length + r.not_updated.length return r end |
.prototype_identifier(project_id, created_by_id) ⇒ String, Identifer
94 95 96 97 |
# File 'app/models/identifier.rb', line 94 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
218 219 220 |
# File 'app/models/identifier.rb', line 218 def build_cached nil end |
#build_cached_numeric_identifier ⇒ Object (protected)
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'app/models/identifier.rb', line 222 def build_cached_numeric_identifier return nil if is_global? re = if is_local? && is_virtual? /[\d\.\,]+\z/ # namespace prefix allowed else /\A[\d\.\,]+\z/ # only 'numeric' supported end if a = identifier.match(re) b = a[0].to_s.gsub(',', '') b.to_f else nil end end |
#is_global? ⇒ Boolean
108 109 110 |
# File 'app/models/identifier.rb', line 108 def is_global? false end |
#is_local? ⇒ Boolean
104 105 106 |
# File 'app/models/identifier.rb', line 104 def is_local? false end |
#set_cached ⇒ Object (protected)
238 239 240 241 242 243 244 245 246 |
# File 'app/models/identifier.rb', line 238 def set_cached # build_cached_numeric_identifier uses the value of #cached, so update it # locally first. self.cached = build_cached update_columns( cached:, cached_numeric_identifier: build_cached_numeric_identifier ) end |
#type_name ⇒ String
100 101 102 |
# File 'app/models/identifier.rb', line 100 def type_name self.class.name.demodulize.downcase end |