Skip to content

Commit d48eddc

Browse files
committed
Refactor RichTextLink and related migrations to improve link handling and ensure default values for link_type, position, and locale
1 parent 19f4164 commit d48eddc

File tree

8 files changed

+112
-19
lines changed

8 files changed

+112
-19
lines changed

app/jobs/better_together/metrics/rich_text_link_checker_queue_job.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def model_collection
4040
# metrics rich_text_links table if records exist, otherwise operate on
4141
# the full Link set.
4242
if BetterTogether::Metrics::RichTextLink.table_exists?
43-
model_class.joins("INNER JOIN better_together_metrics_rich_text_links rt ON rt.link_id = #{model_class.table_name}.id")
43+
model_class.joins(:rich_text_links)
4444
.where('last_checked_at IS NULL OR last_checked_at < ?', last_checked_lt)
4545
else
4646
model_class.where('last_checked_at IS NULL OR last_checked_at < ?', last_checked_lt)

app/models/better_together/metrics/rich_text_link.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module Metrics
55
# Tracks occurrences of links found inside ActionText rich content and
66
# associates them with the original Link record and owning rich text.
77
class RichTextLink < ApplicationRecord
8+
include Positioned
9+
810
belongs_to :link, class_name: 'BetterTogether::Content::Link'
911
belongs_to :rich_text, class_name: 'ActionText::RichText'
1012
belongs_to :rich_text_record, polymorphic: true

app/services/better_together/metrics/rich_text_link_identifier.rb

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,15 @@ def persist_link_and_rich_text_link(rich_text, link, index, canonical_host, uri_
9595
bt_link.host ||= canonical_host
9696
bt_link.scheme ||= uri_obj.scheme
9797
bt_link.external = (canonical_host.present? && (rt_platform_host != canonical_host))
98+
# Ensure link_type is set to a sensible default before persisting to avoid
99+
# NOT NULL constraint violations (some callers create links directly).
100+
bt_link.link_type ||= 'website'
98101
bt_link.save! if bt_link.changed?
99102

100103
# Persist the RichTextLink depending on the schema available.
101-
if BetterTogether::Metrics::RichTextLink.column_names.include?('link_id')
104+
model = BetterTogether::Metrics::RichTextLink
105+
106+
if model.column_names.include?('link_id')
102107
attrs = {
103108
link_id: bt_link.id,
104109
rich_text_id: rich_text.id,
@@ -108,21 +113,42 @@ def persist_link_and_rich_text_link(rich_text, link, index, canonical_host, uri_
108113
locale: rich_text.locale
109114
}
110115

111-
BetterTogether::Metrics::RichTextLink.find_or_create_by!(attrs)
116+
# Build optional metadata hash only for columns that exist on the table
117+
optional_cols = %w[url link_type external valid_link host error_message]
118+
optional_cols.each do |c|
119+
next unless model.column_names.include?(c)
120+
121+
attrs[c.to_sym] = case c
122+
when 'url' then bt_link.url
123+
when 'link_type' then bt_link.link_type || 'website'
124+
when 'external' then bt_link.external || false
125+
when 'valid_link' then bt_link.valid_link || false
126+
when 'host' then bt_link.host
127+
when 'error_message' then bt_link.error_message
128+
end
129+
end
130+
131+
begin
132+
model.create!(attrs)
133+
rescue ActiveRecord::RecordNotUnique
134+
# another process inserted concurrently; ignore
135+
end
112136
else
113137
# Fallback schema: metrics rich_text_links store URL and metadata inline.
114-
model = BetterTogether::Metrics::RichTextLink
115-
unless model.where(rich_text_id: rich_text.id, url: link).exists?
116-
conn = model.connection
117-
table = model.table_name
118-
now = Time.current
119-
cols = %w[rich_text_id url link_type external valid host error_message created_at updated_at]
120-
vals = [rich_text.id, link, bt_link.link_type || 'website', bt_link.external || false,
121-
bt_link.valid_link || false, bt_link.host, bt_link.error_message, now, now]
122-
sql = "INSERT INTO #{table} (#{cols.join(',')}) VALUES (#{vals.map do |v|
123-
conn.quote(v)
124-
end.join(',')})"
125-
conn.execute(sql)
138+
attrs = {
139+
rich_text_id: rich_text.id,
140+
url: link,
141+
link_type: bt_link.link_type || 'website',
142+
external: bt_link.external || false,
143+
valid_link: bt_link.valid_link || false,
144+
host: bt_link.host,
145+
error_message: bt_link.error_message
146+
}
147+
148+
begin
149+
model.create!(attrs.merge(position: index, locale: rich_text.locale))
150+
rescue ActiveRecord::RecordNotUnique
151+
# ignore duplicate insertion races
126152
end
127153
end
128154
end
@@ -133,13 +159,17 @@ def parse_uri(link)
133159
end
134160

135161
def create_invalid(rich_text, index, link, invalid_type)
162+
# Create the content link with a default link_type to satisfy DB constraints
163+
bt_link = BetterTogether::Content::Link.create!(url: link, valid_link: false, error_message: invalid_type,
164+
link_type: 'website')
165+
136166
BetterTogether::Metrics::RichTextLink.create!(
137167
rich_text_id: rich_text.id,
138168
rich_text_record_id: rich_text.record_id,
139169
rich_text_record_type: rich_text.record_type,
140170
position: index,
141171
locale: rich_text.locale,
142-
link: BetterTogether::Content::Link.create!(url: link, valid_link: false, error_message: invalid_type)
172+
link: bt_link
143173
)
144174
end
145175

db/migrate/20250902203000_add_rich_text_link_associations.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
# Migration to add association columns to better_together_metrics_rich_text_links
24
class AddRichTextLinkAssociations < ActiveRecord::Migration[7.1]
35
def change
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
# Migration to add position and locale columns (and a unique index) to the
4+
# better_together_metrics_rich_text_links table for ordering and locale-aware
5+
# rich-text link tracking.
6+
class AddPositionAndLocaleToRichTextLinks < ActiveRecord::Migration[7.1]
7+
def change
8+
add_column :better_together_metrics_rich_text_links, :position, :integer, null: false, default: 0
9+
add_column :better_together_metrics_rich_text_links, :locale, :string, limit: 5, null: false,
10+
default: I18n.default_locale
11+
12+
add_index :better_together_metrics_rich_text_links, %i[rich_text_id position locale],
13+
name: 'idx_bt_rtl_on_rich_text_pos_loc', unique: true
14+
end
15+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
# Migration to rename a legacy `valid` boolean column to `valid_link` on the
4+
# better_together_metrics_rich_text_links table to avoid method name collisions
5+
# with ActiveRecord predicate methods.
6+
class RenameValidToValidLinkOnMetricsRichTextLinks < ActiveRecord::Migration[7.1]
7+
def change
8+
table = :better_together_metrics_rich_text_links
9+
return unless column_exists?(table, :valid) && !column_exists?(table, :valid_link)
10+
11+
rename_column table, :valid, :valid_link
12+
end
13+
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
# Migration to ensure `link_type` on better_together_content_links has a sensible
4+
# default (`'website'`) and is non-nullable to satisfy callers that expect a
5+
# present link_type value.
6+
class EnsureLinkTypeDefaultOnContentLinks < ActiveRecord::Migration[7.1]
7+
def up
8+
table = :better_together_content_links
9+
if column_exists?(table, :link_type)
10+
change_column_default table, :link_type, 'website'
11+
# Set existing nulls to default before enforcing NOT NULL
12+
execute <<-SQL.squish
13+
UPDATE #{table} SET link_type = 'website' WHERE link_type IS NULL
14+
SQL
15+
change_column_null table, :link_type, false
16+
else
17+
add_column table, :link_type, :string, null: false, default: 'website'
18+
end
19+
end
20+
21+
def down
22+
table = :better_together_content_links
23+
return unless column_exists?(table, :link_type)
24+
25+
change_column_null table, :link_type, true
26+
change_column_default table, :link_type, nil
27+
end
28+
end

spec/dummy/db/schema.rb

Lines changed: 6 additions & 3 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[7.2].define(version: 2025_09_02_203001) do
13+
ActiveRecord::Schema[7.2].define(version: 2025_09_02_203004) do
1414
# These are extensions that must be enabled in order to support this database
1515
enable_extension "pgcrypto"
1616
enable_extension "plpgsql"
@@ -358,7 +358,7 @@
358358
t.integer "lock_version", default: 0, null: false
359359
t.datetime "created_at", null: false
360360
t.datetime "updated_at", null: false
361-
t.string "link_type", null: false
361+
t.string "link_type", default: "website", null: false
362362
t.string "url", null: false
363363
t.string "scheme"
364364
t.string "host"
@@ -914,13 +914,16 @@
914914
t.string "url", null: false
915915
t.string "link_type", null: false
916916
t.boolean "external", null: false
917-
t.boolean "valid", default: false
917+
t.boolean "valid_link", default: false
918918
t.string "host"
919919
t.text "error_message"
920920
t.uuid "link_id"
921921
t.string "rich_text_record_type"
922922
t.uuid "rich_text_record_id"
923+
t.integer "position", default: 0, null: false
924+
t.string "locale", limit: 5, default: "en", null: false
923925
t.index ["link_id"], name: "bt_metrics_rich_text_links_on_link_id"
926+
t.index ["rich_text_id", "position", "locale"], name: "idx_bt_rtl_on_rich_text_pos_loc", unique: true
924927
t.index ["rich_text_id"], name: "index_better_together_metrics_rich_text_links_on_rich_text_id"
925928
t.index ["rich_text_record_type", "rich_text_record_id"], name: "bt_metrics_rich_text_links_on_rich_text_record"
926929
end

0 commit comments

Comments
 (0)