Skip to content

Commit 9d7cb8a

Browse files
committed
CV2-6314: Sync sources_count value between PG & ES (#2278)
* CV2-6314: use cached field for sources_count * CV2-6314: fix tests * CV2-6314: delete targets_count field * CV2-6314: fix tests * CV2-6314: apply PR comments
1 parent f26e177 commit 9d7cb8a

File tree

9 files changed

+55
-92
lines changed

9 files changed

+55
-92
lines changed

app/models/concerns/project_media_cached_fields.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,23 @@ def title_or_description_update
107107
recalculate: :recalculate_suggestions_count,
108108
update_on: [SIMILARITY_EVENT]
109109

110+
cached_field :sources_count,
111+
start_as: 0,
112+
update_es: true,
113+
update_pg: true,
114+
recalculate: :recalculate_sources_count,
115+
update_on: [
116+
{
117+
model: Relationship,
118+
if: proc { |r| r.is_confirmed? },
119+
affected_ids: proc { |r| [r.source_id, r.target_id] },
120+
events: {
121+
save: :recalculate,
122+
destroy: :recalculate
123+
}
124+
}
125+
]
126+
110127
cached_field :related_count,
111128
start_as: 0,
112129
update_es: true,
@@ -538,6 +555,10 @@ def recalculate_suggestions_count
538555
Relationship.send('suggested').where(source_id: self.id).count
539556
end
540557

558+
def recalculate_sources_count
559+
Relationship.where(target_id: self.id).where('relationship_type = ?', Relationship.confirmed_type.to_yaml).count
560+
end
561+
541562
def recalculate_is_suggested
542563
Relationship.where('relationship_type = ?', Relationship.suggested_type.to_yaml).where(target_id: self.id).exists?
543564
end

app/models/concerns/relationship_bulk.rb

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ def bulk_update(ids, updates, team)
2929
}
3030
self.delay.run_update_callbacks(ids.to_json, extra_options.to_json)
3131
delete_cached_fields(pm_source.id, relationships.map(&:target_id))
32+
# Call the cached field sources_count to update its value, as the field is not called from the UI.
33+
ids = [pm_source.id, relationships.map(&:target_id)].flatten
34+
ProjectMedia.where(id: ids).each{ |pm| pm.sources_count(true) }
3235
{ source_project_media: pm_source }
3336
end
3437
end
@@ -66,7 +69,7 @@ def run_update_callbacks(ids_json, extra_options_json)
6669
index_alias = CheckElasticSearchModel.get_index_alias
6770
es_body = []
6871
versions = []
69-
callbacks = [:reset_counters, :update_counters, :propagate_inversion]
72+
callbacks = [:turn_off_unmatched_field, :move_explainers_to_source, :apply_status_to_target]
7073
target_ids = []
7174
Relationship.where(id: ids, source_id: extra_options['source_id']).find_each do |r|
7275
target_ids << r.target_id
@@ -106,11 +109,8 @@ def run_update_callbacks(ids_json, extra_options_json)
106109
# Send report if needed
107110
if extra_options['action'] == 'accept'
108111
begin Relationship.smooch_send_report(r.id) rescue nil end
109-
Relationship.replicate_status_to_children(r.source_id, whodunnit, team_id)
110112
end
111113
end
112-
# Update un-matched field
113-
ProjectMedia.where(id: target_ids).update_all(unmatched: 0)
114114
# Update ES docs
115115
$repository.client.bulk body: es_body unless es_body.blank?
116116
# Import versions
@@ -128,9 +128,6 @@ def run_destroy_callbacks(relationship_target_json, extra_options_json)
128128
source = ProjectMedia.find_by_id(source_id)
129129
version_metadata = nil
130130
unless source.nil?
131-
source.skip_check_ability = true
132-
source.targets_count = Relationship.where(source_id: source.id).where('relationship_type = ? OR relationship_type = ?', Relationship.confirmed_type.to_yaml, Relationship.suggested_type.to_yaml).count
133-
source.save!
134131
# Get version metadata
135132
version_metadata = {
136133
source: {
@@ -141,13 +138,8 @@ def run_destroy_callbacks(relationship_target_json, extra_options_json)
141138
}
142139
}.to_json
143140
end
144-
145-
ProjectMedia.where(id: target_ids).find_each do |target|
146-
target.skip_check_ability = true
147-
target.unmatched = 1
148-
target.sources_count = Relationship.where(target_id: target.id).where('relationship_type = ?', Relationship.confirmed_type.to_yaml).count
149-
target.save!
150-
end
141+
# Update un-matched field
142+
ProjectMedia.where(id: target_ids).update_all(unmatched: 1)
151143
# Update ES
152144
options = {
153145
index: CheckElasticSearchModel.get_index_alias,

app/models/relationship.rb

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@ class Relationship < ApplicationRecord
2222
before_create :point_targets_to_new_source
2323
before_create :destroy_same_suggested_item, if: proc { |r| r.is_confirmed? }
2424
after_create :move_to_same_project_as_main, prepend: true
25-
after_create :update_counters, prepend: true
26-
after_update :reset_counters, prepend: true
2725
after_update :propagate_inversion
2826
after_save :turn_off_unmatched_field, if: proc { |r| r.is_confirmed? || r.is_suggested? }
2927
after_save :move_explainers_to_source, :apply_status_to_target, if: proc { |r| r.is_confirmed? }
3028
before_destroy :archive_detach_to_list
31-
after_destroy :update_counters, prepend: true
3229
after_destroy :turn_on_unmatched_field, if: proc { |r| r.is_confirmed? || r.is_suggested? }
33-
after_commit :update_counter_and_elasticsearch, on: [:create, :update]
30+
after_commit :update_elasticsearch_data, on: [:create, :update]
3431
after_commit :destroy_elasticsearch_relation, on: :destroy
3532

3633
has_paper_trail on: [:create, :update, :destroy], if: proc { |x| User.current.present? && !x.is_being_copied? }, versions: { class_name: 'Version' }
@@ -99,13 +96,6 @@ def self.default_type
9996
{ source: 'parent', target: 'child' }
10097
end
10198

102-
def self.propagate_inversion(ids, source_id)
103-
Relationship.where(id: ids.split(',')).each do |r|
104-
r.source_id = source_id
105-
r.send(:reset_counters)
106-
end
107-
end
108-
10999
def is_being_copied?
110100
(self.source && self.source.is_being_copied) || self.is_being_copied
111101
end
@@ -138,22 +128,6 @@ def is_being_confirmed?
138128
self.send(method).to_json == Relationship.suggested_type.to_json && self.relationship_type.to_json == Relationship.confirmed_type.to_json
139129
end
140130

141-
def update_counters
142-
return if self.is_default?
143-
unless self.target.nil?
144-
target = self.target
145-
target.skip_check_ability = true
146-
target.sources_count = Relationship.where(target_id: target.id).where('relationship_type = ?', Relationship.confirmed_type.to_yaml).count
147-
target.save!
148-
end
149-
unless self.source.nil?
150-
source = self.source
151-
source.skip_check_ability = true
152-
source.targets_count = Relationship.where(source_id: source.id).where('relationship_type = ? OR relationship_type = ?', Relationship.confirmed_type.to_yaml, Relationship.suggested_type.to_yaml).count
153-
source.save!
154-
end
155-
end
156-
157131
def create_or_update_parent_id
158132
self.source_id
159133
end
@@ -293,15 +267,6 @@ def relationship_type_is_valid
293267
end
294268
end
295269

296-
def reset_counters
297-
if (self.source_id_before_last_save && self.source_id_before_last_save != self.source_id) || (self.target_id_before_last_save && self.target_id_before_last_save != self.target_id)
298-
previous = Relationship.new(source_id: self.source_id_before_last_save, target_id: self.target_id_before_last_save)
299-
previous.update_counters
300-
current = Relationship.new(source_id: self.source_id, target_id: self.target_id)
301-
current.update_counters
302-
end
303-
end
304-
305270
def propagate_inversion
306271
if self.source_id_before_last_save == self.target_id && self.target_id_before_last_save == self.source_id
307272
ids = Relationship.where(source_id: self.target_id).map(&:id).join(',')
@@ -322,7 +287,6 @@ def propagate_inversion
322287
end
323288
self.source&.clear_cached_fields
324289
self.target&.clear_cached_fields
325-
Relationship.delay_for(1.second).propagate_inversion(ids, self.source_id)
326290
end
327291
end
328292

@@ -382,8 +346,7 @@ def turn_on_unmatched_field
382346
set_unmatched_field(1)
383347
end
384348

385-
def update_counter_and_elasticsearch
386-
self.update_counters
349+
def update_elasticsearch_data
387350
self.update_elasticsearch_parent
388351
end
389352

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class RemoveTargetsCountFromProjectMedia < ActiveRecord::Migration[6.1]
2+
def change
3+
remove_column :project_medias, :targets_count
4+
end
5+
end

db/schema.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2025_03_28_140809) do
13+
ActiveRecord::Schema.define(version: 2025_04_08_164959) do
1414

1515
# These are extensions that must be enabled in order to support this database
1616
enable_extension "plpgsql"
@@ -349,7 +349,7 @@
349349
t.string "tags", default: [], array: true
350350
t.boolean "trashed", default: false
351351
t.bigint "author_id"
352-
t.integer "channel"
352+
t.integer "channel", null: false
353353
t.index "date_trunc('day'::text, created_at)", name: "explainer_created_at_day"
354354
t.index ["author_id"], name: "index_explainers_on_author_id"
355355
t.index ["channel"], name: "index_explainers_on_channel"
@@ -376,7 +376,7 @@
376376
t.boolean "imported", default: false
377377
t.boolean "trashed", default: false
378378
t.bigint "author_id"
379-
t.integer "channel"
379+
t.integer "channel", null: false
380380
t.index "date_trunc('day'::text, created_at)", name: "fact_check_created_at_day"
381381
t.index ["author_id"], name: "index_fact_checks_on_author_id"
382382
t.index ["channel"], name: "index_fact_checks_on_channel"
@@ -560,7 +560,6 @@
560560
t.boolean "read", default: false, null: false
561561
t.integer "sources_count", default: 0, null: false
562562
t.integer "archived", default: 0
563-
t.integer "targets_count", default: 0, null: false
564563
t.integer "last_seen"
565564
t.datetime "created_at", null: false
566565
t.datetime "updated_at", null: false

test/controllers/elastic_search_5_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def setup
1414
end
1515

1616
test "should match secondary items and show items based on show_similar option" do
17+
RequestStore.store[:skip_cached_field_update] = false
1718
t = create_team
1819
parent = create_project_media team: t, disable_es_callbacks: false
1920
child_1 = create_project_media team: t, quote: 'child_media a', disable_es_callbacks: false

test/models/project_media_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def setup
1111

1212
test "should query media" do
1313
setup_elasticsearch
14+
RequestStore.store[:skip_cached_field_update] = false
1415
t = create_team
1516
pm = create_project_media team: t, disable_es_callbacks: false
1617
create_project_media team: t, disable_es_callbacks: false

test/models/relationship_2_test.rb

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,28 +94,18 @@ def teardown
9494
end
9595
end
9696

97-
test "should start with targets count zero" do
98-
pm = create_project_media project: @project
99-
assert_equal 0, pm.targets_count
100-
end
101-
10297
test "should increment and decrement counters when relationship is created or destroyed" do
98+
RequestStore.store[:skip_cached_field_update] = false
10399
s = create_project_media project: @project
104100
t = create_project_media project: @project
105-
assert_equal 0, s.targets_count
106101
assert_equal 0, s.sources_count
107-
assert_equal 0, t.targets_count
108102
assert_equal 0, t.sources_count
109103
r = create_relationship source_id: s.id, target_id: t.id, relationship_type: Relationship.confirmed_type
110-
assert_equal 1, s.reload.targets_count
111104
assert_equal 0, s.reload.sources_count
112105
assert_equal 1, t.reload.sources_count
113-
assert_equal 0, t.reload.targets_count
114106
r.destroy
115-
assert_equal 0, s.reload.targets_count
116107
assert_equal 0, s.reload.sources_count
117108
assert_equal 0, t.reload.sources_count
118-
assert_equal 0, t.reload.targets_count
119109
end
120110

121111
test "should create related report" do

test/models/relationship_test.rb

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def setup
3434
end
3535

3636
test "should update sources_count and parent_id for confirmed item" do
37+
RequestStore.store[:skip_cached_field_update] = false
3738
setup_elasticsearch
3839
t = create_team
3940
pm_s = create_project_media team: t, disable_es_callbacks: false
@@ -99,16 +100,16 @@ def setup
99100
end
100101

101102
test "should verify versions and ES for bulk-update" do
103+
setup_elasticsearch
104+
RequestStore.store[:skip_cached_field_update] = false
105+
t = create_team
106+
u = create_user
107+
create_team_user team: t, user: u, role: 'admin'
102108
with_versioning do
103-
setup_elasticsearch
104-
RequestStore.store[:skip_cached_field_update] = false
105-
t = create_team
106-
u = create_user
107-
create_team_user team: t, user: u, role: 'admin'
108109
with_current_user_and_team(u, t) do
109110
# Try to create an item with title that trigger a version metadata error(CV2-2910)
110111
pm_s = create_project_media team: t, quote: "Rahul Gandhi's interaction with Indian?param:test&Journalists Association in London"
111-
pm_t1 = create_project_media team: t
112+
pm_t1 = create_project_media team: t, disable_es_callbacks: false
112113
pm_t2 = create_project_media team: t
113114
pm_t3 = create_project_media team: t
114115
r1 = create_relationship source_id: pm_s.id, target_id: pm_t1.id, relationship_type: Relationship.suggested_type
@@ -157,18 +158,16 @@ def setup
157158

158159
test "should bulk-reject similar items" do
159160
RequestStore.store[:skip_cached_field_update] = false
161+
setup_elasticsearch
162+
t = create_team
163+
u = create_user
164+
create_team_user team: t, user: u, role: 'admin'
160165
with_versioning do
161-
setup_elasticsearch
162-
t = create_team
163-
u = create_user
164-
p = create_project team: t
165-
p2 = create_project team: t
166-
create_team_user team: t, user: u, role: 'admin'
167166
with_current_user_and_team(u, t) do
168-
pm_s = create_project_media team: t, project: p
169-
pm_t1 = create_project_media team: t, project: p
170-
pm_t2 = create_project_media team: t, project: p
171-
pm_t3 = create_project_media team: t, project: p
167+
pm_s = create_project_media team: t
168+
pm_t1 = create_project_media team: t
169+
pm_t2 = create_project_media team: t
170+
pm_t3 = create_project_media team: t
172171
r1 = create_relationship source_id: pm_s.id, target_id: pm_t1.id, relationship_type: Relationship.suggested_type
173172
r2 = create_relationship source_id: pm_s.id, target_id: pm_t2.id, relationship_type: Relationship.suggested_type
174173
r3 = create_relationship source_id: pm_s.id, target_id: pm_t3.id, relationship_type: Relationship.suggested_type
@@ -275,14 +274,6 @@ def setup
275274
assert_equal 1, pm2.explainer_items.count
276275
end
277276

278-
test "should not attempt to update source count if source does not exist" do
279-
r = create_relationship relationship_type: Relationship.confirmed_type
280-
r.source.delete
281-
assert_nothing_raised do
282-
r.reload.send :update_counters
283-
end
284-
end
285-
286277
test "should cache the name of who created a similar item" do
287278
RequestStore.store[:skip_cached_field_update] = false
288279
t = create_team

0 commit comments

Comments
 (0)