active_fedora-noid 2.0.0.beta3 → 2.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
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