Skip to content

Commit

Permalink
Add postgresql support
Browse files Browse the repository at this point in the history
  • Loading branch information
gmalette committed Sep 24, 2014
1 parent 4ea2058 commit 80ca169
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 9 deletions.
1 change: 1 addition & 0 deletions activerecord-rescue_from_duplicate.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'sqlite3'
spec.add_development_dependency 'mysql2'
spec.add_development_dependency 'rspec'
spec.add_development_dependency 'pg'
spec.add_development_dependency 'pry'
end
26 changes: 20 additions & 6 deletions lib/rescue_from_duplicate/active_record/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ def exception_validator(exception)
protected

def exception_columns(exception)
columns = exception.message =~ /SQLite3::ConstraintException/ ? sqlite3_exception_columns(exception) : other_exception_columns(exception)
columns.sort
columns = case
when exception.message =~ /SQLite3::ConstraintException/
sqlite3_exception_columns(exception)
when exception.message =~ /PG::UniqueViolation/
postgresql_exception_columns(exception)
else
other_exception_columns(exception)
end
end

def exception_rescuer(exception)
Expand All @@ -55,15 +61,23 @@ def exception_rescuer(exception)
_rescue_from_duplicates.detect { |rescuer| rescuer.matches?(columns) }
end

def postgresql_exception_columns(exception)
extract_columns(exception.message[/Key \((.*?)\)=\(.*?\) already exists./, 1])
end

def sqlite3_exception_columns(exception)
columns = exception.message[/column (.*) is not unique/, 1]
return unless columns
columns.split(",").map(&:strip)
extract_columns(exception.message[/columns? (.*) (?:is|are) not unique/, 1])
end

def extract_columns(columns_string)
return unless columns_string
columns_string.split(",").map(&:strip).sort
end

def other_exception_columns(exception)
indexes = self.class.connection.indexes(self.class.table_name)
indexes.detect{ |i| exception.message.include?(i.name) }.try(:columns) || []
columns = indexes.detect{ |i| exception.message.include?(i.name) }.try(:columns) || []
columns.sort
end

def rescue_with_validator?(columns, validator)
Expand Down
2 changes: 1 addition & 1 deletion spec/rescue_from_duplicate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
Rescuable.stub(connection: double(indexes: []))
}

let(:message) { super().gsub(/column (.*) is/, 'column toto is') }
let(:message) { super().gsub(/column (.*?) is/, 'column toto is').gsub(/Key \((.*?)\)=/, 'Key (toto)=') }

it "returns nil" do
expect(subject.exception_validator(uniqueness_exception)).to be_nil
Expand Down
31 changes: 31 additions & 0 deletions spec/rescuer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,34 @@
end
end
end

shared_examples 'a model with rescued unique error without validator' do
describe 'create!' do
context 'when catching a race condition' do
before {
described_class.create!(relation_id: 1, handle: 'toto')
}

it 'adds an error on the model' do
model = described_class.create(relation_id: 1, handle: 'toto')
expect(model.errors[:handle]).to eq(["handle must be unique for this relation"])
end
end
end
end

describe Sqlite3Model do
it_behaves_like 'a model with rescued unique error without validator'
end

if defined?(MysqlModel)
describe MysqlModel do
it_behaves_like 'a model with rescued unique error without validator'
end
end

if defined?(PostgresqlModel)
describe PostgresqlModel do
it_behaves_like 'a model with rescued unique error without validator'
end
end
10 changes: 8 additions & 2 deletions spec/support/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ def self.recreate_table(name, *args, &block)
execute "drop table if exists #{name}"

create_table(name, *args) do |t|
t.integer :relation_id
t.string :handle

t.string :name
t.integer :size
end

add_index name, [:relation_id, :handle], unique: true
add_index name, :name, unique: true
add_index name, :size, unique: true
end
Expand Down Expand Up @@ -49,8 +53,10 @@ module TestModel
extend ActiveSupport::Concern

included do
validates_uniqueness_of :name, rescue_from_duplicate: true
validates_uniqueness_of :size
rescue_from_duplicate :handle, scope: :relation_id, message: "handle must be unique for this relation"

validates_uniqueness_of :name, rescue_from_duplicate: true, allow_nil: true
validates_uniqueness_of :size, allow_nil: true
end
end

Expand Down

0 comments on commit 80ca169

Please sign in to comment.