Skip to content

Commit fd98456

Browse files
committed
refactor: create sequel plugin for insert ignore
1 parent 141346f commit fd98456

File tree

6 files changed

+128
-95
lines changed

6 files changed

+128
-95
lines changed

lib/pact_broker/db/insert_ignore.rb

-28
This file was deleted.

lib/pact_broker/domain/pacticipant.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
require 'pact_broker/repositories/helpers'
44
require 'pact_broker/versions/latest_version'
55
require 'pact_broker/domain/label'
6-
require 'pact_broker/db/insert_ignore'
76

87
module PactBroker
98
module Domain
109
class Pacticipant < Sequel::Model
1110

1211
include Messages
13-
include PactBroker::DB::InsertIgnore
12+
plugin :insert_ignore, identifying_columns: [:name]
1413

1514
plugin :timestamps, update_on_create: true
1615

lib/pact_broker/pacticipants/repository.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def create args
5959
repository_url: args[:repository_url],
6060
created_at: Sequel.datetime_class.now,
6161
updated_at: Sequel.datetime_class.now
62-
).save
62+
).insert_ignore
6363
PactBroker::Domain::Pacticipant.find(name: args[:name])
6464
end
6565

lib/sequel/plugins/insert_ignore.rb

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# This plugin can be included into a Sequel model to allow the original record
2+
# to be loaded into the model if a duplicate is inserted.
3+
# This is to handle race conditions when two requests come in in parallel to create
4+
# the same resource.
5+
6+
# Rather than re-writing the whole save method and all the hooks and validation logic in it,
7+
# it naughtily overrides the private _insert_dataset.
8+
9+
# MySQL does not return the id of the original row when a duplicate is inserted,
10+
# so we have to manually find the original record an load it into the model.
11+
12+
module Sequel
13+
module Plugins
14+
module InsertIgnore
15+
def self.configure(model, opts=OPTS)
16+
model.instance_exec do
17+
@insert_ignore_identifying_columns = opts.fetch(:identifying_columns)
18+
end
19+
end
20+
21+
module ClassMethods
22+
attr_reader :insert_ignore_identifying_columns
23+
end
24+
25+
module InstanceMethods
26+
def insert_ignore(opts = {})
27+
save(opts)
28+
rescue Sequel::NoExistingObject
29+
# MySQL. Ruining it for everyone.
30+
query = self.class.insert_ignore_identifying_columns.each_with_object({}) do | column_name, q |
31+
q[column_name] = values[column_name]
32+
end
33+
self.id = model.where(query).single_record.id
34+
self.refresh
35+
end
36+
37+
# Does the job for Sqlite and Postgres
38+
def _insert_dataset
39+
super.insert_ignore
40+
end
41+
end
42+
end
43+
end
44+
end

spec/lib/pact_broker/db/insert_ignore_spec.rb

-64
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
require 'sequel/plugins/insert_ignore'
2+
require 'sequel'
3+
4+
module Sequel
5+
module Plugins
6+
module InsertIgnore
7+
class PacticipantNoInsertIgnore < Sequel::Model(:pacticipants)
8+
plugin :timestamps, update_on_create: true
9+
end
10+
11+
class Pacticipant < Sequel::Model
12+
plugin :insert_ignore, identifying_columns: [:name]
13+
plugin :timestamps, update_on_create: true
14+
end
15+
16+
class Version < Sequel::Model
17+
plugin :insert_ignore, identifying_columns: [:pacticipant_id, :number]
18+
plugin :timestamps, update_on_create: true
19+
end
20+
21+
context "when a duplicate is inserted with no insert_ignore" do
22+
before do
23+
PacticipantNoInsertIgnore.new(name: "Foo").save
24+
end
25+
26+
subject do
27+
PacticipantNoInsertIgnore.new(name: "Foo").save
28+
end
29+
30+
it "raises an error" do
31+
expect { subject }.to raise_error Sequel::UniqueConstraintViolation
32+
end
33+
end
34+
35+
# This doesn't work on MSQL because the _insert_raw method
36+
# does not return the row ID of the duplicated row when insert_ignore is used
37+
# May have to go back to the old method of doing this
38+
context "when a duplicate Pacticipant is inserted with insert_ignore" do
39+
before do
40+
Pacticipant.new(name: "Foo", repository_url: "http://foo").insert_ignore
41+
end
42+
43+
subject do
44+
Pacticipant.new(name: "Foo").insert_ignore
45+
end
46+
47+
it "does not raise an error" do
48+
expect { subject }.to_not raise_error
49+
end
50+
51+
it "sets the values on the object" do
52+
expect(subject.repository_url).to eq "http://foo"
53+
end
54+
55+
it "does not insert another row" do
56+
expect { subject }.to_not change { Pacticipant.count }
57+
end
58+
end
59+
60+
context "when a duplicate Version is inserted with insert_ignore" do
61+
let!(:pacticipant) { Pacticipant.new(name: "Foo").save }
62+
let!(:original_version) { Version.new(number: "1", pacticipant_id: pacticipant.id).insert_ignore }
63+
64+
subject do
65+
Version.new(number: "1", pacticipant_id: pacticipant.id).insert_ignore
66+
end
67+
68+
it "does not raise an error" do
69+
expect { subject }.to_not raise_error
70+
end
71+
72+
it "sets the values on the object" do
73+
expect(subject.id).to eq original_version.id
74+
end
75+
76+
it "does not insert another row" do
77+
expect { subject }.to_not change { Version.count }
78+
end
79+
end
80+
end
81+
end
82+
end

0 commit comments

Comments
 (0)