From d374d0be1af0e6cef4a150425cd73189f9960f54 Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Tue, 13 Mar 2018 16:53:55 +0100 Subject: Backwards-compat for migration specs. The specs are based on a schema version that doesn't know about `internal_ids` table. However, the actual code being execute relies on it. --- app/models/internal_id.rb | 20 ++++++++++++++++++-- spec/models/internal_id_spec.rb | 19 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb index 4673dffa842..6419cdc5b67 100644 --- a/app/models/internal_id.rb +++ b/app/models/internal_id.rb @@ -17,6 +17,8 @@ class InternalId < ActiveRecord::Base validates :usage, presence: true + REQUIRED_SCHEMA_VERSION = 20180305095250 + # Increments #last_value and saves the record # # The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL). @@ -30,8 +32,22 @@ class InternalId < ActiveRecord::Base class << self def generate_next(subject, scope, usage, init) + # Shortcut if `internal_ids` table is not available (yet) + # This can be the case in other (unrelated) migration specs + return (init.call(subject) || 0) + 1 unless available? + InternalIdGenerator.new(subject, scope, usage, init).generate end + + def available? + @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION # rubocop:disable Gitlab/PredicateMemoization + end + + # Flushes cached information about schema + def reset_column_information + @available_flag = nil + super + end end class InternalIdGenerator @@ -49,12 +65,12 @@ class InternalId < ActiveRecord::Base # subject: The instance we're generating an internal id for. Gets passed to init if called. # scope: Attributes that define the scope for id generation. # usage: Symbol to define the usage of the internal id, see InternalId.usages - # init: Block that gets called to initialize InternalId record if not yet present (optional) + # init: Block that gets called to initialize InternalId record if not present attr_reader :subject, :scope, :init, :scope_attrs, :usage def initialize(subject, scope, usage, init) @subject = subject @scope = scope - @init = init || ->(s) { 0 } + @init = init @usage = usage raise 'scope is not well-defined, need at least one column for scope (given: 0)' if scope.empty? diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 6d5a12c9d06..ef6db2daa95 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -12,9 +12,9 @@ describe InternalId do end describe '.generate_next' do - context 'in the absence of a record' do - subject { described_class.generate_next(issue, scope, usage, init) } + subject { described_class.generate_next(issue, scope, usage, init) } + context 'in the absence of a record' do it 'creates a record if not yet present' do expect { subject }.to change { described_class.count }.from(0).to(1) end @@ -47,6 +47,21 @@ describe InternalId do normalized = seq.map { |i| i - seq.min } expect(normalized).to eq((0..seq.size - 1).to_a) end + + context 'with an insufficient schema version' do + before do + described_class.reset_column_information + expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) + end + + let(:init) { double('block') } + + it 'calculates next internal ids on the fly' do + val = rand(1..100) + expect(init).to receive(:call).with(issue).and_return(val) + expect(subject).to eq(val + 1) + end + end end describe '#increment_and_save!' do -- cgit v1.2.1