noid-rails 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +36 -0
- data/.rspec +2 -0
- data/.rubocop.yml +24 -0
- data/.rubocop_todo.yml +107 -0
- data/.travis.yml +11 -0
- data/CONTRIBUTING.md +159 -0
- data/Gemfile +39 -0
- data/LICENSE +15 -0
- data/README.md +214 -0
- data/Rakefile +23 -0
- data/app/models/minter_state.rb +16 -0
- data/db/migrate/20160610010003_create_minter_states.rb +16 -0
- data/db/migrate/20161021203429_rename_minter_state_random_to_rand.rb +7 -0
- data/lib/generators/noid/rails/install_generator.rb +22 -0
- data/lib/generators/noid/rails/seed_generator.rb +37 -0
- data/lib/noid/rails/config.rb +35 -0
- data/lib/noid/rails/engine.rb +10 -0
- data/lib/noid/rails/minter/base.rb +65 -0
- data/lib/noid/rails/minter/db.rb +82 -0
- data/lib/noid/rails/minter/file.rb +65 -0
- data/lib/noid/rails/minter.rb +5 -0
- data/lib/noid/rails/rspec.rb +67 -0
- data/lib/noid/rails/service.rb +26 -0
- data/lib/noid/rails/version.rb +7 -0
- data/lib/noid-rails.rb +29 -0
- data/lib/tasks/noid_tasks.rake +59 -0
- data/noid-rails.gemspec +31 -0
- data/spec/models/minter_state_spec.rb +41 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/minterstate_table.rb +13 -0
- data/spec/support/shared_examples/minter.rb +39 -0
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +16 -0
- data/spec/unit/config_spec.rb +45 -0
- data/spec/unit/db_minter_spec.rb +88 -0
- data/spec/unit/file_minter_spec.rb +54 -0
- data/spec/unit/noid_spec.rb +43 -0
- data/spec/unit/rspec_spec.rb +90 -0
- data/spec/unit/service_spec.rb +32 -0
- metadata +225 -0
@@ -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,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,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
|
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
|
data/noid-rails.gemspec
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|