Skip to content
Draft
4 changes: 2 additions & 2 deletions app/controllers/cards_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class CardsController < ApplicationController
wrap_parameters :card, include: %i[ title description image created_at last_active_at ]
wrap_parameters :card, include: %i[ title description image created_at last_active_at tag_ids ]

include FilterScoped

Expand Down Expand Up @@ -68,6 +68,6 @@ def ensure_permission_to_administer_card
end

def card_params
params.expect(card: [ :title, :description, :image, :created_at, :last_active_at ])
params.expect(card: [ :title, :description, :image, :created_at, :last_active_at, tag_ids: [] ])
end
end
7 changes: 7 additions & 0 deletions app/models/card/taggable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ module Card::Taggable
scope :tagged_with, ->(tags) { joins(:taggings).where(taggings: { tag: tags }) }
end

def tag_ids=(ids)
ids = Array(ids).compact_blank
account_scope = account || board&.account || Current.account
Comment thread
robzolkos marked this conversation as resolved.
Outdated

self.tags = ids.present? ? account_scope.tags.find(ids) : []
end

def toggle_tag_with(title)
tag = account.tags.find_or_create_by!(title: title)

Expand Down
28 changes: 28 additions & 0 deletions test/controllers/api/flat_json_params_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ class FlatJsonParamsTest < ActionDispatch::IntegrationTest
assert_equal "Flat description", card.description.to_plain_text
end

test "create card with flat JSON and tag_ids" do
tag = tags(:mobile)

assert_difference -> { Card.count }, +1 do
post board_cards_path(boards(:writebook)),
params: { title: "Flat tagged card", tag_ids: [ tag.id ] },
as: :json
end

assert_response :created
card = Card.last
assert_equal [ tag ], card.reload.tags
assert_equal [ tag.title ], @response.parsed_body["tags"]
end

test "update card with flat JSON" do
card = cards(:logo)

Expand All @@ -88,6 +103,19 @@ class FlatJsonParamsTest < ActionDispatch::IntegrationTest
assert_equal "Updated flat", card.description.to_plain_text
end

test "update card with flat JSON and tag_ids" do
card = cards(:logo)
tag = tags(:mobile)

put card_path(card),
params: { tag_ids: [ tag.id ] },
as: :json

assert_response :success
assert_equal [ tag ], card.reload.tags
assert_equal [ tag.title ], @response.parsed_body["tags"]
end

test "create board with flat JSON" do
assert_difference -> { Board.count }, +1 do
post boards_path, params: { name: "Flat board" }, as: :json
Expand Down
89 changes: 89 additions & 0 deletions test/controllers/cards_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,43 @@ class CardsControllerTest < ActionDispatch::IntegrationTest
assert_equal "Big if true", card.description.to_plain_text
end

test "create as JSON with tag_ids applies tags to the created card" do
tag = tags(:mobile)

assert_difference -> { Card.count }, +1 do
post board_cards_path(boards(:writebook)),
params: { card: { title: "Tagged card", tag_ids: [ tag.id ] } },
as: :json
assert_response :created
end

card = Card.last
assert_equal [ tag ], card.reload.tags
assert_equal [ tag.title ], @response.parsed_body["tags"]
end

test "create as JSON with nonexistent tag_ids returns not found" do
assert_no_difference -> { Card.count } do
post board_cards_path(boards(:writebook)),
params: { card: { title: "Tagged card", tag_ids: [ "does-not-exist" ] } },
as: :json
end

assert_response :not_found
end

test "create as JSON with foreign-account tag_ids returns not found" do
foreign_tag = accounts(:initech).tags.create!(title: "foreign")

assert_no_difference -> { Card.count } do
post board_cards_path(boards(:writebook)),
params: { card: { title: "Tagged card", tag_ids: [ foreign_tag.id ] } },
as: :json
end

assert_response :not_found
end

test "create as JSON with custom created_at" do
custom_time = Time.utc(2024, 1, 15, 10, 30, 0)

Expand Down Expand Up @@ -293,6 +330,58 @@ class CardsControllerTest < ActionDispatch::IntegrationTest
assert_equal "Update test", card.reload.title
end

test "update as JSON with tag_ids updates tags on the card" do
card = cards(:logo)
tag = tags(:mobile)

put card_path(card, format: :json), params: { card: { tag_ids: [ tag.id ] } }
assert_response :success

assert_equal [ tag ], card.reload.tags
assert_equal [ tag.title ], @response.parsed_body["tags"]
end

test "update as JSON without tag_ids preserves existing tags" do
Comment thread
robzolkos marked this conversation as resolved.
card = cards(:logo)

put card_path(card, format: :json), params: { card: { title: "Updated title" } }
assert_response :success

assert_equal [ tags(:web) ], card.reload.tags
assert_equal [ tags(:web).title ], @response.parsed_body["tags"]
end

test "update as JSON with foreign-account tag_ids returns not found" do
card = cards(:logo)
foreign_tag = accounts(:initech).tags.create!(title: "foreign")

put card_path(card, format: :json), params: { card: { tag_ids: [ foreign_tag.id ] } }

assert_response :not_found
assert_equal [ tags(:web) ], card.reload.tags
end

test "update as JSON with description and tag_ids busts the card cache key" do
card = cards(:logo)
original_cache_key = card.cache_key_with_version
tag = tags(:mobile)

travel 1.minute do
put card_path(card, format: :json), params: {
card: {
description: "Updated description",
tag_ids: [ tag.id ]
}
}
end

assert_response :success
assert_not_equal original_cache_key, card.reload.cache_key_with_version
assert_equal "Updated description", card.description.to_plain_text.strip
assert_equal [ tag ], card.tags
assert_equal [ tag.title ], @response.parsed_body["tags"]
end

test "delete as JSON" do
card = cards(:logo)

Expand Down
34 changes: 34 additions & 0 deletions test/models/card/taggable_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,38 @@ class Card::TaggableTest < ActiveSupport::TestCase

assert_not_equal cards(:logo).tags.last, cards(:paycheck).tags.last
end

test "updating just tag_ids touches the card and board" do
board = @card.board

travel 1.minute do
assert_changes -> { @card.reload.updated_at } do
assert_changes -> { board.reload.updated_at } do
@card.update!(tag_ids: [ tags(:web).id, tags(:mobile).id ])
end
end
end
end

test "updating tag_ids with an empty array clears tags" do
assert_equal [ tags(:web) ], @card.tags

@card.update!(tag_ids: [])

assert_empty @card.reload.tags
end

test "updating tag_ids raises when a tag does not exist" do
assert_raises(ActiveRecord::RecordNotFound) do
@card.update!(tag_ids: [ "does-not-exist" ])
end
end

test "updating tag_ids raises when the tag belongs to another account" do
foreign_tag = accounts(:initech).tags.create!(title: "foreign")

assert_raises(ActiveRecord::RecordNotFound) do
@card.update!(tag_ids: [ foreign_tag.id ])
end
end
end