Files
sure/test/controllers/transactions/bulk_updates_controller_test.rb
Ang Wei Feng (Ted) c77971ea0d fix: Preserve tags on bulk edits (take 3) (#889)
* fix: handle tags separately from entryable_attributes in bulk updates

Tags use a join table (taggings) rather than a direct column, which means
empty tag_ids clears all tags rather than meaning "no change". This caused
bulk category-only edits to accidentally clear existing tags.

This fix:
- Removes tag_ids from entryable_attributes in Entry.bulk_update!
- Adds update_tags parameter to explicitly control tag updates
- Uses params.key?(:tag_ids) in controller to detect explicit tag changes
- Preserves existing tags when tag_ids is not provided in the request

This is a cleaner architectural solution compared to tracking "touched"
state in the frontend, as it properly acknowledges the semantic difference
between column attributes and join table associations.

https://claude.ai/code/session_014CsmTwjteP4qJs6YZqCKnY

* fix: handle tags separately in API transaction updates

Apply the same pattern to the API endpoint: tags are now handled
separately from entryable_attributes to distinguish between "not
provided" (preserve existing tags) and "explicitly set to empty"
(clear all tags).

This allows API consumers to:
- Update other fields without affecting tags (omit tag_ids)
- Clear all tags (send tag_ids: [])
- Set specific tags (send tag_ids: [id1, id2])

https://claude.ai/code/session_014CsmTwjteP4qJs6YZqCKnY

* Proposed fix

* fix: improve tag handling in bulk updates for transactions

* fix: allow bulk edit to clear/preserve tags by omitting hidden multi-select field

* PR comments

* Dumb copy/paste error

* Linter

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-02-06 14:11:46 +01:00

122 lines
4.0 KiB
Ruby

require "test_helper"
class Transactions::BulkUpdatesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in @user = users(:family_admin)
end
test "bulk update" do
transactions = @user.family.entries.transactions
assert_difference [ "Entry.count", "Transaction.count" ], 0 do
post transactions_bulk_update_url, params: {
bulk_update: {
entry_ids: transactions.map(&:id),
date: 1.day.ago.to_date,
category_id: Category.second.id,
merchant_id: Merchant.second.id,
tag_ids: [ Tag.first.id, Tag.second.id ],
notes: "Updated note"
}
}
end
assert_redirected_to transactions_url
assert_equal "#{transactions.count} transactions updated", flash[:notice]
transactions.reload.each do |transaction|
assert_equal 1.day.ago.to_date, transaction.date
assert_equal Category.second, transaction.transaction.category
assert_equal Merchant.second, transaction.transaction.merchant
assert_equal "Updated note", transaction.notes
assert_equal [ Tag.first.id, Tag.second.id ], transaction.entryable.tag_ids.sort
end
end
test "bulk update preserves tags when tag_ids not provided" do
transaction_entry = @user.family.entries.transactions.first
original_tags = [ Tag.first, Tag.second ]
transaction_entry.transaction.tags = original_tags
transaction_entry.transaction.save!
# Update only the category, without providing tag_ids
post transactions_bulk_update_url, params: {
bulk_update: {
entry_ids: [ transaction_entry.id ],
category_id: Category.second.id
}
}
assert_redirected_to transactions_url
transaction_entry.reload
assert_equal Category.second, transaction_entry.transaction.category
# Tags should be preserved since tag_ids was not in the request
assert_equal original_tags.map(&:id).sort, transaction_entry.transaction.tag_ids.sort
end
test "bulk update clears tags when tag_ids is blank string array (web multi-select None)" do
transaction_entry = @user.family.entries.transactions.first
original_tags = [ Tag.first, Tag.second ]
transaction_entry.transaction.tags = original_tags
transaction_entry.transaction.save!
# For a multiple select, choosing the blank ("None") option submits a blank value.
post transactions_bulk_update_url, params: {
bulk_update: {
entry_ids: [ transaction_entry.id ],
category_id: Category.second.id,
tag_ids: [ "" ]
}
}
assert_redirected_to transactions_url
transaction_entry.reload
assert_equal Category.second, transaction_entry.transaction.category
assert_empty transaction_entry.transaction.tags
end
test "bulk update clears tags when empty tag_ids explicitly provided (JSON)" do
transaction_entry = @user.family.entries.transactions.first
transaction_entry.transaction.tags = [ Tag.first, Tag.second ]
transaction_entry.transaction.save!
post transactions_bulk_update_url,
params: {
bulk_update: {
entry_ids: [ transaction_entry.id ],
category_id: Category.second.id,
tag_ids: []
}
},
as: :json
assert_redirected_to transactions_url
transaction_entry.reload
assert_equal Category.second, transaction_entry.transaction.category
assert_empty transaction_entry.transaction.tags
end
test "bulk update replaces tags when tag_ids explicitly provided" do
transaction_entry = @user.family.entries.transactions.first
transaction_entry.transaction.tags = [ Tag.first ]
transaction_entry.transaction.save!
new_tag = Tag.second
post transactions_bulk_update_url, params: {
bulk_update: {
entry_ids: [ transaction_entry.id ],
tag_ids: [ new_tag.id ]
}
}
assert_redirected_to transactions_url
transaction_entry.reload
assert_equal [ new_tag.id ], transaction_entry.transaction.tag_ids
end
end