Skip to content

Commit 9f60dbf

Browse files
committed
refactor: move upsert logic into a plugin
1 parent ba2cd5d commit 9f60dbf

11 files changed

+314
-100
lines changed

DEVELOPER_SETUP.md

+7-13
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,22 @@ To make the barrier to entry as low as possible, the mysql2 and pg gems are not
4545

4646
You can set the `PACT_BROKER_DATABASE_URL` environment variable to use a postgres/mysql database using the format `driver://username:password@host:port/database` eg. `postgres://pact_broker:password@localhost/pact_broker`. Ensure you have set `INSTALL_MYSQL=true` or `INSTALL_PG=true` and run `bundle install` to make sure the required gems are present.
4747

48-
## Running the tests with mysql
48+
## Running the tests with postgres and mysql
4949

5050
```
51-
docker-compose -f docker-compose-test-mysql.yml up --build --remove-orphans
51+
docker-compose -f docker-compose-test.yml up --build --remove-orphans
5252
5353
# in separate console window...
54-
docker-compose -f docker-compose-test-mysql.yml run --rm tests bash
55-
56-
# inside the tests container
57-
bundle exec rake
58-
```
59-
60-
## Running the tests with postgres
61-
62-
```
63-
docker-compose -f docker-compose-test-postgres.yml up --build --remove-orphans
54+
docker-compose -f docker-compose-test.yml run --rm postgres-tests bash
6455
6556
# in separate console window...
66-
docker-compose -f docker-compose-test-postgres.yml run --rm tests bash
57+
docker-compose -f docker-compose-test.yml run --rm mysql-tests bash
6758
6859
# inside the tests container
6960
bundle exec rake
61+
62+
# if you don't want to run the whole rake test suite, init the db first
63+
/home/init-db.sh
7064
```
7165
## Running the tests
7266

docker-compose-test-mysql.yml

-37
This file was deleted.

docker-compose-test.yml

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# This doesn't work yet
2+
version: "3"
3+
4+
services:
5+
postgres:
6+
image: postgres
7+
healthcheck:
8+
test: psql postgres --command "select 1" -U postgres
9+
ports:
10+
- "5432:5432"
11+
environment:
12+
POSTGRES_USER: postgres
13+
POSTGRES_PASSWORD: postgres
14+
POSTGRES_DB: postgres
15+
16+
init-postgres:
17+
build: .
18+
depends_on:
19+
- postgres
20+
environment:
21+
DATABASE_ADAPTER: docker_compose_postgres
22+
PACT_BROKER_HIDE_PACTFLOW_MESSAGES: 'true'
23+
INSTALL_PG: 'true'
24+
volumes:
25+
- ./lib:/home/lib
26+
- ./db:/home/db
27+
- ./tasks:/home/tasks
28+
- ./Rakefile:/home/Rakefile
29+
- ./config:/home/config
30+
command: dockerize --wait tcp://postgres:5432 /home/init-db.sh
31+
32+
postgres-tests:
33+
build: .
34+
depends_on:
35+
- postgres
36+
environment:
37+
DATABASE_ADAPTER: docker_compose_postgres
38+
PACT_BROKER_HIDE_PACTFLOW_MESSAGES: 'true'
39+
INSTALL_PG: 'true'
40+
volumes:
41+
- ./lib:/home/lib
42+
- ./spec:/home/spec
43+
- ./db:/home/db
44+
- ./config.ru:/home/config.ru
45+
- ./tasks:/home/tasks
46+
- ./Rakefile:/home/Rakefile
47+
- ./config:/home/config
48+
- ./.rspec:/home/.rspec
49+
- ./.rubocop:/home/.rubocop
50+
- ./.gitignore:/home/.gitignore
51+
- ./public:/home/public
52+
53+
mysql:
54+
image: mysql:5.7.28
55+
command: --default-authentication-plugin=mysql_native_password
56+
environment:
57+
MYSQL_ROOT_PASSWORD: pact_broker
58+
MYSQL_DATABASE: pact_broker
59+
MYSQL_USER: pact_broker
60+
MYSQL_PASSWORD: pact_broker
61+
ports:
62+
- "3306:3306"
63+
64+
init-mysql:
65+
build: .
66+
depends_on:
67+
- mysql
68+
environment:
69+
DATABASE_ADAPTER: docker_compose_mysql
70+
PACT_BROKER_HIDE_PACTFLOW_MESSAGES: 'true'
71+
INSTALL_MYSQL: 'true'
72+
volumes:
73+
- ./lib:/home/lib
74+
- ./db:/home/db
75+
- ./tasks:/home/tasks
76+
- ./Rakefile:/home/Rakefile
77+
- ./config:/home/config
78+
command: dockerize --wait tcp://mysql:3306 /home/init-db.sh
79+
80+
mysql-tests:
81+
build: .
82+
depends_on:
83+
- mysql
84+
environment:
85+
DATABASE_ADAPTER: docker_compose_mysql
86+
PACT_BROKER_HIDE_PACTFLOW_MESSAGES: 'true'
87+
INSTALL_MYSQL: 'true'
88+
volumes:
89+
- ./lib:/home/lib
90+
- ./spec:/home/spec
91+
- ./db:/home/db
92+
- ./config.ru:/home/config.ru
93+
- ./tasks:/home/tasks
94+
- ./Rakefile:/home/Rakefile
95+
- ./config:/home/config
96+
- ./.rspec:/home/.rspec
97+
- ./.rubocop:/home/.rubocop
98+
- ./.gitignore:/home/.gitignore
99+
- ./public:/home/public
100+

lib/pact_broker/pacts/latest_pact_publication_id_for_consumer_version.rb

+2-6
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@
44
module PactBroker
55
module Pacts
66
class LatestPactPublicationIdForConsumerVersion < Sequel::Model(:latest_pact_publication_ids_for_consumer_versions)
7-
7+
set_primary_key [:provider_id, :consumer_version_id]
88
unrestrict_primary_key
9+
plugin :upsert, identifying_columns: [:provider_id, :consumer_version_id]
910

1011
dataset_module do
1112
include PactBroker::Repositories::Helpers
1213
end
13-
14-
def upsert
15-
before_create
16-
self.class.upsert(to_hash, [:provider_id, :consumer_version_id])
17-
end
1814
end
1915
end
2016
end

lib/pact_broker/pacts/pact_publication.rb

+3-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
module PactBroker
88
module Pacts
99
class PactPublication < Sequel::Model(:pact_publications)
10-
UNIQUE_CONSTRAINT_KEYS = [:consumer_version_id, :provider_id, :revision_number].freeze
1110

1211
extend Forwardable
1312

@@ -22,6 +21,9 @@ class PactPublication < Sequel::Model(:pact_publications)
2221
one_to_one(:latest_verification, class: "PactBroker::Verifications::LatestVerificationForPactVersion", key: :pact_version_id, primary_key: :pact_version_id)
2322
one_to_many(:head_pact_tags, class: "PactBroker::Tags::HeadPactTag", primary_key: :id, key: :pact_publication_id)
2423

24+
plugin :upsert, identifying_columns: [:consumer_version_id, :provider_id, :revision_number]
25+
plugin :timestamps, update_on_create: true
26+
2527
dataset_module do
2628
include PactBroker::Repositories::Helpers
2729

@@ -158,21 +160,12 @@ def to_version_domain_lightweight
158160
OpenStruct.new(number: consumer_version.number, pacticipant: consumer, order: consumer_version.order)
159161
end
160162

161-
def upsert
162-
before_create
163-
params = to_hash.merge(created_at: Sequel.datetime_class.now)
164-
self.id = self.class.upsert(params, UNIQUE_CONSTRAINT_KEYS).id
165-
self.refresh
166-
end
167-
168163
private
169164

170165
def cached_domain_for_delegation
171166
@domain_object ||= to_domain
172167
end
173168
end
174-
175-
PactPublication.plugin :timestamps, update_on_create: true
176169
end
177170
end
178171

lib/pact_broker/pacts/pact_version.rb

+2-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ module PactBroker
66
module Pacts
77
class PactVersion < Sequel::Model(:pact_versions)
88
plugin :timestamps
9+
plugin :upsert, identifying_columns: [:consumer_id, :provider_id, :sha]
10+
911
one_to_many :pact_publications, reciprocal: :pact_version
1012
one_to_many :verifications, reciprocal: :verification, order: :id, class: "PactBroker::Domain::Verification"
1113
one_to_one :latest_verification, class: "PactBroker::Verifications::LatestVerificationForPactVersion", key: :pact_version_id, primary_key: :id
@@ -79,11 +81,6 @@ def verified_successfully_by_any_provider_version?
7981
.join(:versions, Sequel[:versions][:id] => Sequel[:verifications][:provider_version_id])
8082
.any?
8183
end
82-
83-
def upsert
84-
self.before_create
85-
self.class.upsert(to_hash, [:consumer_id, :provider_id, :sha])
86-
end
8784
end
8885
end
8986
end

lib/pact_broker/repositories/helpers.rb

-18
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,6 @@ def select_for_subquery column
5151
select(column)
5252
end
5353
end
54-
55-
def upsert row, unique_key_names, columns_to_update = nil
56-
if postgres?
57-
insert_conflict(update: row, target: unique_key_names).insert(row)
58-
elsif mysql?
59-
update_cols = columns_to_update || (row.keys - unique_key_names)
60-
on_duplicate_key_update(*update_cols).insert(row)
61-
else
62-
# Sqlite
63-
key = row.reject{ |k, v| !unique_key_names.include?(k) }
64-
if where(key).count == 0
65-
insert(row)
66-
else
67-
where(key).update(row)
68-
end
69-
end
70-
model.where(row.select{ |key, _| unique_key_names.include?(key)}).single_record
71-
end
7254
end
7355
end
7456
end

lib/pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version.rb

+3-6
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
module PactBroker
44
module Verifications
55
class LatestVerificationIdForPactVersionAndProviderVersion < Sequel::Model(:latest_verification_id_for_pact_version_and_provider_version)
6-
76
unrestrict_primary_key
7+
set_primary_key [:pact_version_id, :provider_version_id]
8+
9+
plugin :upsert, identifying_columns: [:pact_version_id, :provider_version_id]
810

911
dataset_module do
1012
include PactBroker::Repositories::Helpers
1113
end
12-
13-
def upsert
14-
before_create
15-
self.class.upsert(to_hash, [:pact_version_id, :provider_version_id])
16-
end
1714
end
1815
end
1916
end

lib/sequel/plugins/insert_ignore.rb

+1-5
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
# Rather than re-writing the whole save method and all the hooks and validation logic in it,
77
# it naughtily overrides the private _insert_dataset.
88

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-
129
module Sequel
1310
module Plugins
1411
module InsertIgnore
@@ -28,7 +25,6 @@ def insert_ignore(opts = {})
2825
load_values_from_previously_inserted_object unless id
2926
self
3027
rescue Sequel::NoExistingObject
31-
# MySQL. Ruining it for everyone.
3228
load_values_from_previously_inserted_object
3329
end
3430

@@ -40,7 +36,7 @@ def load_values_from_previously_inserted_object
4036
refresh
4137
end
4238

43-
# Does the job for Sqlite and Postgres
39+
# naughty override of Sequel private method
4440
def _insert_dataset
4541
super.insert_ignore
4642
end

0 commit comments

Comments
 (0)