Skip to content

Commit 33e5c4e

Browse files
authored
Merge pull request #13094 from demarche-numerique/faster_departement_json_migration
Tech: accélère la migration des données géographiques des départements
2 parents 05d9764 + cf4a99a commit 33e5c4e

2 files changed

Lines changed: 149 additions & 10 deletions

File tree

app/tasks/maintenance/t20260504_backfill_canonical_keys_in_departement_champs_task.rb

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,27 @@
33
module Maintenance
44
class T20260504BackfillCanonicalKeysInDepartementChampsTask < MaintenanceTasks::Task
55
include RunnableOnDeployConcern
6+
include StatementsHelpersConcern
7+
8+
RANGE_SIZE = 50_000
69

710
def collection
8-
Champs::DepartementChamp.all
11+
min_id = Champ.minimum(:id) || 0
12+
max_id = Champ.maximum(:id) || 0
13+
(min_id..max_id).step(RANGE_SIZE).map { |from| from...(from + RANGE_SIZE) }
914
end
1015

11-
def process(champ)
12-
current = champ.value_json || {}
13-
14-
additions = {
15-
'department_code' => champ.code,
16-
'region_code' => current['code_region'],
17-
}.compact
16+
def process(range)
17+
Champs::DepartementChamp.where(id: range).update_all(<<~SQL.squish)
18+
value_json = COALESCE(value_json, '{}'::jsonb) || jsonb_strip_nulls(jsonb_build_object(
19+
'department_code', external_id,
20+
'region_code', value_json->>'code_region'
21+
))
22+
SQL
23+
end
1824

19-
result = current.merge(additions)
20-
champ.update_column(:value_json, result) if result != current
25+
def count
26+
collection.size
2127
end
2228
end
2329
end
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# frozen_string_literal: true
2+
3+
require "rails_helper"
4+
5+
module Maintenance
6+
RSpec.describe T20260504BackfillCanonicalKeysInDepartementChampsTask do
7+
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :departements }]) }
8+
let(:dossier) { create(:dossier, :with_populated_champs, procedure:) }
9+
let(:champ) { dossier.champs.first }
10+
11+
describe "#process" do
12+
let(:range) { (champ.id)...(champ.id + 1) }
13+
14+
subject(:process) { described_class.new.process(range) }
15+
16+
context "when only legacy keys are present" do
17+
before do
18+
champ.update_columns(
19+
value: "Paris",
20+
external_id: "75",
21+
value_json: { 'code_region' => '11' }
22+
)
23+
end
24+
25+
it "adds canonical keys without removing legacy ones" do
26+
process
27+
expect(champ.reload.value_json).to eq({
28+
'code_region' => '11',
29+
'department_code' => '75',
30+
'region_code' => '11',
31+
})
32+
end
33+
end
34+
35+
context "when value_json is nil" do
36+
before do
37+
champ.update_columns(value: "Paris", external_id: "75", value_json: nil)
38+
end
39+
40+
it "creates value_json with department_code from external_id" do
41+
process
42+
expect(champ.reload.value_json).to eq({
43+
'department_code' => '75',
44+
})
45+
end
46+
end
47+
48+
context "when external_id and legacy keys are all nil" do
49+
before do
50+
champ.update_columns(value: nil, external_id: nil, value_json: {})
51+
end
52+
53+
it "leaves value_json empty" do
54+
process
55+
expect(champ.reload.value_json).to eq({})
56+
end
57+
end
58+
59+
context "when canonical keys already hold stale values" do
60+
before do
61+
champ.update_columns(
62+
value: "Paris",
63+
external_id: "75",
64+
value_json: {
65+
'code_region' => '11',
66+
'department_code' => 'OBSOLETE',
67+
'region_code' => 'OBSOLETE',
68+
}
69+
)
70+
end
71+
72+
it "overwrites canonical keys with values from columns and legacy keys" do
73+
process
74+
expect(champ.reload.value_json).to eq({
75+
'code_region' => '11',
76+
'department_code' => '75',
77+
'region_code' => '11',
78+
})
79+
end
80+
end
81+
82+
context "when champ id is outside the processed range" do
83+
let(:range) { (champ.id + 1)...(champ.id + 100) }
84+
85+
before do
86+
champ.update_columns(
87+
value: "Paris",
88+
external_id: "75",
89+
value_json: { 'code_region' => '11' }
90+
)
91+
end
92+
93+
it "does not modify the champ" do
94+
expect { process }.not_to(change { champ.reload.value_json })
95+
end
96+
end
97+
98+
context "when running twice on the same champ" do
99+
before do
100+
champ.update_columns(
101+
value: "Paris",
102+
external_id: "75",
103+
value_json: { 'code_region' => '11' }
104+
)
105+
end
106+
107+
it "is idempotent" do
108+
process
109+
first_pass = champ.reload.value_json
110+
process
111+
expect(champ.reload.value_json).to eq(first_pass)
112+
end
113+
end
114+
end
115+
116+
describe "#collection" do
117+
before { champ }
118+
119+
it "returns ranges that cover existing champ ids" do
120+
collection = described_class.new.collection
121+
expect(collection.any? { |range| range.include?(champ.id) }).to be(true)
122+
end
123+
end
124+
125+
describe "#count" do
126+
before { champ }
127+
128+
it "returns the number of ranges" do
129+
expect(described_class.new.count).to eq(1)
130+
end
131+
end
132+
end
133+
end

0 commit comments

Comments
 (0)