active_fedora-noid 2.0.0.beta3 → 2.0.0.beta4

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
  SHA1:
3
- metadata.gz: 5db388af056519beadb49a49d0d1e4b10c5871fb
4
- data.tar.gz: d50a20cc2589d1b84f0068dbed814d4002cc52c5
3
+ metadata.gz: 709c8e18225adbbd00041373fc45c436166ffa50
4
+ data.tar.gz: 4b4a569df3483bf994dc154ed986fc4bf700561c
5
5
  SHA512:
6
- metadata.gz: 9d11883c84164902abdbcca6cb97e94bcfb23c4910f8230d390464070ef982866eb76d85969c916aa6c6cbb014eea3acdf35496a5dfd68d8b621cfec3dac7a0b
7
- data.tar.gz: ae62d72ce5bc53cded2427798b3b07d37d3314c0c3423d703d6ccabeea59280602d4a161301792665e3b5a471082f3ad14e939c67ff27ed34a55b772b501ca5e
6
+ metadata.gz: 8ad9a677d42550c1fa40545ab19b5f872da006971a73cdbe9cf8788667316da5fee10541779f155d156ba772eca59f79d0a9c7db5262c07bb97e0f0320b506d1
7
+ data.tar.gz: db77507d045c565b2c7aa1ea351f4112405f0dcbaef0f47081f5589e6a56c2099cb1639dd6fda906ac621ec9a30a4229c3607db728a676674f01fa770159477d
data/.travis.yml CHANGED
@@ -7,8 +7,8 @@ env:
7
7
  global:
8
8
  - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
9
9
  matrix:
10
- - "RAILS_VERSION=4.2.6"
11
- - "RAILS_VERSION=5.0.0"
10
+ - "RAILS_VERSION=4.2.7.1"
11
+ - "RAILS_VERSION=5.0.0.1"
12
12
  notifications:
13
13
  irc:
14
14
  channels:
data/Gemfile CHANGED
@@ -6,13 +6,13 @@ gemspec
6
6
  group :development, :test do
7
7
  gem 'byebug' unless ENV['CI']
8
8
  gem 'coveralls', require: false
9
- gem 'byebug' unless ENV['CI']
10
9
  end
10
+
11
11
  # BEGIN ENGINE_CART BLOCK
12
- # engine_cart: 0.8.2
13
- # engine_cart stanza: 0.8.0
12
+ # engine_cart: 1.0.1
13
+ # engine_cart stanza: 0.10.0
14
14
  # the below comes from engine_cart, a gem used to test this Rails engine gem in the context of a Rails app.
15
- file = File.expand_path("Gemfile", ENV['ENGINE_CART_DESTINATION'] || ENV['RAILS_ROOT'] || File.expand_path(".internal_test_app", File.dirname(__FILE__)))
15
+ file = File.expand_path('Gemfile', ENV['ENGINE_CART_DESTINATION'] || ENV['RAILS_ROOT'] || File.expand_path('.internal_test_app', File.dirname(__FILE__)))
16
16
  if File.exist?(file)
17
17
  begin
18
18
  eval_gemfile file
@@ -26,19 +26,19 @@ else
26
26
  if ENV['RAILS_VERSION']
27
27
  if ENV['RAILS_VERSION'] == 'edge'
28
28
  gem 'rails', github: 'rails/rails'
29
- ENV['ENGINE_CART_RAILS_OPTIONS']= "--edge --skip-turbolinks"
29
+ ENV['ENGINE_CART_RAILS_OPTIONS'] = '--edge --skip-turbolinks'
30
30
  else
31
31
  gem 'rails', ENV['RAILS_VERSION']
32
32
  end
33
33
  end
34
34
 
35
- if ENV['RAILS_VERSION'].nil? || ENV['RAILS_VERSION'] =~ /^4.2/
36
- gem 'responders', "~> 2.0"
37
- gem 'sass-rails', ">= 5.0"
38
- elsif ENV['RAILS_VERSION'] =~ /^5.0/ || ENV['RAILS_VERSION'] == 'edge'
39
- # nop
40
- else
41
- gem 'sass-rails', "< 5.0"
35
+ case ENV['RAILS_VERSION']
36
+ when /^4.2/
37
+ gem 'responders', '~> 2.0'
38
+ gem 'sass-rails', '>= 5.0'
39
+ gem 'coffee-rails', '~> 4.1.0'
40
+ when /^4.[01]/
41
+ gem 'sass-rails', '< 5.0'
42
42
  end
43
43
  end
44
44
  # END ENGINE_CART BLOCK
data/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  [![Version](https://badge.fury.io/rb/active_fedora-noid.png)](http://badge.fury.io/rb/active_fedora-noid)
2
+ [![Build Status](https://travis-ci.org/projecthydra-labs/active_fedora-noid.png?branch=master)](https://travis-ci.org/projecthydra-labs/active_fedora-noid)
3
+ [![Coverage Status](https://coveralls.io/repos/projecthydra-labs/active_fedora-noid/badge.svg)](https://coveralls.io/r/projecthydra-labs/active_fedora-noid)
4
+ [![Code Climate](https://codeclimate.com/github/projecthydra-labs/active_fedora-noid/badges/gpa.svg)](https://codeclimate.com/github/projecthydra-labs/active_fedora-noid)
5
+ [![Dependency Status](https://gemnasium.com/projecthydra-labs/active_fedora-noid.png)](https://gemnasium.com/projecthydra-labs/active_fedora-noid)
2
6
  [![Apache 2.0 License](http://img.shields.io/badge/APACHE2-license-blue.svg)](./LICENSE)
3
7
  [![Contribution Guidelines](http://img.shields.io/badge/CONTRIBUTING-Guidelines-blue.svg)](./CONTRIBUTING.md)
4
8
  [![API Docs](http://img.shields.io/badge/API-docs-blue.svg)](http://rubydoc.info/gems/active_fedora-noid)
5
- [![Build Status](https://travis-ci.org/projecthydra-labs/active_fedora-noid.png?branch=master)](https://travis-ci.org/projecthydra-labs/active_fedora-noid)
6
- [![Dependency Status](https://gemnasium.com/projecthydra-labs/active_fedora-noid.png)](https://gemnasium.com/projecthydra-labs/active_fedora-noid)
7
- [![Coverage Status](https://coveralls.io/repos/projecthydra-labs/active_fedora-noid/badge.svg)](https://coveralls.io/r/projecthydra-labs/active_fedora-noid)
8
9
 
9
10
  # ActiveFedora::Noid
10
11
 
@@ -96,18 +97,30 @@ This will make sure your objects have Noid-like identifiers (e.g. `bb22bb22b`) t
96
97
 
97
98
  ## Overriding default behavior
98
99
 
99
- ### Minter state (for replayability)
100
+ The default minter in ActiveFedora::Noid 2.x is the database-backed minter to better support multi-host production installations that expect a shared database but not necessarily a shared filesystem (e.g., between load-balanced Rails applications).
100
101
 
101
- The default minter creates a Noid and dumps it to a statefile in the /tmp directory. You can override the location or name of this statefile as follows in e.g. `config/initializers/active_fedora-noid.rb`:
102
+ ### Use file-based minter state (for replayability)
103
+
104
+ The file-based minter -- which was the default and only minter in 1.x -- creates a Noid and dumps it to a statefile in the /tmp directory. You can override the location or name of this statefile as follows in e.g. `config/initializers/active_fedora-noid.rb`:
102
105
 
103
106
  ```ruby
104
107
  require 'active_fedora/noid'
105
108
 
106
109
  ActiveFedora::Noid.configure do |config|
110
+ config.minter_class = ActiveFedora::Noid::Minter::File
107
111
  config.statefile = '/var/foo/bar'
108
112
  end
109
113
  ```
110
114
 
115
+ **NOTE**: If you switch to a new minter, it will not automatically start with the same state as the old minter. AF::Noid does include a couple of rake tasks for copying state from database-backed minters to file-backed ones and vice versa:
116
+
117
+ ``` bash
118
+ # For migrating minter state from a file to a database
119
+ $ rake active_fedora:noid:migrate:file_to_database
120
+ # For migrating minter state from a database to a file
121
+ $ rake active_fedora:noid:migrate:database_to_file
122
+ ```
123
+
111
124
  ### Identifier template
112
125
 
113
126
  To override the default identifier pattern -- a nine-character string consisting of two alphanumeric digits, two numeric digits, two alphanumeric digits, two numeric digits, and a check digit -- put the following code in e.g. `config/initializers/active_fedora-noid.rb`:
@@ -124,28 +137,38 @@ For more information about the format of Noid patterns, see pages 8-10 of the [N
124
137
 
125
138
  ### Custom minters
126
139
 
127
- If you don't want your minter's state to be persisted, you may also pass in your own minter. First write up a minter class that looks like the following:
140
+ If you don't want your minter's state to be persisted, you may also write and configure your own minter. First write up a minter class that looks like the following:
128
141
 
129
142
  ```ruby
130
- class MyMinter
131
- def initialize(*args)
132
- # do something if you need initialization
143
+ class MyMinter < ActiveFedora::Noid::Minter::Base
144
+ def valid?(identifier)
145
+ # return true/false if you care about ids conforming to templates
133
146
  end
134
147
 
135
- def mint
136
- # spit out an identifier
148
+ def read
149
+ # return current minter state
137
150
  end
138
151
 
139
- def valid?(identifier)
140
- # return true/false if you care about ids conforming to templates
152
+ def write!(state)
153
+ # write a passed-in minter state
154
+ end
155
+
156
+ protected
157
+
158
+ def next_id
159
+ # return the next identifier from the minter
141
160
  end
142
161
  end
143
162
  ```
144
163
 
145
- Then inject an instance of your minter into ActiveFedora::Noid::Service:
164
+ Then add your new minter class to the ActiveFedora::Noid configuration (`config/initializers/active_fedora-noid.rb`):
146
165
 
147
166
  ```ruby
148
- noid_service = ActiveFedora::Noid::Service.new(MyMinter.new)
167
+ require 'active_fedora/noid'
168
+
169
+ ActiveFedora::Noid.configure do |config|
170
+ config.minter_class = MyMinter
171
+ end
149
172
  ```
150
173
 
151
174
  And the service will delegate minting and validating to an instance of your customized minter class.
@@ -19,11 +19,11 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_dependency 'active-fedora', '>= 9.7', '< 12'
21
21
  spec.add_dependency 'noid', '~> 0.9'
22
- spec.add_dependency 'rails', '>= 4.2.6', '< 6'
22
+ spec.add_dependency 'rails', '>= 4.2.7.1', '< 6'
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.7"
25
- spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rake", "~> 11.0"
26
26
  spec.add_development_dependency 'rspec', '~> 3.2'
27
27
  spec.add_development_dependency 'sqlite3'
28
- spec.add_development_dependency 'engine_cart', '~> 0.8'
28
+ spec.add_development_dependency 'engine_cart', '~> 1.0'
29
29
  end
@@ -2,20 +2,4 @@ class MinterState < ActiveRecord::Base
2
2
  validates :namespace, presence: true, uniqueness: true
3
3
  validates :template, presence: true
4
4
  validates :template, format: { with: Object.const_get('Noid::Template::VALID_PATTERN'), message: 'value fails regex' }
5
-
6
- # @return [Hash] options for Noid::Minter.new
7
- # * template [String] setting the identifier pattern
8
- # * seq [Integer] reflecting minter position in sequence
9
- # * counters [Array{Hash}] "buckets" each with :current and :max values
10
- # * rand [Object] random number generator object
11
- def noid_options
12
- return nil unless template
13
- opts = {
14
- :template => template,
15
- :seq => seq
16
- }
17
- opts[:counters] = JSON.parse(counters, :symbolize_names => true) if counters
18
- opts[:rand] = Marshal.load(random) if random
19
- opts
20
- end
21
5
  end
@@ -0,0 +1,5 @@
1
+ class RenameMinterStateRandomToRand < ActiveRecord::Migration
2
+ def change
3
+ rename_column :minter_states, :random, :rand
4
+ end
5
+ end
@@ -2,9 +2,7 @@ require 'active_fedora/noid/version'
2
2
  require 'active_fedora/noid/config'
3
3
  require 'active_fedora/noid/engine'
4
4
  require 'active_fedora/noid/service'
5
- require 'active_fedora/noid/minter/base'
6
- require 'active_fedora/noid/minter/db'
7
- require 'active_fedora/noid/synchronized_minter'
5
+ require 'active_fedora/noid/minter'
8
6
 
9
7
  module ActiveFedora
10
8
  module Noid
@@ -18,7 +16,9 @@ module ActiveFedora
18
16
  end
19
17
 
20
18
  def treeify(identifier)
21
- (identifier.scan(/..?/).first(4) + [identifier]).join('/')
19
+ head = identifier.split('/').first
20
+ head.gsub!(/#.*/, '')
21
+ (head.scan(/..?/).first(4) + [identifier]).join('/')
22
22
  end
23
23
  end
24
24
  end
@@ -1,7 +1,8 @@
1
1
  module ActiveFedora
2
2
  module Noid
3
3
  class Config
4
- attr_writer :template, :translate_uri_to_id, :translate_id_to_uri, :statefile, :namespace
4
+ attr_writer :template, :translate_uri_to_id, :translate_id_to_uri,
5
+ :statefile, :namespace, :minter_class
5
6
 
6
7
  def template
7
8
  @template ||= '.reeddeeddk'
@@ -15,6 +16,10 @@ module ActiveFedora
15
16
  @namespace ||= 'default'
16
17
  end
17
18
 
19
+ def minter_class
20
+ @minter_class ||= Minter::Db
21
+ end
22
+
18
23
  def translate_uri_to_id
19
24
  lambda do |uri|
20
25
  uri.to_s.sub(baseurl, '').split('/', baseparts).last
@@ -0,0 +1,3 @@
1
+ require_relative 'minter/base'
2
+ require_relative 'minter/file'
3
+ require_relative 'minter/db'
@@ -1,13 +1,23 @@
1
1
  require 'noid'
2
+ require 'active_fedora'
2
3
 
3
4
  module ActiveFedora
4
5
  module Noid
5
6
  module Minter
6
7
  class Base < ::Noid::Minter
8
+ ##
9
+ # @param template [#to_s] a NOID template
10
+ #
11
+ # @see Noid::Template
7
12
  def initialize(template = default_template)
8
- super(:template => template.to_s)
13
+ super(template: template.to_s)
9
14
  end
10
15
 
16
+ ##
17
+ # Sychronously mint a new identifier. Guarantees the ID is not
18
+ # already reserved in ActiveFedora.
19
+ #
20
+ # @return [String] the minted identifier
11
21
  def mint
12
22
  Mutex.new.synchronize do
13
23
  while true
@@ -17,14 +27,34 @@ module ActiveFedora
17
27
  end
18
28
  end
19
29
 
30
+ ##
31
+ # @return [Hash] an object representing the current minter state
32
+ def read
33
+ raise NotImplementedError.new('Implement #read in child class')
34
+ end
35
+
36
+ ##
37
+ # Updates the minter state to that of the `minter` parameter.
38
+ #
39
+ # @param minter [Minter::Base]
40
+ #
41
+ # @return [void]
42
+ def write!(_)
43
+ raise NotImplementedError.new('Implement #write! in child class')
44
+ end
45
+
20
46
  protected
21
47
 
48
+ ##
49
+ # @return [#to_s] the default template for this
22
50
  def default_template
23
51
  ActiveFedora::Noid.config.template
24
52
  end
25
53
 
54
+ ##
55
+ # @return [String] a new identifier.
26
56
  def next_id
27
- raise NotImplementedError.new('Implement next_id in child class')
57
+ raise NotImplementedError.new('Implement #next_id in child class')
28
58
  end
29
59
  end
30
60
  end
@@ -4,6 +4,22 @@ module ActiveFedora
4
4
  module Noid
5
5
  module Minter
6
6
  class Db < Base
7
+
8
+ def read
9
+ filtered_hash = instance.as_json.select { |key| ['template', 'counters', 'seq', 'rand', 'namespace'].include?(key) }
10
+ filtered_hash['counters'] = JSON.parse(filtered_hash['counters'], symbolize_names: true) if filtered_hash['counters']
11
+ filtered_hash.symbolize_keys
12
+ end
13
+
14
+ def write!(minter)
15
+ # namespace and template are the same, now update the other attributes
16
+ instance.update_attributes!(
17
+ seq: minter.seq,
18
+ counters: JSON.generate(minter.counters),
19
+ rand: Marshal.dump(minter.instance_variable_get(:@rand))
20
+ )
21
+ end
22
+
7
23
  protected
8
24
 
9
25
  # Uses pessimistic lock to ensure the record fetched is the same one updated.
@@ -19,21 +35,20 @@ module ActiveFedora
19
35
  def next_id
20
36
  id = nil
21
37
  MinterState.transaction do
22
- state = MinterState.lock.where(
23
- namespace: ActiveFedora::Noid.config.namespace,
24
- template: ActiveFedora::Noid.config.template,
25
- ).first!
26
- minter = ::Noid::Minter.new(state.noid_options)
38
+ state = read
39
+ minter = ::Noid::Minter.new(state)
27
40
  id = minter.mint
28
- # namespace and template are the same, now update the other attributes
29
- state.seq = minter.seq
30
- state.counters = JSON.generate(minter.counters)
31
- state.random = Marshal.dump(minter.instance_variable_get(:@rand))
32
- state.save!
41
+ write!(minter)
33
42
  end # transaction
34
43
  id
35
44
  end
36
45
 
46
+ def instance
47
+ MinterState.lock.find_by(
48
+ namespace: ActiveFedora::Noid.config.namespace,
49
+ template: ActiveFedora::Noid.config.template,
50
+ )
51
+ end
37
52
  end # class Db
38
53
  end
39
54
  end
@@ -0,0 +1,60 @@
1
+ require 'noid'
2
+
3
+ module ActiveFedora
4
+ module Noid
5
+ module Minter
6
+ class File < Base
7
+ attr_reader :statefile
8
+
9
+ def initialize(template = default_template, statefile = default_statefile)
10
+ @statefile = statefile
11
+ super(template)
12
+ end
13
+
14
+ def default_statefile
15
+ ActiveFedora::Noid.config.statefile
16
+ end
17
+
18
+ def read
19
+ with_file do |f|
20
+ state_for(f)
21
+ end
22
+ end
23
+
24
+ def write!(minter)
25
+ with_file do |f|
26
+ # Wipe prior contents so the new state can be written from the beginning of the file
27
+ f.truncate(0)
28
+ f.write(Marshal.dump(minter.dump))
29
+ end
30
+ end
31
+
32
+ protected
33
+
34
+ def with_file
35
+ ::File.open(statefile, 'a+b', 0644) do |f|
36
+ f.flock(::File::LOCK_EX)
37
+ # Files opened in append mode seek to end of file
38
+ f.rewind
39
+ yield f
40
+ end
41
+ end
42
+
43
+ def state_for(io_object)
44
+ Marshal.load(io_object.read)
45
+ rescue TypeError, ArgumentError
46
+ { template: template }
47
+ end
48
+
49
+ def next_id
50
+ state = read
51
+ state[:template] &&= state[:template].to_s
52
+ minter = ::Noid::Minter.new(state) # minter w/in the minter, lives only for an instant
53
+ id = minter.mint
54
+ write!(minter)
55
+ id
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -20,7 +20,7 @@ module ActiveFedora
20
20
  protected
21
21
 
22
22
  def default_minter
23
- ActiveFedora::Noid::SynchronizedMinter.new
23
+ ActiveFedora::Noid.config.minter_class.new
24
24
  end
25
25
  end
26
26
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveFedora
2
2
  module Noid
3
- VERSION = '2.0.0.beta3'.freeze
3
+ VERSION = '2.0.0.beta4'.freeze
4
4
  end
5
5
  end
@@ -4,25 +4,51 @@ require 'yaml'
4
4
 
5
5
  namespace :active_fedora do
6
6
  namespace :noid do
7
- desc 'Migrate minter state file from YAML to Marshal'
8
- task :migrate_statefile do
9
- statefile = ENV.fetch('AFNOID_STATEFILE', ActiveFedora::Noid.config.statefile)
10
- raise "File not found: #{statefile}\nAborting" unless File.exist?(statefile)
11
- puts "Migrating #{statefile} from YAML to Marshal serialization..."
12
- File.open(statefile, 'a+b', 0644) do |f|
13
- f.flock(File::LOCK_EX)
14
- f.rewind
15
- begin
16
- yaml_state = YAML.load(f)
17
- rescue Psych::SyntaxError
18
- raise "File not valid YAML: #{statefile}\nAborting."
7
+ namespace :migrate do
8
+ desc 'Migrate minter state file from YAML to Marshal'
9
+ task :yaml_to_marshal do
10
+ statefile = ENV.fetch('AFNOID_STATEFILE', ActiveFedora::Noid.config.statefile)
11
+ raise "File not found: #{statefile}\nAborting" unless File.exist?(statefile)
12
+ puts "Migrating #{statefile} from YAML to Marshal serialization..."
13
+ File.open(statefile, 'a+b', 0644) do |f|
14
+ f.flock(File::LOCK_EX)
15
+ f.rewind
16
+ begin
17
+ yaml_state = YAML.load(f)
18
+ rescue Psych::SyntaxError
19
+ raise "File not valid YAML: #{statefile}\nAborting."
20
+ end
21
+ minter = Noid::Minter.new(yaml_state)
22
+ f.truncate(0)
23
+ new_state = Marshal.dump(minter.dump)
24
+ f.write(new_state)
19
25
  end
20
- minter = Noid::Minter.new(yaml_state)
21
- f.truncate(0)
22
- new_state = Marshal.dump(minter.dump)
23
- f.write(new_state)
26
+ puts "Done!"
27
+ end
28
+
29
+ desc 'Migrate minter state from file to database'
30
+ task file_to_database: :environment do
31
+ statefile = ENV.fetch('AFNOID_STATEFILE', ActiveFedora::Noid.config.statefile)
32
+ raise "File not found: #{statefile}\nAborting" unless File.exist?(statefile)
33
+ puts "Migrating #{statefile} to database..."
34
+ state = ActiveFedora::Noid::Minter::File.new.read
35
+ minter = Noid::Minter.new(state)
36
+ new_state = ActiveFedora::Noid::Minter::Db.new
37
+ new_state.write!(minter)
38
+ puts "Done!"
39
+ end
40
+
41
+ desc 'Migrate minter state from database to file'
42
+ task database_to_file: :environment do
43
+ statefile = ENV.fetch('AFNOID_STATEFILE', ActiveFedora::Noid.config.statefile)
44
+ raise "File already exists (delete it first if it's not valuable): #{statefile}\nAborting" if File.exist?(statefile)
45
+ puts "Migrating minter state from database to #{statefile}..."
46
+ state = ActiveFedora::Noid::Minter::Db.new.read
47
+ minter = Noid::Minter.new(state)
48
+ new_state = ActiveFedora::Noid::Minter::File.new
49
+ new_state.write!(minter)
50
+ puts "Done!"
24
51
  end
25
- puts "Done!"
26
52
  end
27
53
  end
28
54
  end
@@ -37,22 +37,4 @@ describe MinterState, type: :model do
37
37
  expect(described_class.group(:namespace).count).to eq('default' => 1, 'foobar' => 1)
38
38
  end
39
39
  end
40
-
41
- describe '#noid_options' do
42
- it 'returns nil without template (new object not persisted)' do
43
- expect(state.noid_options).to be_nil
44
- end
45
- it 'returns correct hash when populated' do
46
- state.template = '.reeddddk'
47
- state.seq = 1
48
- expect(state.noid_options).to match a_hash_including(
49
- :template => '.reeddddk',
50
- :seq => 1
51
- )
52
- expect(first.noid_options).to match a_hash_including(
53
- :template => '.reeddeeddk',
54
- :seq => 0
55
- )
56
- end
57
- end
58
40
  end
@@ -0,0 +1,55 @@
1
+ shared_examples 'a minter' do
2
+ describe '#mint' do
3
+ subject { minter.mint }
4
+ let(:other) { described_class.new('.reedddk') }
5
+
6
+ it { is_expected.not_to be_empty }
7
+
8
+ it 'does not mint the same ID twice in a row' do
9
+ expect(subject).not_to eq described_class.new.mint
10
+ end
11
+
12
+ it 'is valid' do
13
+ expect(minter.valid?(subject)).to be true
14
+ expect(described_class.new.valid?(subject)).to be true
15
+ end
16
+
17
+ it 'is invalid under a different template' do
18
+ expect(described_class.new('.reedddk').valid?(subject)).to be false
19
+ end
20
+
21
+ it 'is invalid under a different template' do
22
+ expect(other).not_to be_valid(minter.mint)
23
+ end
24
+ end
25
+
26
+ context 'conflicts' do
27
+ let(:existing_pid) { 'ef12ef12f' }
28
+ let(:unique_pid) { 'bb22bb22b' }
29
+
30
+ before :each do
31
+ expect(subject).to receive(:next_id).and_return(existing_pid, unique_pid)
32
+ end
33
+
34
+ context 'when the pid already exists in Fedora' do
35
+ before do
36
+ expect(ActiveFedora::Base).to receive(:exists?).with(existing_pid).and_return(true)
37
+ end
38
+ it 'skips the existing pid' do
39
+ expect(subject.mint).to eq unique_pid
40
+ end
41
+ end
42
+
43
+ context 'when the pid already existed in Fedora and now is gone' do
44
+ let(:gone_pid) { existing_pid }
45
+
46
+ before do
47
+ expect(ActiveFedora::Base).to receive(:gone?).with(gone_pid).and_return(true)
48
+ end
49
+
50
+ it 'skips the deleted pid' do
51
+ expect(subject.mint).to eq unique_pid
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,7 +1,10 @@
1
1
  describe ActiveFedora::Noid::Config do
2
+ subject { described_class.new }
3
+
2
4
  it { is_expected.to respond_to(:template) }
3
5
  it { is_expected.to respond_to(:statefile) }
4
6
  it { is_expected.to respond_to(:namespace) }
7
+ it { is_expected.to respond_to(:minter_class) }
5
8
  it { is_expected.to respond_to(:translate_id_to_uri) }
6
9
  it { is_expected.to respond_to(:translate_uri_to_id) }
7
10
 
@@ -23,6 +26,22 @@ describe ActiveFedora::Noid::Config do
23
26
  end
24
27
  end
25
28
 
29
+ describe '#minter_class' do
30
+ let(:default) { ActiveFedora::Noid::Minter::Db }
31
+
32
+ it 'has a default' do
33
+ expect(subject.minter_class).to eq default
34
+ end
35
+
36
+ context 'when overridden' do
37
+ before { subject.minter_class = different_minter }
38
+ let(:different_minter) { ActiveFedora::Noid::Minter::File }
39
+ it 'uses the different minter' do
40
+ expect(subject.minter_class).to eq different_minter
41
+ end
42
+ end
43
+ end
44
+
26
45
  describe '#translate_uri_to_id' do
27
46
  let(:config) { described_class.new }
28
47
  let(:translator) { config.translate_uri_to_id }
@@ -38,10 +57,10 @@ describe ActiveFedora::Noid::Config do
38
57
 
39
58
  context "with a hash code uri" do
40
59
  let(:uri) { "http://localhost:8983/fedora/rest/test/hh/63/vz/22/hh63vz22q#g123" }
41
- it { is_expected.to eq 'hh63vz22q#g123' }
60
+ it { is_expected.to eq 'hh63vz22q#g123' }
42
61
  end
43
62
 
44
- describe 'with a short custom template' do
63
+ context 'with a short custom template' do
45
64
  let(:uri) { "http://localhost:8983/fedora/rest/test/ab/cd/abcd/members" }
46
65
  let(:custom_template) { '.reeee' }
47
66
  before { config.template = custom_template }
@@ -50,7 +69,7 @@ describe ActiveFedora::Noid::Config do
50
69
  it { is_expected.to eq 'abcd/members' }
51
70
  end
52
71
 
53
- describe 'with an even shorter custom template' do
72
+ context 'with an even shorter custom template' do
54
73
  let(:uri) { "http://localhost:8983/fedora/rest/test/ab/c/abc/members" }
55
74
  let(:custom_template) { '.reee' }
56
75
  before { config.template = custom_template }
@@ -59,7 +78,7 @@ describe ActiveFedora::Noid::Config do
59
78
  it { is_expected.to eq 'abc/members' }
60
79
  end
61
80
 
62
- describe 'with a long custom template' do
81
+ context 'with a long custom template' do
63
82
  let(:uri) { "http://localhost:8983/fedora/rest/test/ab/cd/ef/gh/abcdefghijklmnopqrstuvwxyz/members" }
64
83
  let(:custom_template) { '.reeeeeeeeeeeeeeeeeeeeeeeeee' }
65
84
  before { config.template = custom_template }
@@ -67,6 +86,48 @@ describe ActiveFedora::Noid::Config do
67
86
 
68
87
  it { is_expected.to eq 'abcdefghijklmnopqrstuvwxyz/members' }
69
88
  end
89
+ end
70
90
 
91
+ describe '#translate_id_to_uri' do
92
+ let(:config) { described_class.new }
93
+ let(:translator) { config.translate_id_to_uri }
94
+ let(:id) { "hh63vz2/members" }
95
+ let(:ActiveFedora) { double(ActiveFedora) }
96
+ subject { translator.call(id) }
97
+ before do
98
+ allow(ActiveFedora).to receive_message_chain("fedora.host") { "http://localhost:8983" }
99
+ allow(ActiveFedora).to receive_message_chain("fedora.base_path") { "/fedora/rest/test" }
100
+ end
101
+
102
+ it { is_expected.to eq "http://localhost:8983/fedora/rest/test/hh/63/vz/2/hh63vz2/members" }
103
+
104
+ context "with a hash code id" do
105
+ let(:id) { 'hh63vz2#g123' }
106
+ it { is_expected.to eq "http://localhost:8983/fedora/rest/test/hh/63/vz/2/hh63vz2#g123" }
107
+ end
108
+
109
+ context 'with a short custom template' do
110
+ let(:id) { "abcd/members" }
111
+ let(:custom_template) { '.reeee' }
112
+ before { config.template = custom_template }
113
+ subject { translator.call(id) }
114
+ it { is_expected.to eq "http://localhost:8983/fedora/rest/test/ab/cd/abcd/members" }
115
+ end
116
+
117
+ context 'with an even shorter custom template' do
118
+ let(:id) { 'abc/members' }
119
+ let(:custom_template) { '.reee' }
120
+ before { config.template = custom_template }
121
+ subject { translator.call(id) }
122
+ it { is_expected.to eq "http://localhost:8983/fedora/rest/test/ab/c/abc/members" }
123
+ end
124
+
125
+ context 'with a long custom template' do
126
+ let(:id) { "abcdefghijklmnopqrstuvwxyz/members" }
127
+ let(:custom_template) { '.reeeeeeeeeeeeeeeeeeeeeeeeee' }
128
+ before { config.template = custom_template }
129
+ subject { translator.call(id) }
130
+ it { is_expected.to eq "http://localhost:8983/fedora/rest/test/ab/cd/ef/gh/abcdefghijklmnopqrstuvwxyz/members" }
131
+ end
71
132
  end
72
133
  end
@@ -2,7 +2,7 @@ include MinterStateHelper
2
2
 
3
3
  describe ActiveFedora::Noid::Minter::Db do
4
4
  before(:each) { reset_minter_state_table }
5
- after( :all ) { reset_minter_state_table }
5
+ after(:all) { reset_minter_state_table }
6
6
 
7
7
  before :each do
8
8
  # default novel mintings
@@ -10,19 +10,22 @@ describe ActiveFedora::Noid::Minter::Db do
10
10
  allow(ActiveFedora::Base).to receive(:gone?).and_return(false)
11
11
  end
12
12
 
13
- let(:minter) { described_class.new }
14
13
  let(:other) { described_class.new('.reedddk') }
15
14
 
15
+ it_behaves_like 'a minter' do
16
+ let(:minter) { described_class.new }
17
+ end
18
+
16
19
  describe '#initialize' do
17
20
  it 'raises on bad templates' do
18
21
  expect{ described_class.new('reeddeeddk') }.to raise_error(Noid::TemplateError)
19
22
  expect{ described_class.new('') }.to raise_error(Noid::TemplateError)
20
23
  end
21
24
  it 'returns object w/ default template' do
22
- expect(minter).to be_instance_of described_class
23
- expect(minter).to be_a Noid::Minter
24
- expect(minter.template).to be_instance_of Noid::Template
25
- expect(minter.template.to_s).to eq ActiveFedora::Noid.config.template
25
+ expect(subject).to be_instance_of described_class
26
+ expect(subject).to be_a Noid::Minter
27
+ expect(subject.template).to be_instance_of Noid::Template
28
+ expect(subject.template.to_s).to eq ActiveFedora::Noid.config.template
26
29
  end
27
30
  it 'accepts valid template arg' do
28
31
  expect(other).to be_instance_of described_class
@@ -32,45 +35,29 @@ describe ActiveFedora::Noid::Minter::Db do
32
35
  end
33
36
  end
34
37
 
35
- describe '#mint' do
36
- subject { minter.mint }
37
- it { is_expected.not_to be_empty }
38
- it 'does not mint the same ID twice in a row' do
39
- expect(subject).not_to eq described_class.new.mint
38
+ describe '#read' do
39
+ it 'returns a hash' do
40
+ expect(subject.read).to be_a(Hash)
40
41
  end
41
- it 'is valid' do
42
- expect(minter.valid?(subject)).to be true
43
- expect(described_class.new.valid?(subject)).to be true
42
+ it 'has the expected namespace' do
43
+ expect(subject.read[:namespace]).to eq ActiveFedora::Noid.config.namespace
44
44
  end
45
- it 'is invalid under a different template' do
46
- expect(described_class.new('.reedddk').valid?(subject)).to be false
45
+ it 'has the expected template' do
46
+ expect(subject.read[:template]).to eq ActiveFedora::Noid.config.template
47
47
  end
48
48
  end
49
49
 
50
- context 'conflicts' do
51
- let(:existing_pid) { 'ef12ef12f' }
52
- let(:unique_pid) { 'bb22bb22b' }
53
- before :each do
54
- expect(minter).to receive(:next_id).and_return(existing_pid, unique_pid)
55
- end
56
-
57
- context 'when the pid already exists in Fedora' do
58
- before do
59
- expect(ActiveFedora::Base).to receive(:exists?).with(existing_pid).and_return(true)
60
- end
61
- it 'skips the existing pid' do
62
- expect(minter.mint).to eq unique_pid
63
- end
64
- end
65
-
66
- context 'when the pid already existed in Fedora and now is gone' do
67
- let(:gone_pid) { existing_pid }
68
- before do
69
- expect(ActiveFedora::Base).to receive(:gone?).with(gone_pid).and_return(true)
70
- end
71
- it 'skips the deleted pid' do
72
- expect(minter.mint).to eq unique_pid
73
- end
50
+ describe '#write!' do
51
+ let(:starting_state) { subject.read }
52
+ let(:minter) { Noid::Minter.new(starting_state) }
53
+ before { minter.mint }
54
+ it 'changes the state of the minter' do
55
+ expect { subject.write!(minter) }.to change { subject.read[:seq] }
56
+ .from(starting_state[:seq]).to(minter.seq)
57
+ .and change { subject.read[:counters] }
58
+ .from(starting_state[:counters]).to(minter.counters)
59
+ .and change { subject.read[:rand] }
60
+ .from(starting_state[:rand]).to(Marshal.dump(minter.instance_variable_get(:@rand)))
74
61
  end
75
62
  end
76
63
  end
@@ -0,0 +1,58 @@
1
+ describe ActiveFedora::Noid::Minter::File do
2
+ before :each do
3
+ # default novel mintings
4
+ allow(ActiveFedora::Base).to receive(:exists?).and_return(false)
5
+ allow(ActiveFedora::Base).to receive(:gone?).and_return(false)
6
+ end
7
+
8
+ it { is_expected.to respond_to(:mint) }
9
+
10
+ it 'has a default statefile' do
11
+ expect(subject.statefile).to eq ActiveFedora::Noid.config.statefile
12
+ end
13
+ it 'has a default template' do
14
+ expect(subject.template.to_s).to eq ActiveFedora::Noid.config.template
15
+ end
16
+
17
+ it_behaves_like 'a minter' do
18
+ let(:minter) { described_class.new }
19
+ end
20
+
21
+ describe '#initialize' do
22
+ let(:template) { '.rededk' }
23
+ let(:statefile) { '/tmp/foobar' }
24
+
25
+ subject { described_class.new(template, statefile) }
26
+
27
+ it 'respects the custom template' do
28
+ expect(subject.template.to_s).to eq template
29
+ end
30
+ it 'respects the custom statefile' do
31
+ expect(subject.statefile).to eq statefile
32
+ end
33
+ end
34
+
35
+ describe '#read' do
36
+ it 'returns a hash' do
37
+ expect(subject.read).to be_a(Hash)
38
+ end
39
+ it 'has the expected template' do
40
+ expect(subject.read[:template]).to eq ActiveFedora::Noid.config.template
41
+ end
42
+ end
43
+
44
+ describe '#write!' do
45
+ let(:starting_state) { subject.read }
46
+ let(:minter) { Noid::Minter.new(starting_state) }
47
+ before { minter.mint }
48
+ it 'changes the state of the minter' do
49
+ expect { subject.write!(minter) }.to change { subject.read[:seq] }
50
+ .from(starting_state[:seq]).to(minter.seq)
51
+ .and change { subject.read[:rand] }
52
+ .from(starting_state[:rand]).to(Marshal.dump(minter.instance_variable_get(:@rand)))
53
+ .and change { subject.read[:counters] }
54
+ .to(minter.counters)
55
+
56
+ end
57
+ end
58
+ end
@@ -13,5 +13,9 @@ describe ActiveFedora::Noid do
13
13
  subject { ActiveFedora::Noid.treeify(id) }
14
14
  let(:id) { 'abc123def45' }
15
15
  it { is_expected.to eq 'ab/c1/23/de/abc123def45' }
16
+ context 'with a seven-digit identifier' do
17
+ let(:id) { 'abc123z' }
18
+ it { is_expected.to eq 'ab/c1/23/z/abc123z' }
19
+ end
16
20
  end
17
21
  end
@@ -5,7 +5,7 @@ describe ActiveFedora::Noid::Service do
5
5
  end
6
6
 
7
7
  it 'has a default minter' do
8
- expect(subject.minter).to be_instance_of ActiveFedora::Noid::SynchronizedMinter
8
+ expect(subject.minter).to be_instance_of ActiveFedora::Noid::Minter::Db
9
9
  end
10
10
 
11
11
  context 'with a custom minter' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_fedora-noid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta3
4
+ version: 2.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael J. Giarlo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-13 00:00:00.000000000 Z
11
+ date: 2016-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active-fedora
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 4.2.6
53
+ version: 4.2.7.1
54
54
  - - "<"
55
55
  - !ruby/object:Gem::Version
56
56
  version: '6'
@@ -60,7 +60,7 @@ dependencies:
60
60
  requirements:
61
61
  - - ">="
62
62
  - !ruby/object:Gem::Version
63
- version: 4.2.6
63
+ version: 4.2.7.1
64
64
  - - "<"
65
65
  - !ruby/object:Gem::Version
66
66
  version: '6'
@@ -84,14 +84,14 @@ dependencies:
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '10.0'
87
+ version: '11.0'
88
88
  type: :development
89
89
  prerelease: false
90
90
  version_requirements: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '10.0'
94
+ version: '11.0'
95
95
  - !ruby/object:Gem::Dependency
96
96
  name: rspec
97
97
  requirement: !ruby/object:Gem::Requirement
@@ -126,14 +126,14 @@ dependencies:
126
126
  requirements:
127
127
  - - "~>"
128
128
  - !ruby/object:Gem::Version
129
- version: '0.8'
129
+ version: '1.0'
130
130
  type: :development
131
131
  prerelease: false
132
132
  version_requirements: !ruby/object:Gem::Requirement
133
133
  requirements:
134
134
  - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '0.8'
136
+ version: '1.0'
137
137
  description: Noid identifier services for ActiveFedora-based applications.
138
138
  email:
139
139
  - leftwing@alumni.rutgers.edu
@@ -152,13 +152,15 @@ files:
152
152
  - active_fedora-noid.gemspec
153
153
  - app/models/minter_state.rb
154
154
  - db/migrate/20160610010003_create_minter_states.rb
155
+ - db/migrate/20161021203429_rename_minter_state_random_to_rand.rb
155
156
  - lib/active_fedora/noid.rb
156
157
  - lib/active_fedora/noid/config.rb
157
158
  - lib/active_fedora/noid/engine.rb
159
+ - lib/active_fedora/noid/minter.rb
158
160
  - lib/active_fedora/noid/minter/base.rb
159
161
  - lib/active_fedora/noid/minter/db.rb
162
+ - lib/active_fedora/noid/minter/file.rb
160
163
  - lib/active_fedora/noid/service.rb
161
- - lib/active_fedora/noid/synchronized_minter.rb
162
164
  - lib/active_fedora/noid/version.rb
163
165
  - lib/generators/active_fedora/noid/install_generator.rb
164
166
  - lib/generators/active_fedora/noid/seed_generator.rb
@@ -166,12 +168,13 @@ files:
166
168
  - spec/models/minter_state_spec.rb
167
169
  - spec/spec_helper.rb
168
170
  - spec/support/minterstate_table.rb
171
+ - spec/support/shared_examples/minter.rb
169
172
  - spec/test_app_templates/lib/generators/test_app_generator.rb
170
173
  - spec/unit/config_spec.rb
171
174
  - spec/unit/db_minter_spec.rb
175
+ - spec/unit/file_minter_spec.rb
172
176
  - spec/unit/noid_spec.rb
173
177
  - spec/unit/service_spec.rb
174
- - spec/unit/synchronized_minter_spec.rb
175
178
  homepage: https://github.com/projecthydra-labs/active_fedora-noid
176
179
  licenses:
177
180
  - Apache2
@@ -200,9 +203,10 @@ test_files:
200
203
  - spec/models/minter_state_spec.rb
201
204
  - spec/spec_helper.rb
202
205
  - spec/support/minterstate_table.rb
206
+ - spec/support/shared_examples/minter.rb
203
207
  - spec/test_app_templates/lib/generators/test_app_generator.rb
204
208
  - spec/unit/config_spec.rb
205
209
  - spec/unit/db_minter_spec.rb
210
+ - spec/unit/file_minter_spec.rb
206
211
  - spec/unit/noid_spec.rb
207
212
  - spec/unit/service_spec.rb
208
- - spec/unit/synchronized_minter_spec.rb
@@ -1,45 +0,0 @@
1
- require 'noid'
2
-
3
- module ActiveFedora
4
- module Noid
5
- class SynchronizedMinter < Minter::Base
6
- attr_reader :statefile
7
-
8
- def initialize(template = default_template, statefile = default_statefile)
9
- super(template)
10
- @statefile = statefile
11
- end
12
-
13
- protected
14
-
15
- def default_statefile
16
- ActiveFedora::Noid.config.statefile
17
- end
18
-
19
- def state_for(io_object)
20
- Marshal.load(io_object.read)
21
- rescue TypeError, ArgumentError
22
- { template: template }
23
- end
24
-
25
- def next_id
26
- id = nil
27
- ::File.open(statefile, 'a+b', 0644) do |f|
28
- f.flock(::File::LOCK_EX)
29
- # Files opened in append mode seek to end of file
30
- f.rewind
31
- state = state_for(f)
32
- state[:template] &&= state[:template].to_s
33
- minter = ::Noid::Minter.new(state) # minter w/in the minter, lives only for an instant
34
- id = minter.mint
35
-
36
- # Wipe prior contents so the new state can be written from the beginning of the file
37
- f.truncate(0)
38
- new_state = Marshal.dump(minter.dump)
39
- f.write(new_state)
40
- end
41
- id
42
- end
43
- end
44
- end
45
- end
@@ -1,70 +0,0 @@
1
- describe ActiveFedora::Noid::SynchronizedMinter do
2
- before :each do
3
- # default novel mintings
4
- allow(ActiveFedora::Base).to receive(:exists?).and_return(false)
5
- allow(ActiveFedora::Base).to receive(:gone?).and_return(false)
6
- end
7
-
8
- let(:minter) { described_class.new }
9
-
10
- it { is_expected.to respond_to(:mint) }
11
- it 'has a default statefile' do
12
- expect(subject.statefile).to eq ActiveFedora::Noid.config.statefile
13
- end
14
- it 'has a default template' do
15
- expect(subject.template.to_s).to eq ActiveFedora::Noid.config.template
16
- end
17
-
18
- describe '#initialize' do
19
- let(:template) { '.rededk' }
20
- let(:statefile) { '/tmp/foobar' }
21
-
22
- subject { described_class.new(template, statefile) }
23
-
24
- it 'respects the custom template' do
25
- expect(subject.template.to_s).to eq template
26
- end
27
- it 'respects the custom statefile' do
28
- expect(subject.statefile).to eq statefile
29
- end
30
- end
31
-
32
- describe '#mint' do
33
- subject { minter.mint }
34
- it { is_expected.not_to be_empty }
35
- it 'does not mint the same ID twice in a row' do
36
- expect(subject).not_to eq described_class.new.mint
37
- end
38
- it 'is valid' do
39
- expect(minter.valid?(subject)).to be true
40
- expect(described_class.new.valid?(subject)).to be true
41
- end
42
- end
43
-
44
- context 'conflicts' do
45
- let(:unique_pid) { 'bb22bb22b' }
46
- let(:existing_pid) { 'ef12ef12f' }
47
- before :each do
48
- expect(minter).to receive(:next_id).and_return(existing_pid, unique_pid)
49
- end
50
-
51
- context 'when the pid already exists in Fedora' do
52
- before do
53
- expect(ActiveFedora::Base).to receive(:exists?).with(existing_pid).and_return(true)
54
- end
55
- it 'skips the existing pid' do
56
- expect(minter.mint).to eq unique_pid
57
- end
58
- end
59
-
60
- context 'when the pid already existed in Fedora and now is gone' do
61
- let(:gone_pid) { existing_pid }
62
- before do
63
- expect(ActiveFedora::Base).to receive(:gone?).with(gone_pid).and_return(true)
64
- end
65
- it 'skips the deleted pid' do
66
- expect(minter.mint).to eq unique_pid
67
- end
68
- end
69
- end
70
- end