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.
- 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
         
     |