labimotion 2.2.0.rc18 → 2.2.0.rc19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac9e13a86048207de58add4ce3c2670dd3d3ece7b334103a4b8afef774309bd7
4
- data.tar.gz: a21c3957630ea45d248eec5f49999e31424d3ea30c70b3d9b34b11dbe3105c12
3
+ metadata.gz: bf9125138e1e1d1e52a978fd85f63e67dd1339cf683296bdf0680c86dd9cc16b
4
+ data.tar.gz: e910e27b0a70cc1e6995142a23af5fba0f59a9313bd13d5eea01092f42991c85
5
5
  SHA512:
6
- metadata.gz: 7ec1e065535fe7fbfa1c94f82f1f5f06057857aa6439b41fab7e8570b4b584276a9625fd5d806431a6abe3307b147107f6e91469c65b351bcf3b5a9f868d8d83
7
- data.tar.gz: 4ab8e424d00107d029dbe0cdd97740a2d2470994121c1d82b40fd902c5bbd1d1712849756da75631f675666ee39f09a4d56f0839452eb0a29a9b8a78c1ef37bc
6
+ metadata.gz: ee02aa627ad14085e7860c8f017442d3733a764883781cd6e5156b5a4cd759b41a9697612fd37238437b4c7ef6671f5e43e355243c6cf70061ec555ebb46b7b8
7
+ data.tar.gz: de67299ee82e4e59b8dcce459265b3905338fef48c60271d86b653f00279a8e749afbeb5ea53681a2e0e2ca7a9c4a4386073ec9fe29f17f96504ff1a7ea9cf50
@@ -0,0 +1,55 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://datacite.org/schema/kernel-4" xsi:schemaLocation="http://datacite.org/schema/kernel-4 https://schema.datacite.org/meta/kernel-4/metadata.xsd">
3
+ <identifier identifierType="DOI">https://dx.doi.org/<%= full_doi %></identifier>
4
+ <creators>
5
+ <% if creators.empty? %>
6
+ <creator>
7
+ <creatorName>(:unav)</creatorName>
8
+ </creator>
9
+ <% end %>
10
+ <% creators.each do |creator| %>
11
+ <creator>
12
+ <creatorName nameType="Personal"><%= creator['familyName'] %>, <%= creator['givenName'] %></creatorName>
13
+ <givenName><%= creator['givenName'] %></givenName>
14
+ <familyName><%= creator['familyName'] %></familyName>
15
+ <% if creator['orcid'].present? %>
16
+ <nameIdentifier schemeURI="https://orcid.org/" nameIdentifierScheme="ORCID"><%= creator['orcid'] %></nameIdentifier>
17
+ <% end %>
18
+ <% if creator['affiliation'].present? %>
19
+ <affiliation><%= creator['affiliation'] %></affiliation>
20
+ <% end %>
21
+ </creator>
22
+ <% end %>
23
+ </creators>
24
+ <titles>
25
+ <title xml:lang="en-US"><%= title %></title>
26
+ </titles>
27
+ <%= render_publisher(publisher_config) %>
28
+ <publicationYear><%= publication_year %></publicationYear>
29
+ <subjects>
30
+ <subject>Chemistry</subject>
31
+ </subjects>
32
+ <% if contributor %>
33
+ <contributors>
34
+ <contributor contributorType="Researcher">
35
+ <contributorName nameType="Personal"><%= contributor['familyName'] %>, <%= contributor['givenName'] %></contributorName>
36
+ <givenName><%= contributor['givenName'] %></givenName>
37
+ <familyName><%= contributor['familyName'] %></familyName>
38
+ <% if contributor['orcid'].present? %>
39
+ <nameIdentifier schemeURI="https://orcid.org/" nameIdentifierScheme="ORCID"><%= contributor['orcid'] %></nameIdentifier>
40
+ <% end %>
41
+ </contributor>
42
+ </contributors>
43
+ <% end %>
44
+ <language>en</language>
45
+ <resourceType resourceTypeGeneral="Model"><%= resource_label %></resourceType>
46
+ <version><%= version %></version>
47
+ <rightsList>
48
+ <rights><%= license %></rights>
49
+ </rightsList>
50
+ <descriptions>
51
+ <% if description.present? %>
52
+ <description xml:lang="en-US" descriptionType="Abstract"><%= description %></description>
53
+ <% end %>
54
+ </descriptions>
55
+ </resource>
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TemplateDoi concern
4
+ #
5
+ # LabIMotion template DOI logic for the host's Doi model. The host owns the DOI
6
+ # record and the generic DataCite plumbing (build_suffix/align_suffix, the MDS
7
+ # client, the DataCitePublisher concern); this adds only the LabIMotion-specific
8
+ # class methods that the gem's template DOI usecases and APIs call on ::Doi.
9
+ #
10
+ # The host wires it up with a single line:
11
+ # class Doi < ApplicationRecord
12
+ # include Labimotion::TemplateDoi
13
+ # end
14
+ #
15
+ # Assumes the host `dois` table has a jsonb `metadata` column (the DOI version
16
+ # is stamped there) and that the deterministic suffix survives Doi#align_suffix
17
+ # unchanged (reserve sets inchikey == suffix and version_count: 0 for that).
18
+ module Labimotion
19
+ module TemplateDoi
20
+ extend ActiveSupport::Concern
21
+
22
+ # Literal namespace shared by every LabIMotion template DOI suffix.
23
+ LABIMOTION_DOI_NAMESPACE = 'labimotion'
24
+
25
+ # ActiveSupport::Concern extends this into the including class, so every
26
+ # method below becomes a class method on the host's Doi.
27
+ module ClassMethods
28
+ # Deterministic DOI suffix for a LabIMotion template klass. The same suffix
29
+ # is used at reserve and at release time, so both share one DOI:
30
+ # element -> labimotion/element/<name>/<identifier>/<doi_version>
31
+ # segment -> labimotion/segment/<element>/<identifier>/<doi_version>
32
+ # dataset -> labimotion/dataset/<ols_term_id>/<identifier>/<doi_version>
33
+ # `<identifier>` is the first UUID group (8 hex chars) of the template's
34
+ # identifier/uuid, falling back to the record id. `<doi_version>` is the DOI
35
+ # version (see .labimotion_version_segment). Callers may pass an explicit
36
+ # template version; otherwise the record's current version is used.
37
+ def build_labimotion_suffix(record, version = nil)
38
+ parts = labimotion_suffix_parts(record)
39
+ raise ArgumentError, "unsupported template: #{record.class}" if parts.nil?
40
+
41
+ segment = labimotion_version_segment(record, version)
42
+ return nil if segment.blank?
43
+
44
+ segments = parts.map { |part| sanitize_doi_part(part) }.reject(&:empty?)
45
+ "#{segments.join('/')}/#{segment}"
46
+ end
47
+
48
+ # The DOI version segment for a template version. A DOI is published per major
49
+ # release: the .0 release opens that version (X.0 -> "X") and later minor
50
+ # revisions roll into the NEXT DOI (X.y, y>=1 -> "X+1"). Sub-1.0 and
51
+ # unversioned/blank templates map to the first DOI (v1).
52
+ # 0.x -> "1" 1.0 -> "1" 1.1..2.0 -> "2" 2.1..3.0 -> "3"
53
+ def labimotion_version_segment(record, version = nil)
54
+ raw = (version.presence || record.try(:version)).to_s.strip
55
+ parts = raw.split('.')
56
+ major = parts[0].to_i
57
+ minor = parts[1].to_i
58
+ return '1' if major.zero?
59
+ return major.to_s if minor.zero?
60
+
61
+ (major + 1).to_s
62
+ end
63
+
64
+ # The DOIs reserved for a LabIMotion template, in reservation order.
65
+ def labimotion_dois(record)
66
+ where(doiable_id: record.id, doiable_type: record.class.name).order(:id).to_a
67
+ end
68
+
69
+ # The current (most recently reserved) DOI for a template, or nil.
70
+ def labimotion_latest_doi(record)
71
+ labimotion_dois(record).last
72
+ end
73
+
74
+ # The DOI version (next-major integer) recorded on a reserved template DOI.
75
+ def labimotion_doi_version(doi)
76
+ doi.metadata.to_h.dig('labimotion', 'doi_version')
77
+ end
78
+
79
+ # Metadata stamped on a reserved template DOI so the version it represents
80
+ # survives on the record (used to gate reserving the next version).
81
+ def labimotion_doi_metadata(record, version = nil)
82
+ { 'labimotion' => { 'doi_version' => labimotion_version_segment(record, version) } }
83
+ end
84
+
85
+ def labimotion_suffix_parts(record)
86
+ # identifier/uuid may both be blank for a draft, which would collapse the
87
+ # scope (colliding across templates). Fall back to the stable record id so
88
+ # every template gets a unique, deterministic suffix. Resolved lazily so an
89
+ # unsupported record (no #id) still falls through to the ArgumentError in the
90
+ # caller. The identifier is shortened to its first UUID group (8 hex chars).
91
+ # A segment's <element> is the name of the ElementKlass it belongs to.
92
+ identifier = lambda do
93
+ raw = record.try(:identifier).presence || record.try(:uuid).presence || record.id
94
+ raw.to_s.split('-').first
95
+ end
96
+ case record
97
+ when ::Labimotion::ElementKlass
98
+ [LABIMOTION_DOI_NAMESPACE, 'element', record.name, identifier.call]
99
+ when ::Labimotion::SegmentKlass
100
+ [LABIMOTION_DOI_NAMESPACE, 'segment', record.element_klass&.name, identifier.call]
101
+ when ::Labimotion::DatasetKlass
102
+ [LABIMOTION_DOI_NAMESPACE, 'dataset', record.ols_term_id, identifier.call]
103
+ end
104
+ end
105
+
106
+ # Keeps a suffix segment DOI-safe: trims, collapses whitespace to hyphens and
107
+ # drops anything outside [A-Za-z0-9._-] (colons stripped, as elsewhere here).
108
+ def sanitize_doi_part(value)
109
+ value.to_s.strip.gsub(/\s+/, '-').gsub(/[^A-Za-z0-9._-]/, '')
110
+ end
111
+ end
112
+ end
113
+ end
@@ -11,7 +11,11 @@ module Labimotion
11
11
  class BuildTemplateDoiXml
12
12
  include ::DataCitePublisher
13
13
 
14
- TEMPLATE_PATH = Rails.root.join('app/publish/datacite_metadata_labimotion_template.html.erb')
14
+ # The template ships with the gem. A host app may still override it by
15
+ # placing its own copy at HOST_TEMPLATE_PATH, which is how it used to be
16
+ # sourced — hence the lookup order below.
17
+ GEM_TEMPLATE_PATH = File.expand_path('../libs/data/datacite/labimotion_template.html.erb', __dir__)
18
+ HOST_TEMPLATE_PATH = 'app/publish/datacite_metadata_labimotion_template.html.erb'
15
19
 
16
20
  RESOURCE_LABEL = {
17
21
  'element' => 'LabIMotion Element Template',
@@ -29,11 +33,19 @@ module Labimotion
29
33
  def call
30
34
  return nil if @doi.nil?
31
35
 
32
- ERB.new(File.read(TEMPLATE_PATH), trim_mode: '-').result(binding)
36
+ ERB.new(File.read(template_path), trim_mode: '-').result(binding)
33
37
  end
34
38
 
35
39
  private
36
40
 
41
+ # Resolved per call rather than at load time, so the class no longer needs
42
+ # Rails to be booted just to be autoloaded.
43
+ def template_path
44
+ # `Rails.root` is still nil if Rails is loaded but the app has not booted.
45
+ host = defined?(Rails) && Rails.respond_to?(:root) ? Rails.root&.join(HOST_TEMPLATE_PATH) : nil
46
+ host && File.exist?(host) ? host : GEM_TEMPLATE_PATH
47
+ end
48
+
37
49
  def full_doi
38
50
  @doi.full_doi
39
51
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  ## Labimotion Version
4
4
  module Labimotion
5
- VERSION = '2.2.0.rc18'
5
+ VERSION = '2.2.0.rc19'
6
6
  end
data/lib/labimotion.rb CHANGED
@@ -148,4 +148,5 @@ module Labimotion
148
148
  autoload :Datasetable, 'labimotion/models/concerns/datasetable'
149
149
  autoload :AttachmentConverter, 'labimotion/models/concerns/attachment_converter'
150
150
  autoload :LinkedProperties, 'labimotion/models/concerns/linked_properties'
151
+ autoload :TemplateDoi, 'labimotion/models/concerns/template_doi'
151
152
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: labimotion
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0.rc18
4
+ version: 2.2.0.rc19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chia-Lin Lin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-07-01 00:00:00.000000000 Z
12
+ date: 2026-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: caxlsx
@@ -111,6 +111,7 @@ files:
111
111
  - lib/labimotion/helpers/vocabulary_helpers.rb
112
112
  - lib/labimotion/libs/attachment_handler.rb
113
113
  - lib/labimotion/libs/converter.rb
114
+ - lib/labimotion/libs/data/datacite/labimotion_template.html.erb
114
115
  - lib/labimotion/libs/data/mapper/Chemwiki.json
115
116
  - lib/labimotion/libs/data/mapper/Source.json
116
117
  - lib/labimotion/libs/data/vocab/Standard.json
@@ -136,6 +137,7 @@ files:
136
137
  - lib/labimotion/models/concerns/linked_properties.rb
137
138
  - lib/labimotion/models/concerns/metadata_validation.rb
138
139
  - lib/labimotion/models/concerns/segmentable.rb
140
+ - lib/labimotion/models/concerns/template_doi.rb
139
141
  - lib/labimotion/models/concerns/workflow.rb
140
142
  - lib/labimotion/models/dataset.rb
141
143
  - lib/labimotion/models/dataset_klass.rb