diff --git a/app/models/badge.rb b/app/models/badge.rb index 6a9be1a92..913fd252a 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -12,6 +12,11 @@ class Badge < ApplicationRecord validates :title, presence: true, uniqueness: true before_validation :generate_slug + after_save :bust_path + + def path + "/badge/#{slug}" + end def self.id_for_slug(slug) select(:id).find_by(slug: slug)&.id @@ -22,4 +27,10 @@ class Badge < ApplicationRecord def generate_slug self.slug = CGI.escape(title.to_s).parameterize end + + def bust_path + cache_bust = EdgeCache::Bust.new + cache_bust.call(path) + cache_bust.call("#{path}?i=i") + end end diff --git a/app/views/badges/_badge_detail.html.erb b/app/views/badges/_badge_detail.html.erb deleted file mode 100644 index 1056ed48b..000000000 --- a/app/views/badges/_badge_detail.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -
- <%= badge.title %> badge -
-
-

<%= badge.title %>

-

<%= badge.description %>

-
- -
-
diff --git a/app/views/badges/index.html.erb b/app/views/badges/index.html.erb index 30f9eaac1..c03028143 100644 --- a/app/views/badges/index.html.erb +++ b/app/views/badges/index.html.erb @@ -1,33 +1,19 @@ <% title t("views.badges.meta.title") %>
-
-

<%= t("views.badges.heading") %>

-

<%= t("views.badges.desc") %>

-
+
+

<%= t("views.badges.heading") %>

+

<%= t("views.badges.desc") %>

+
<% @badges.each do |badge| %> -
-
- " - src="<%= optimized_image_url(badge.badge_image_url, width: 192) %>" - alt="<%= badge.title %> badge" - title="<%= badge.title %>" - loading="lazy" /> -
- - - -
+ + " + src="<%= optimized_image_url(badge.badge_image_url, width: 375) %>" + alt="<%= badge.title %> badge" + title="<%= badge.title %>" + loading="lazy" /> + <% end %>
-
+ diff --git a/app/views/badges/show.html.erb b/app/views/badges/show.html.erb new file mode 100644 index 000000000..ee4b917de --- /dev/null +++ b/app/views/badges/show.html.erb @@ -0,0 +1,14 @@ +<% title t("views.badges.meta.name", title: @badge.title) %> + +
+
+

<%= t("views.badges.name", title: @badge.title) %>

+
+

+ <%= @badge.title %> badge +

+

+ <%= @badge.description %> +

+
+
diff --git a/app/views/users/_badges_area.html.erb b/app/views/users/_badges_area.html.erb index 174f50837..84e03f602 100644 --- a/app/views/users/_badges_area.html.erb +++ b/app/views/users/_badges_area.html.erb @@ -6,7 +6,7 @@
<% achievements.each_with_index do |achievement, i| %> -
-
- - + <% end %>
diff --git a/config/routes.rb b/config/routes.rb index c3e766481..4f3dc43d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -314,6 +314,8 @@ Rails.application.routes.draw do get "/t/:tag/admin", to: "tags#admin" patch "/tag/:id", to: "tags#update" + get "/badge/:slug", to: "badges#show", as: :badge + get "/top/:timeframe", to: "stories#index" get "/:timeframe", to: "stories#index", constraints: { timeframe: /latest/ } diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb index 1955644dc..4fed769dc 100644 --- a/spec/models/badge_spec.rb +++ b/spec/models/badge_spec.rb @@ -31,6 +31,35 @@ RSpec.describe Badge, type: :model do end end + context "when callbacks are triggered after save" do + let!(:badge) { create(:badge) } + + describe "cache busting" do + before do + allow(EdgeCache::Bust).to receive(:new).and_return(cache_bust) + allow(cache_bust).to receive(:call) + end + + it "calls the cache buster with the path" do + badge.save + + expect(cache_bust).to have_received(:call).with(badge.path) + end + + it "calls the cache buster with the internal path" do + badge.save + + expect(cache_bust).to have_received(:call).with("#{badge.path}?i=i") + end + end + end + + describe "#path" do + it "returns the path of the badge" do + expect(badge.path).to eq("/badge/#{badge.slug}") + end + end + describe "#slug" do it "generates the correct slug for C" do badge = build(:badge, title: "C") diff --git a/spec/requests/badges_spec.rb b/spec/requests/badges_spec.rb index 25baf7647..8cf978975 100644 --- a/spec/requests/badges_spec.rb +++ b/spec/requests/badges_spec.rb @@ -28,4 +28,21 @@ RSpec.describe "Badges", type: :request do end end end + + describe "GET /badge/:slug" do + let(:badge) { create(:badge) } + + context "when badge exists" do + it "shows the badge" do + get "/badge/#{badge.slug}" + expect(response.body).to include CGI.escapeHTML(badge.title) + end + end + + context "when badge does not exist" do + it "renders 404" do + expect { get "/badge/that-does-not-exists" }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end end diff --git a/spec/system/admin/admin_awards_badges_spec.rb b/spec/system/admin/admin_awards_badges_spec.rb index 9d8c59627..8016da4e6 100644 --- a/spec/system/admin/admin_awards_badges_spec.rb +++ b/spec/system/admin/admin_awards_badges_spec.rb @@ -43,7 +43,7 @@ RSpec.describe "Admin awards badges", type: :system do visit "/#{user.username}/" - expect(page).to have_css("img[src='#{Badge.last.badge_image.url}']") + expect(page).to have_link(href: badge_path(Badge.last.slug)) end it "does not award badges if no badge is selected", js: true do