Skip to content

Commit 8156153

Browse files
committed
Rubocop fixes
1 parent b61f8d8 commit 8156153

File tree

10 files changed

+241
-89
lines changed

10 files changed

+241
-89
lines changed

app/jobs/better_together/metrics/external_link_checker_job.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,31 @@
55

66
module BetterTogether
77
module Metrics
8+
# Background job that checks an external link's HTTP status and updates
9+
# the corresponding BetterTogether::Content::Link record with the
10+
# latest check timestamp, status code and validity flag.
811
class ExternalLinkCheckerJob < ApplicationJob
912
queue_as :default
1013

1114
def perform(link_id)
1215
link = BetterTogether::Content::Link.find(link_id)
13-
uri = URI.parse(link.url)
14-
response = http_head(uri)
16+
checker = BetterTogether::Metrics::HttpLinkChecker.new(link.url)
17+
result = checker.call
1518

16-
link.update!(last_checked_at: Time.current, latest_status_code: response.code.to_s, valid_link: response.is_a?(Net::HTTPSuccess))
17-
rescue StandardError => e
18-
link.update!(last_checked_at: Time.current, latest_status_code: nil, valid_link: false,
19-
error_message: e.message)
19+
update_link_from_result(link, result)
2020
end
2121

2222
private
2323

24-
def http_head(uri)
25-
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 5, read_timeout: 5) do |http|
26-
request = Net::HTTP::Head.new(uri.request_uri)
27-
http.request(request)
28-
end
24+
def update_link_from_result(link, result)
25+
attrs = {
26+
last_checked_at: Time.current,
27+
latest_status_code: result.status_code,
28+
valid_link: result.success
29+
}
30+
31+
attrs[:error_message] = result.error&.message unless result.success
32+
link.update!(attrs)
2933
end
3034
end
3135
end

app/jobs/better_together/metrics/internal_link_checker_job.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,31 @@
55

66
module BetterTogether
77
module Metrics
8+
# Background job that checks an internal link's HTTP status and updates
9+
# the corresponding BetterTogether::Content::Link record with the
10+
# latest check timestamp, status code and validity flag.
811
class InternalLinkCheckerJob < ApplicationJob
912
queue_as :default
1013

1114
def perform(link_id)
1215
link = BetterTogether::Content::Link.find(link_id)
13-
uri = URI.parse(link.url)
14-
response = http_head(uri)
16+
checker = BetterTogether::Metrics::HttpLinkChecker.new(link.url)
17+
result = checker.call
1518

16-
link.update!(last_checked_at: Time.current, latest_status_code: response.code.to_s, valid_link: response.is_a?(Net::HTTPSuccess))
17-
rescue StandardError => e
18-
link.update!(last_checked_at: Time.current, latest_status_code: nil, valid_link: false,
19-
error_message: e.message)
19+
update_link_from_result(link, result)
2020
end
2121

2222
private
2323

24-
def http_head(uri)
25-
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 5, read_timeout: 5) do |http|
26-
request = Net::HTTP::Head.new(uri.request_uri)
27-
http.request(request)
28-
end
24+
def update_link_from_result(link, result)
25+
attrs = {
26+
last_checked_at: Time.current,
27+
latest_status_code: result.status_code,
28+
valid_link: result.success
29+
}
30+
31+
attrs[:error_message] = result.error&.message unless result.success
32+
link.update!(attrs)
2933
end
3034
end
3135
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
require 'net/http'
4+
require 'uri'
5+
6+
module BetterTogether
7+
module Metrics
8+
# Service to perform HTTP checks for a given URI with simple retry logic.
9+
# Returns a struct with :success, :status_code, :error
10+
CheckResult = Struct.new(:success, :status_code, :error)
11+
12+
# Performs a HEAD request with configurable timeouts and retries. The
13+
# service returns a CheckResult struct indicating success, the HTTP
14+
# status code (string) and any error encountered.
15+
class HttpLinkChecker
16+
DEFAULT_RETRIES = 2
17+
DEFAULT_OPEN_TIMEOUT = 5
18+
DEFAULT_READ_TIMEOUT = 5
19+
20+
def initialize(uri, retries: DEFAULT_RETRIES,
21+
open_timeout: DEFAULT_OPEN_TIMEOUT,
22+
read_timeout: DEFAULT_READ_TIMEOUT)
23+
@uri = URI.parse(uri)
24+
@retries = retries
25+
@open_timeout = open_timeout
26+
@read_timeout = read_timeout
27+
end
28+
29+
def call
30+
attempts = 0
31+
begin
32+
attempts += 1
33+
response = http_head(@uri)
34+
CheckResult.new(response.is_a?(Net::HTTPSuccess), response.code.to_s, nil)
35+
rescue StandardError => e
36+
retry if attempts <= @retries
37+
CheckResult.new(false, nil, e)
38+
end
39+
end
40+
41+
private
42+
43+
def http_head(uri)
44+
Net::HTTP.start(
45+
uri.host,
46+
uri.port,
47+
use_ssl: uri.scheme == 'https',
48+
open_timeout: @open_timeout,
49+
read_timeout: @read_timeout
50+
) do |http|
51+
request = Net::HTTP::Head.new(uri.request_uri)
52+
http.request(request)
53+
end
54+
end
55+
end
56+
end
57+
end

app/services/better_together/metrics/rich_text_link_identifier.rb

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module Metrics
88
#
99
# Usage:
1010
# BetterTogether::Metrics::RichTextLinkIdentifier.call
11-
class RichTextLinkIdentifier
11+
class RichTextLinkIdentifier # rubocop:disable Metrics/ClassLength
1212
def self.call(rich_texts: nil)
1313
new(rich_texts: rich_texts).call
1414
end
@@ -17,121 +17,128 @@ def initialize(rich_texts: nil)
1717
@rich_texts = rich_texts
1818
end
1919

20+
# rubocop:disable Metrics/MethodLength
2021
def call
2122
texts = rich_texts || ActionText::RichText.includes(:record).where.not(body: nil)
2223
valid_count = 0
2324
invalid_count = 0
2425

2526
if texts.respond_to?(:find_each)
26-
texts.find_each do |rt|
27-
v, i = process_rich_text(rt)
27+
texts.find_each do |rich_text|
28+
v, i = process_rich_text(rich_text)
2829
valid_count += v
2930
invalid_count += i
3031
end
3132
else
32-
Array(texts).each do |rt|
33-
v, i = process_rich_text(rt)
33+
Array(texts).each do |rich_text|
34+
v, i = process_rich_text(rich_text)
3435
valid_count += v
3536
invalid_count += i
3637
end
3738
end
3839

3940
{ valid: valid_count, invalid: invalid_count }
4041
end
42+
# rubocop:enable Metrics/MethodLength
4143

4244
private
4345

4446
attr_reader :rich_texts
4547

46-
def extract_links(rt)
48+
def extract_links(rich_text)
4749
# ActionText stores HTML; use the body helper to extract hrefs
48-
rt.body.links.uniq
50+
rich_text.body.links.uniq
4951
rescue StandardError
5052
[]
5153
end
5254

53-
def process_rich_text(rt)
55+
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
56+
def process_rich_text(rich_text)
5457
valid_count = 0
5558
invalid_count = 0
56-
links = extract_links(rt)
59+
links = extract_links(rich_text)
5760
return [0, 0] if links.empty?
5861

5962
links.each_with_index do |link, index|
60-
uri = parse_uri(link)
61-
if uri.nil?
62-
create_invalid(rt, index, link, 'undetermined')
63+
uri_obj = parse_uri(link)
64+
if uri_obj.nil?
65+
create_invalid(rich_text, index, link, 'undetermined')
6366
invalid_count += 1
6467
next
6568
end
6669

67-
canonical_host = uri.host
68-
if canonical_host.nil? && uri.scheme.nil?
70+
canonical_host = uri_obj.host
71+
if canonical_host.nil? && uri_obj.scheme.nil?
6972
if link.start_with?('/')
7073
canonical_host = rt_platform_host
7174
else
72-
create_invalid(rt, index, link, 'undetermined')
75+
create_invalid(rich_text, index, link, 'undetermined')
7376
invalid_count += 1
7477
next
7578
end
7679
end
7780

78-
bt_link = BetterTogether::Content::Link.find_or_initialize_by(url: link)
79-
bt_link.host ||= canonical_host
80-
bt_link.scheme ||= uri.scheme
81-
bt_link.external = (canonical_host.present? && (rt_platform_host != canonical_host))
82-
bt_link.save! if bt_link.changed?
83-
84-
# Persist the RichTextLink depending on the schema available.
85-
if BetterTogether::Metrics::RichTextLink.column_names.include?('link_id')
86-
attrs = {
87-
link_id: bt_link.id,
88-
rich_text_id: rt.id,
89-
rich_text_record_id: rt.record_id,
90-
rich_text_record_type: rt.record_type,
91-
position: index,
92-
locale: rt.locale
93-
}
94-
95-
BetterTogether::Metrics::RichTextLink.find_or_create_by!(attrs)
96-
else
97-
# Fallback schema: metrics rich_text_links store URL and metadata inline
98-
# Some legacy schemas use column names (for example `valid`) that clash
99-
# with Active Record method names. To avoid DangerousAttributeError we
100-
# perform a raw INSERT unless a record already exists for this
101-
# (rich_text_id, url) tuple.
102-
model = BetterTogether::Metrics::RichTextLink
103-
unless model.where(rich_text_id: rt.id, url: link).exists?
104-
conn = model.connection
105-
table = model.table_name
106-
now = Time.current
107-
cols = %w[rich_text_id url link_type external valid host error_message created_at updated_at]
108-
vals = [rt.id, link, bt_link.link_type || 'website', bt_link.external || false,
109-
bt_link.valid_link || false, bt_link.host, bt_link.error_message, now, now]
110-
sql = "INSERT INTO #{table} (#{cols.join(',')}) VALUES (#{vals.map { |v| conn.quote(v) }.join(',')})"
111-
conn.execute(sql)
112-
end
113-
end
114-
81+
persist_link_and_rich_text_link(rich_text, link, index, canonical_host, uri_obj)
11582
valid_count += 1
11683
rescue URI::InvalidURIError
117-
create_invalid(rt, index, link, 'invalid_uri')
84+
create_invalid(rich_text, index, link, 'invalid_uri')
11885
invalid_count += 1
11986
end
12087

12188
[valid_count, invalid_count]
12289
end
90+
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
91+
92+
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
93+
def persist_link_and_rich_text_link(rich_text, link, index, canonical_host, uri_obj)
94+
bt_link = BetterTogether::Content::Link.find_or_initialize_by(url: link)
95+
bt_link.host ||= canonical_host
96+
bt_link.scheme ||= uri_obj.scheme
97+
bt_link.external = (canonical_host.present? && (rt_platform_host != canonical_host))
98+
bt_link.save! if bt_link.changed?
99+
100+
# Persist the RichTextLink depending on the schema available.
101+
if BetterTogether::Metrics::RichTextLink.column_names.include?('link_id')
102+
attrs = {
103+
link_id: bt_link.id,
104+
rich_text_id: rich_text.id,
105+
rich_text_record_id: rich_text.record_id,
106+
rich_text_record_type: rich_text.record_type,
107+
position: index,
108+
locale: rich_text.locale
109+
}
110+
111+
BetterTogether::Metrics::RichTextLink.find_or_create_by!(attrs)
112+
else
113+
# 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)
126+
end
127+
end
128+
end
129+
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
123130

124131
def parse_uri(link)
125132
URI.parse(link)
126133
end
127134

128-
def create_invalid(rt, index, link, invalid_type)
135+
def create_invalid(rich_text, index, link, invalid_type)
129136
BetterTogether::Metrics::RichTextLink.create!(
130-
rich_text_id: rt.id,
131-
rich_text_record_id: rt.record_id,
132-
rich_text_record_type: rt.record_type,
137+
rich_text_id: rich_text.id,
138+
rich_text_record_id: rich_text.record_id,
139+
rich_text_record_type: rich_text.record_type,
133140
position: index,
134-
locale: rt.locale,
141+
locale: rich_text.locale,
135142
link: BetterTogether::Content::Link.create!(url: link, valid_link: false, error_message: invalid_type)
136143
)
137144
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
FactoryBot.define do
4+
factory :content_link, class: 'BetterTogether::Content::Link' do
5+
link_type { 'website' }
6+
sequence(:url) { |n| "https://example.test/#{n}" }
7+
scheme { 'https' }
8+
host { URI.parse(url).host }
9+
external { false }
10+
valid_link { false }
11+
end
12+
end

spec/jobs/better_together/metrics/external_link_checker_job_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55

66
module BetterTogether
77
RSpec.describe Metrics::ExternalLinkCheckerJob do
8-
let(:link) { BetterTogether::Content::Link.create!(url: 'https://external.test/', valid_link: false) }
8+
let(:link) { create(:content_link, url: 'https://external.test/', valid_link: false) }
99

1010
it 'updates link status on success' do
1111
stub_request(:head, 'https://external.test/').to_return(status: 200)
1212

1313
described_class.new.perform(link.id)
1414

1515
link.reload
16-
expect(link.valid_link).to be true
17-
expect(link.latest_status_code).to eq('200')
16+
expect([link.valid_link, link.latest_status_code]).to eq([true, '200'])
1817
end
1918
end
2019
end

spec/jobs/better_together/metrics/internal_link_checker_job_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@
55

66
module BetterTogether
77
RSpec.describe Metrics::InternalLinkCheckerJob do
8-
let(:link) { BetterTogether::Content::Link.create!(url: 'https://example.com/', valid_link: false) }
8+
let(:link) { create(:content_link, url: 'https://example.com/', valid_link: false) }
99

1010
it 'updates link status on success' do
1111
stub_request(:head, 'https://example.com/').to_return(status: 200)
1212

1313
described_class.new.perform(link.id)
1414

1515
link.reload
16-
expect(link.valid_link).to be true
17-
expect(link.latest_status_code).to eq('200')
16+
expect([link.valid_link, link.latest_status_code]).to eq([true, '200'])
1817
end
1918
end
2019
end

0 commit comments

Comments
 (0)