Move taggings to bubbles

This commit is contained in:
David Heinemeier Hansson
2025-04-05 22:19:12 +02:00
parent 540316e639
commit ee22e69e6e
7 changed files with 52 additions and 70 deletions
+7 -26
View File
@@ -2,34 +2,15 @@ class Bubbles::TaggingsController < ApplicationController
include BubbleScoped
def new
render partial: "bubbles/tagging", locals: { bubble: @bubble, tags: Current.account.tags }
@tags = Current.account.tags.alphabetically
end
def create
if params[:tag_title].present?
sanitized_title = params[:tag_title].strip.gsub(/\A#/, "")
tag = Current.account.tags.find_or_create_by!(title: sanitized_title)
@bubble.toggle_tag(tag)
else
new_tag_ids = Array(params[:tag_id])
current_tags = @bubble.tags
current_tags.each do |tag|
@bubble.toggle_tag(tag) unless new_tag_ids.include?(tag.id.to_s)
end
new_tag_ids.each do |id|
tag = Current.account.tags.find(id)
@bubble.toggle_tag(tag) unless current_tags.include?(tag)
end
end
@bubble.tags.reload
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace([ @bubble, :tags ], partial: "bubbles/cards/perma/tags", locals: { bubble: @bubble })
end
end
@bubble.toggle_tag(tag_title_param)
end
private
def tag_title_param
params.required(:tag_title).strip.gsub(/\A#/, "")
end
end
+12 -7
View File
@@ -8,8 +8,11 @@ module Bubble::Taggable
scope :tagged_with, ->(tags) { joins(:taggings).where(taggings: { tag: tags }) }
end
def toggle_tag(tag)
tagged_with?(tag) ? untag(tag) : tag(tag)
def toggle_tag(title)
transaction do
tag = find_or_create_tag_with_title(title)
tagged_with?(tag) ? untag_with(tag) : tag_with(tag)
end
end
def tagged_with?(tag)
@@ -17,13 +20,15 @@ module Bubble::Taggable
end
private
def tag(tag)
taggings.create! tag: tag
rescue ActiveRecord::RecordNotUnique
# Already tagged
def find_or_create_tag_with_title(title)
bucket.account.tags.find_or_create_by!(title: title)
end
def untag(tag)
def tag_with(tag)
taggings.create tag: tag
end
def untag_with(tag)
taggings.destroy_by tag: tag
end
end
+2
View File
@@ -8,6 +8,8 @@ class Tag < ApplicationRecord
validates :title, format: { without: /\A#/ }
scope :alphabetically, -> { order("lower(title)") }
def hashtag
"#" + title
end
-31
View File
@@ -1,31 +0,0 @@
<% cache [ bubble, Current.account ] do %>
<%= turbo_frame_tag bubble, :tagging do %>
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
<strong class="popup__title margin-block-half pad-inline-half">Tag this…</strong>
<%= form_with url: bubble_taggings_path(bubble), class: "flex gap-half align-center full-width pad-inline-half margin-block-end" do |form| %>
<%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width" %>
<%= form.button "Add a tag…", type: "submit", class: "btn txt-small" do %>
<%= icon_tag "add" %>
<span class="for-screen-reader">Add a tag</span>
<% end %>
<% end %>
<%= form_with url: bubble_taggings_path(bubble), class: "flex flex-column full-width popup__list", data: { controller: "form" } do |form| %>
<% tags.sort_by { |tag| tag.hashtag.downcase }.each do |tag| %>
<div class="btn popup__item">
<%= form.check_box "tag_id[]", {
checked: bubble.tagged_with?(tag),
data: { action: "change->form#submit" },
id: dom_id(tag, :tag),
include_hidden: false,
}, tag.id %>
<%= form.label "tag_id[]", tag.hashtag, for: dom_id(tag, :tag), class: "overflow-ellipsis" %>
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" %>
</div>
<% end %>
<% end %>
</dialog>
<% end %>
<% end %>
@@ -0,0 +1 @@
<%= turbo_stream.replace([ @bubble, :tags ], partial: "bubbles/cards/perma/tags", locals: { bubble: @bubble }) %>
+24
View File
@@ -0,0 +1,24 @@
<% cache [ @bubble, Current.account ] do %>
<%= turbo_frame_tag @bubble, :tagging do %>
<dialog class="popup panel flex-column align-start justify-start fill-white shadow" data-dialog-target="dialog">
<strong class="popup__title margin-block-half pad-inline-half">Tag this…</strong>
<%= form_with url: bubble_taggings_path(@bubble), class: "flex gap-half align-center full-width pad-inline-half margin-block-end" do |form| %>
<%= form.text_field :tag_title, placeholder: "New tag name...", class: "input txt-small full-width" %>
<%= form.button "Add a tag…", type: "submit", class: "btn txt-small" do %>
<%= icon_tag "add" %>
<span class="for-screen-reader">Add a tag</span>
<% end %>
<% end %>
<div class: "flex flex-column full-width popup__list">
<% @tags.each do |tag| %>
<div class="btn popup__item">
<%= button_to tag.hashtag, bubble_taggings_path(@bubble, params: { tag_title: tag.title }), method: :post %>
<%= icon_tag "check", size: 18, class: "checked flex-item-justify-end" if @bubble.tagged_with?(tag) %>
</div>
<% end %>
</div>
</dialog>
<% end %>
<% end %>
+6 -6
View File
@@ -50,20 +50,20 @@ class BubbleTest < ActiveSupport::TestCase
end
test "tag toggling" do
assert bubbles(:logo).tagged_with?(tags(:web))
assert bubbles(:logo).tagged_with?(tags(:web).title)
assert_difference "bubbles(:logo).taggings.count", -1 do
bubbles(:logo).toggle_tag tags(:web)
bubbles(:logo).toggle_tag tags(:web).title
end
assert_not bubbles(:logo).tagged_with?(tags(:web))
assert_not bubbles(:logo).tagged_with?(tags(:web).title)
assert_difference "bubbles(:logo).taggings.count", +1 do
bubbles(:logo).toggle_tag tags(:web)
bubbles(:logo).toggle_tag tags(:web).title
end
assert bubbles(:logo).tagged_with?(tags(:web))
assert bubbles(:logo).tagged_with?(tags(:web).title)
assert_difference %w[ bubbles(:logo).taggings.count accounts("37s").tags.count ], +1 do
bubbles(:logo).toggle_tag Tag.new(title: "prioritized")
bubbles(:logo).toggle_tag "prioritized"
end
assert_equal "prioritized", bubbles(:logo).taggings.last.tag.title
end