noid-rails 3.0.0

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +36 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +24 -0
  5. data/.rubocop_todo.yml +107 -0
  6. data/.travis.yml +11 -0
  7. data/CONTRIBUTING.md +159 -0
  8. data/Gemfile +39 -0
  9. data/LICENSE +15 -0
  10. data/README.md +214 -0
  11. data/Rakefile +23 -0
  12. data/app/models/minter_state.rb +16 -0
  13. data/db/migrate/20160610010003_create_minter_states.rb +16 -0
  14. data/db/migrate/20161021203429_rename_minter_state_random_to_rand.rb +7 -0
  15. data/lib/generators/noid/rails/install_generator.rb +22 -0
  16. data/lib/generators/noid/rails/seed_generator.rb +37 -0
  17. data/lib/noid/rails/config.rb +35 -0
  18. data/lib/noid/rails/engine.rb +10 -0
  19. data/lib/noid/rails/minter/base.rb +65 -0
  20. data/lib/noid/rails/minter/db.rb +82 -0
  21. data/lib/noid/rails/minter/file.rb +65 -0
  22. data/lib/noid/rails/minter.rb +5 -0
  23. data/lib/noid/rails/rspec.rb +67 -0
  24. data/lib/noid/rails/service.rb +26 -0
  25. data/lib/noid/rails/version.rb +7 -0
  26. data/lib/noid-rails.rb +29 -0
  27. data/lib/tasks/noid_tasks.rake +59 -0
  28. data/noid-rails.gemspec +31 -0
  29. data/spec/models/minter_state_spec.rb +41 -0
  30. data/spec/spec_helper.rb +40 -0
  31. data/spec/support/minterstate_table.rb +13 -0
  32. data/spec/support/shared_examples/minter.rb +39 -0
  33. data/spec/test_app_templates/lib/generators/test_app_generator.rb +16 -0
  34. data/spec/unit/config_spec.rb +45 -0
  35. data/spec/unit/db_minter_spec.rb +88 -0
  36. data/spec/unit/file_minter_spec.rb +54 -0
  37. data/spec/unit/noid_spec.rb +43 -0
  38. data/spec/unit/rspec_spec.rb +90 -0
  39. data/spec/unit/service_spec.rb +32 -0
  40. metadata +225 -0
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RenameMinterStateRandomToRand < ActiveRecord::Migration[4.2]
4
+ def change
5
+ rename_column :minter_states, :random, :rand
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noid
4
+ module Rails
5
+ # Generates the migrations into the host application
6
+ class InstallGenerator < ::Rails::Generators::Base
7
+ source_root ::File.expand_path('../templates', __FILE__)
8
+
9
+ desc <<~DESCRIPTION
10
+ Copies DB migrations
11
+ DESCRIPTION
12
+
13
+ def banner
14
+ say_status('info', 'Installing noid-rails', :blue)
15
+ end
16
+
17
+ def migrations
18
+ rake 'noid_rails_engine:install:migrations'
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noid
4
+ module Rails
5
+ # Initializes the database with a noid namespace
6
+ class SeedGenerator < ::Rails::Generators::Base
7
+ source_root ::File.expand_path('../templates', __FILE__)
8
+ argument :namespace, type: :string, default: Noid::Rails.config.namespace
9
+ argument :template, type: :string, default: Noid::Rails.config.template
10
+
11
+ desc <<~DESCRIPTION
12
+ Seeds DB from Noid::Rails.config (or command-line overrides)
13
+ DESCRIPTION
14
+
15
+ def banner
16
+ say_status('info', "Initializing database table for namespace:template of '#{namespace}:#{template}'", :blue)
17
+ end
18
+
19
+ def checks
20
+ if namespace != Noid::Rails.config.namespace
21
+ say_status('warn', 'Be sure to use an initializer to do ' \
22
+ "'Noid::Rails.config.namespace = #{namespace}'", :red)
23
+ end
24
+ return if template == Noid::Rails.config.template
25
+ say_status('warn', 'Be sure to use an initializer to do ' \
26
+ "Noid::Rails.config.template = #{template}'", :red)
27
+ end
28
+
29
+ def seed_row
30
+ MinterState.seed!(
31
+ namespace: namespace,
32
+ template: template
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noid
4
+ module Rails
5
+ # Configuration parameters for creating identifiers
6
+ class Config
7
+ attr_writer :template, :statefile, :namespace, :minter_class, :identifier_in_use
8
+
9
+ def template
10
+ @template ||= '.reeddeeddk'
11
+ end
12
+
13
+ def statefile
14
+ @statefile ||= '/tmp/minter-state'
15
+ end
16
+
17
+ def namespace
18
+ @namespace ||= 'default'
19
+ end
20
+
21
+ def minter_class
22
+ @minter_class ||= Minter::File
23
+ end
24
+
25
+ # A check to guarantee the identifier is not already in use. When true,
26
+ # the minter will continue to cycle through ids until it finds one that
27
+ # returns false
28
+ def identifier_in_use
29
+ @identifier_in_use = lambda do |_id|
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module Noid
6
+ module Rails
7
+ class Engine < ::Rails::Engine
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'noid'
4
+
5
+ module Noid
6
+ module Rails
7
+ module Minter
8
+ # @abstract the base class for minters
9
+ class Base < ::Noid::Minter
10
+ ##
11
+ # @param template [#to_s] a NOID template
12
+ # @see Noid::Template
13
+ def initialize(template = default_template)
14
+ super(template: template.to_s)
15
+ end
16
+
17
+ ##
18
+ # Sychronously mint a new identifier.
19
+ #
20
+ # @return [String] the minted identifier
21
+ def mint
22
+ Mutex.new.synchronize do
23
+ loop do
24
+ pid = next_id
25
+ return pid unless identifier_in_use?(pid)
26
+ end
27
+ end
28
+ end
29
+
30
+ ##
31
+ # @return [Hash{Symbol => String, Object}] representation of the current minter state
32
+ def read
33
+ raise NotImplementedError, '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
+ # @return [void]
41
+ def write!(_)
42
+ raise NotImplementedError, 'Implement #write! in child class'
43
+ end
44
+
45
+ private
46
+
47
+ def identifier_in_use?(id)
48
+ Noid::Rails.config.identifier_in_use.call(id)
49
+ end
50
+
51
+ ##
52
+ # @return [#to_s] the default template for this
53
+ def default_template
54
+ Noid::Rails.config.template
55
+ end
56
+
57
+ ##
58
+ # @return [String] a new identifier
59
+ def next_id
60
+ raise NotImplementedError, 'Implement #next_id in child class'
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'noid'
4
+
5
+ module Noid
6
+ module Rails
7
+ module Minter
8
+ # A minter backed by a database table. You would select this if you
9
+ # need to mint identifers on several distributed front-ends that do not
10
+ # share a common file system.
11
+ class Db < Base
12
+ def read
13
+ deserialize(instance)
14
+ end
15
+
16
+ def write!(minter)
17
+ serialize(instance, minter)
18
+ end
19
+
20
+ protected
21
+
22
+ # @param [MinterState] inst minter state to be converted
23
+ # @return [Hash{Symbol => String, Object}] minter state as a Hash, like #read
24
+ # @see #read, Noid::Rails::Minter::Base#read
25
+ def deserialize(inst)
26
+ filtered_hash = inst.as_json.slice('template', 'counters', 'seq', 'rand', 'namespace')
27
+ if filtered_hash['counters']
28
+ filtered_hash['counters'] = JSON.parse(filtered_hash['counters'],
29
+ symbolize_names: true)
30
+ end
31
+ filtered_hash.symbolize_keys
32
+ end
33
+
34
+ # @param [MinterState] inst a locked row/object to be updated
35
+ # @param [::Noid::Minter] minter state containing the updates
36
+ def serialize(inst, minter)
37
+ # namespace and template are the same, now update the other attributes
38
+ inst.update_attributes!(
39
+ seq: minter.seq,
40
+ counters: JSON.generate(minter.counters),
41
+ rand: Marshal.dump(minter.instance_variable_get(:@rand))
42
+ )
43
+ end
44
+
45
+ # Uses pessimistic lock to ensure the record fetched is the same one updated.
46
+ # Should be fast enough to avoid terrible deadlock.
47
+ # Must lock because of multi-connection context! (transaction is per connection -- not enough)
48
+ # The DB table will only ever have at most one row per namespace.
49
+ # The 'default' namespace row is inserted by `rails generate noid:rails:seed`
50
+ # or autofilled by instance below.
51
+ # If you want another namespace, edit your config initialzer to something like:
52
+ # Noid::Rails.config.namespace = 'druid'
53
+ # Noid::Rails.config.template = '.reeedek'
54
+ # and in your app run:
55
+ # bundle exec rails generate noid:rails:seed
56
+ def next_id
57
+ id = nil
58
+ MinterState.transaction do
59
+ locked = instance
60
+ minter = ::Noid::Minter.new(deserialize(locked))
61
+ id = minter.mint
62
+ serialize(locked, minter)
63
+ end
64
+ id
65
+ end
66
+
67
+ # @return [MinterState]
68
+ def instance
69
+ MinterState.lock.find_by!(
70
+ namespace: Noid::Rails.config.namespace,
71
+ template: Noid::Rails.config.template
72
+ )
73
+ rescue ActiveRecord::RecordNotFound
74
+ MinterState.seed!(
75
+ namespace: Noid::Rails.config.namespace,
76
+ template: Noid::Rails.config.template
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'noid'
4
+
5
+ module Noid
6
+ module Rails
7
+ module Minter
8
+ # A file based minter. This is a simple case.
9
+ class File < Base
10
+ attr_reader :statefile
11
+
12
+ def initialize(template = default_template, statefile = default_statefile)
13
+ @statefile = statefile
14
+ super(template)
15
+ end
16
+
17
+ def default_statefile
18
+ Noid::Rails.config.statefile
19
+ end
20
+
21
+ def read
22
+ with_file do |f|
23
+ state_for(f)
24
+ end
25
+ end
26
+
27
+ def write!(minter)
28
+ with_file do |f|
29
+ # Wipe prior contents so the new state can be written from the beginning of the file
30
+ f.truncate(0)
31
+ f.write(Marshal.dump(minter.dump))
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def with_file
38
+ ::File.open(statefile, 'a+b', 0o644) do |f|
39
+ f.flock(::File::LOCK_EX)
40
+ # Files opened in append mode seek to end of file
41
+ f.rewind
42
+ yield f
43
+ end
44
+ end
45
+
46
+ # rubocop:disable Security/MarshalLoad
47
+ def state_for(io_object)
48
+ Marshal.load(io_object.read)
49
+ rescue TypeError, ArgumentError
50
+ { template: template }
51
+ end
52
+ # rubocop:enable Security/MarshalLoad
53
+
54
+ def next_id
55
+ state = read
56
+ state[:template] &&= state[:template].to_s
57
+ minter = ::Noid::Minter.new(state) # minter w/in the minter, lives only for an instant
58
+ id = minter.mint
59
+ write!(minter)
60
+ id
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'minter/base'
4
+ require_relative 'minter/file'
5
+ require_relative 'minter/db'
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noid
4
+ module Rails
5
+ ##
6
+ # Provides a test minter conforming to the `Noid::Rails::Minter`
7
+ # interface for use in unit tests. The test minter is faster and avoids
8
+ # unexpected interactions with cleanup code commonly runs in test suites
9
+ # (e.g. database cleanup).
10
+ #
11
+ # Applications should reenable their production minter for integration tests
12
+ # when appropriate
13
+ #
14
+ # @example general use
15
+ # Noid::Rails::RSpec.disable_production_minter!
16
+ # # some unit tests with the test minter
17
+ # Noid::Rails::RSpec.enable_production_minter!
18
+ # # some integration tests with the original minter
19
+ #
20
+ # @example using a custom test minter
21
+ # Noid::Rails::RSpec.disable_production_minter!(test_minter: Minter)
22
+ #
23
+ # @example use when included in RSpec config
24
+ # require 'noid/rails/rspec'
25
+ #
26
+ # RSpec.configure do |config|
27
+ # config.include(Noid::Rails::RSpec)
28
+ # end
29
+ #
30
+ # before(:suite) { disable_production_minter! }
31
+ # after(:suite) { enable_production_minter! }
32
+ #
33
+ module RSpec
34
+ DEFAULT_TEST_MINTER = Noid::Rails::Minter::File
35
+
36
+ ##
37
+ # Replaces the configured production minter with a test minter.
38
+ #
39
+ # @param test_minter [Class] a Noid::Rails::Minter implementation
40
+ # to use as a replacement minter
41
+ # @return [void]
42
+ def disable_production_minter!(test_minter: DEFAULT_TEST_MINTER)
43
+ return nil if @original_minter
44
+
45
+ @original_minter = Noid::Rails.config.minter_class
46
+
47
+ Noid::Rails.configure do |noid_config|
48
+ noid_config.minter_class = test_minter
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Re-enables the original configured minter.
54
+ #
55
+ # @return [void]
56
+ def enable_production_minter!
57
+ return nil unless @original_minter
58
+
59
+ Noid::Rails.configure do |noid_config|
60
+ noid_config.minter_class = @original_minter
61
+ end
62
+
63
+ @original_minter = nil
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'noid'
4
+
5
+ module Noid
6
+ module Rails
7
+ # A service that validates and mints identifiers
8
+ class Service
9
+ attr_reader :minter
10
+
11
+ def initialize(minter = default_minter)
12
+ @minter = minter
13
+ end
14
+
15
+ delegate :valid?, to: :minter
16
+
17
+ delegate :mint, to: :minter
18
+
19
+ protected
20
+
21
+ def default_minter
22
+ Noid::Rails.config.minter_class.new
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noid
4
+ module Rails
5
+ VERSION = '3.0.0'
6
+ end
7
+ end
data/lib/noid-rails.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'noid/rails/version'
4
+ require 'noid/rails/config'
5
+ require 'noid/rails/engine'
6
+ require 'noid/rails/service'
7
+ require 'noid/rails/minter'
8
+
9
+ module Noid
10
+ # A package to integrate Noid identifers with Rails projects
11
+ module Rails
12
+ class << self
13
+ def configure
14
+ yield config
15
+ end
16
+
17
+ def config
18
+ @config ||= Config.new
19
+ end
20
+
21
+ def treeify(identifier)
22
+ raise ArgumentError, 'Identifier must be a string of size > 0 in order to be treeified' if identifier.blank?
23
+ head = identifier.split('/').first
24
+ head.gsub!(/#.*/, '')
25
+ (head.scan(/..?/).first(4) + [identifier]).join('/')
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'noid-rails'
4
+ require 'noid'
5
+ require 'yaml'
6
+
7
+ namespace :noid do
8
+ namespace :rails do
9
+ namespace :migrate do
10
+ desc 'Migrate minter state file from YAML to Marshal'
11
+ task :yaml_to_marshal do
12
+ statefile = ENV.fetch('RAILS_NOID_STATEFILE', Noid::Rails.config.statefile)
13
+ raise "File not found: #{statefile}\nAborting" unless File.exist?(statefile)
14
+ puts "Migrating #{statefile} from YAML to Marshal serialization..."
15
+ File.open(statefile, 'a+b', 0o644) do |f|
16
+ f.flock(File::LOCK_EX)
17
+ f.rewind
18
+ begin
19
+ yaml_state = YAML.safe_load(f)
20
+ rescue Psych::SyntaxError
21
+ raise "File not valid YAML: #{statefile}\nAborting."
22
+ end
23
+ minter = Noid::Minter.new(yaml_state)
24
+ f.truncate(0)
25
+ new_state = Marshal.dump(minter.dump)
26
+ f.write(new_state)
27
+ end
28
+ puts 'Done!'
29
+ end
30
+
31
+ desc 'Migrate minter state from file to database'
32
+ task file_to_database: :environment do
33
+ statefile = ENV.fetch('RAILS_NOID_STATEFILE', Noid::Rails.config.statefile)
34
+ raise "File not found: #{statefile}\nAborting" unless File.exist?(statefile)
35
+ puts "Migrating #{statefile} to database..."
36
+ state = Noid::Rails::Minter::File.new.read
37
+ minter = Noid::Minter.new(state)
38
+ new_state = Noid::Rails::Minter::Db.new
39
+ new_state.write!(minter)
40
+ puts 'Done!'
41
+ end
42
+
43
+ desc 'Migrate minter state from database to file'
44
+ task database_to_file: :environment do
45
+ statefile = ENV.fetch('RAILS_NOID_STATEFILE', Noid::Rails.config.statefile)
46
+ if File.exist?(statefile)
47
+ raise "File already exists (delete it first if it's not valuable): " \
48
+ "#{statefile}\nAborting"
49
+ end
50
+ puts "Migrating minter state from database to #{statefile}..."
51
+ state = Noid::Rails::Minter::Db.new.read
52
+ minter = Noid::Minter.new(state)
53
+ new_state = Noid::Rails::Minter::File.new
54
+ new_state.write!(minter)
55
+ puts 'Done!'
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'noid/rails/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'noid-rails'
9
+ spec.version = Noid::Rails::VERSION
10
+ spec.authors = ['Michael J. Giarlo']
11
+ spec.email = ['leftwing@alumni.rutgers.edu']
12
+ spec.summary = 'Noid identifier services for Rails-based applications'
13
+ spec.description = 'Noid identifier services for Rails-based applications.'
14
+ spec.homepage = 'https://github.com/samvera/noid-rails'
15
+ spec.license = 'Apache2'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.test_files = spec.files.grep(%r{^spec/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'actionpack', '>= 5.0.0', '< 6'
22
+ spec.add_dependency 'noid', '~> 0.9'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'engine_cart', '~> 1.0'
26
+ spec.add_development_dependency 'rake', '>= 11'
27
+ spec.add_development_dependency 'rspec', '~> 3.2'
28
+ spec.add_development_dependency 'rubocop', '~> 0.52.0'
29
+ spec.add_development_dependency 'rubocop-rspec', '~> 1.20.1'
30
+ spec.add_development_dependency 'sqlite3'
31
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe MinterState, type: :model do
4
+ include MinterStateHelper
5
+ before { reset_minter_state_table }
6
+ after { reset_minter_state_table }
7
+
8
+ let(:state) { described_class.new }
9
+ let(:first) { described_class.first }
10
+
11
+ it 'db is seeded with first row' do
12
+ expect { first }.not_to raise_error
13
+ expect(first.namespace).to eq 'default'
14
+ expect(first.template).to eq '.reeddeeddk'
15
+ expect(first.seq).to eq 0
16
+ expect(described_class.group(:namespace).count).to eq('default' => 1)
17
+ end
18
+ describe 'validation' do
19
+ it 'blocks invalid template' do
20
+ expect { state.save! }.to raise_error(ActiveRecord::RecordInvalid) # empty
21
+ state.template = 'bad_template'
22
+ expect { state.save! }.to raise_error(ActiveRecord::RecordInvalid)
23
+ state.template = 'reeddddk' # close, but missing '.'
24
+ expect { state.save! }.to raise_error(ActiveRecord::RecordInvalid)
25
+ end
26
+ it 'allows valid template (edit)' do
27
+ first.template = '.reeddddk'
28
+ expect { first.save! }.not_to raise_error # OK!
29
+ end
30
+ it 'blocks new record in same namespace' do
31
+ state.template = '.reeddddk'
32
+ expect { state.save! }.to raise_error(ActiveRecord::RecordInvalid)
33
+ end
34
+ it 'allows new record in distinct namespace' do
35
+ state.template = '.reeddddk'
36
+ state.namespace = 'foobar'
37
+ expect { state.save! }.not_to raise_error # OK!
38
+ expect(described_class.group(:namespace).count).to eq('default' => 1, 'foobar' => 1)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+
5
+ def coverage_needed?
6
+ ENV['COVERAGE'] || ENV['TRAVIS']
7
+ end
8
+
9
+ if coverage_needed?
10
+ require 'simplecov'
11
+ require 'coveralls'
12
+ SimpleCov.root(File.expand_path('../..', __FILE__))
13
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
14
+ SimpleCov.start('rails') do
15
+ add_filter '/.internal_test_app'
16
+ add_filter '/lib/generators'
17
+ add_filter '/spec'
18
+ add_filter '/lib/noid/rails/version.rb'
19
+ end
20
+ SimpleCov.command_name 'spec'
21
+ end
22
+
23
+ require 'engine_cart'
24
+ EngineCart.load_application!
25
+
26
+ require 'noid-rails'
27
+ require 'byebug' unless ENV['CI']
28
+
29
+ Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f }
30
+
31
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
32
+ RSpec.configure do |config|
33
+ config.expect_with :rspec do |expectations|
34
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
35
+ end
36
+ config.mock_with :rspec do |mocks|
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+ config.filter_run_when_matching :focus
40
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MinterStateHelper
4
+ # Simple truncation is not enough, since we also need seed data
5
+ def reset_minter_state_table
6
+ MinterState.destroy_all
7
+ MinterState.create!(
8
+ namespace: 'default',
9
+ template: '.reeddeeddk',
10
+ seq: 0
11
+ )
12
+ end
13
+ end