mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-18 20:59:39 +02:00
Fix attribute locking namespace conflict, duplicate syncs
This commit is contained in:
parent
ab5bce3462
commit
137219c121
11 changed files with 87 additions and 28 deletions
|
@ -61,7 +61,7 @@ class TransactionsController < ApplicationController
|
|||
if @entry.save
|
||||
@entry.sync_account_later
|
||||
@entry.lock_saved_attributes!
|
||||
@entry.transaction.lock!(:tag_ids) if @entry.transaction.tags.any?
|
||||
@entry.transaction.lock_attr!(:tag_ids) if @entry.transaction.tags.any?
|
||||
|
||||
flash[:notice] = "Transaction created"
|
||||
|
||||
|
@ -88,7 +88,7 @@ class TransactionsController < ApplicationController
|
|||
|
||||
@entry.sync_account_later
|
||||
@entry.lock_saved_attributes!
|
||||
@entry.transaction.lock!(:tag_ids) if @entry.transaction.tags.any?
|
||||
@entry.transaction.lock_attr!(:tag_ids) if @entry.transaction.tags.any?
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to account_path(@entry.account), notice: "Transaction updated" }
|
||||
|
|
|
@ -42,17 +42,17 @@ module Enrichable
|
|||
!locked?(attr)
|
||||
end
|
||||
|
||||
def lock!(attr)
|
||||
def lock_attr!(attr)
|
||||
update!(locked_attributes: locked_attributes.merge(attr.to_s => Time.current))
|
||||
end
|
||||
|
||||
def unlock!(attr)
|
||||
def unlock_attr!(attr)
|
||||
update!(locked_attributes: locked_attributes.except(attr.to_s))
|
||||
end
|
||||
|
||||
def lock_saved_attributes!
|
||||
saved_changes.keys.reject { |attr| ignored_enrichable_attributes.include?(attr) }.each do |attr|
|
||||
lock!(attr)
|
||||
lock_attr!(attr)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,20 +9,28 @@ module Syncable
|
|||
raise NotImplementedError, "Subclasses must implement the syncing? method"
|
||||
end
|
||||
|
||||
# Schedules a sync for syncable. If there is an existing sync pending/syncing for this syncable,
|
||||
# we do not create a new sync, and attempt to expand the sync window if needed.
|
||||
def sync_later(parent_sync: nil, window_start_date: nil, window_end_date: nil)
|
||||
Sync.transaction do
|
||||
# Since we're scheduling a new sync, mark old syncs for this syncable as stale
|
||||
self.syncs.incomplete.find_each(&:mark_stale!)
|
||||
with_lock do
|
||||
sync = self.syncs.incomplete.first
|
||||
|
||||
new_sync = self.syncs.create!(
|
||||
parent: parent_sync,
|
||||
window_start_date: window_start_date,
|
||||
window_end_date: window_end_date
|
||||
)
|
||||
if sync
|
||||
Rails.logger.info("There is an existing sync, expanding window if needed (#{sync.id})")
|
||||
sync.expand_window_if_needed(window_start_date, window_end_date)
|
||||
else
|
||||
sync = self.syncs.create!(
|
||||
parent: parent_sync,
|
||||
window_start_date: window_start_date,
|
||||
window_end_date: window_end_date
|
||||
)
|
||||
|
||||
SyncJob.perform_later(new_sync)
|
||||
SyncJob.perform_later(sync)
|
||||
end
|
||||
|
||||
new_sync
|
||||
sync
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ class Entry < ApplicationRecord
|
|||
entry.update! bulk_attributes
|
||||
|
||||
entry.lock_saved_attributes!
|
||||
entry.entryable.lock!(:tag_ids) if entry.transaction? && entry.transaction.tags.any?
|
||||
entry.entryable.lock_attr!(:tag_ids) if entry.transaction? && entry.transaction.tags.any?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class Family::AutoCategorizer
|
|||
end
|
||||
|
||||
scope.each do |transaction|
|
||||
transaction.lock!(:category_id)
|
||||
transaction.lock_attr!(:category_id)
|
||||
|
||||
auto_categorization = result.data.find { |c| c.transaction_id == transaction.id }
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class Family::AutoMerchantDetector
|
|||
end
|
||||
|
||||
scope.each do |transaction|
|
||||
transaction.lock!(:merchant_id)
|
||||
transaction.lock_attr!(:merchant_id)
|
||||
|
||||
auto_detection = result.data.find { |c| c.transaction_id == transaction.id }
|
||||
|
||||
|
|
|
@ -95,6 +95,29 @@ class Sync < ApplicationRecord
|
|||
parent&.finalize_if_all_children_finalized
|
||||
end
|
||||
|
||||
# If a sync is pending, we can adjust the window if new syncs are created with a wider window.
|
||||
def expand_window_if_needed(new_window_start_date, new_window_end_date)
|
||||
return unless pending?
|
||||
return if self.window_start_date.nil? && self.window_end_date.nil? # already as wide as possible
|
||||
|
||||
earliest_start_date = if self.window_start_date && new_window_start_date
|
||||
[self.window_start_date, new_window_start_date].min
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
latest_end_date = if self.window_end_date && new_window_end_date
|
||||
[self.window_end_date, new_window_end_date].max
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
update(
|
||||
window_start_date: earliest_start_date,
|
||||
window_end_date: latest_end_date
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def log_status_change
|
||||
Rails.logger.info("changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})")
|
||||
|
|
|
@ -20,7 +20,7 @@ class AutoSyncTest < ActionDispatch::IntegrationTest
|
|||
travel_to Time.current.beginning_of_day
|
||||
last_sync_datetime = 1.hour.ago
|
||||
|
||||
Sync.create!(syncable: @family, created_at: last_sync_datetime)
|
||||
Sync.create!(syncable: @family, created_at: last_sync_datetime, status: "completed")
|
||||
|
||||
assert_difference "Sync.count", 1 do
|
||||
get root_path
|
||||
|
@ -32,7 +32,7 @@ class AutoSyncTest < ActionDispatch::IntegrationTest
|
|||
|
||||
last_created_sync_at = 23.hours.ago
|
||||
|
||||
Sync.create!(syncable: @family, created_at: last_created_sync_at)
|
||||
Sync.create!(syncable: @family, created_at: last_created_sync_at, status: "completed")
|
||||
|
||||
assert_no_difference "Sync.count" do
|
||||
get root_path
|
||||
|
|
|
@ -18,11 +18,19 @@ module SyncableInterfaceTest
|
|||
@syncable.perform_sync(mock_sync)
|
||||
end
|
||||
|
||||
test "any prior syncs for the same syncable entity are marked stale when new sync is requested" do
|
||||
stale_sync = @syncable.sync_later
|
||||
new_sync = @syncable.sync_later
|
||||
test "second sync request widens existing pending window" do
|
||||
later_start = 2.days.ago.to_date
|
||||
first_sync = @syncable.sync_later(window_start_date: later_start, window_end_date: later_start)
|
||||
|
||||
assert_equal "stale", stale_sync.reload.status
|
||||
assert_equal "pending", new_sync.reload.status
|
||||
earlier_start = 5.days.ago.to_date
|
||||
wider_end = Date.current
|
||||
|
||||
assert_no_difference "@syncable.syncs.count" do
|
||||
@syncable.sync_later(window_start_date: earlier_start, window_end_date: wider_end)
|
||||
end
|
||||
|
||||
first_sync.reload
|
||||
assert_equal earlier_start, first_sync.window_start_date
|
||||
assert_equal wider_end, first_sync.window_end_date
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ class Rule::ActionTest < ActiveSupport::TestCase
|
|||
|
||||
test "set_transaction_category" do
|
||||
# Does not modify transactions that are locked (user edited them)
|
||||
@txn1.lock!(:category_id)
|
||||
@txn1.lock_attr!(:category_id)
|
||||
|
||||
action = Rule::Action.new(
|
||||
rule: @transaction_rule,
|
||||
|
@ -42,7 +42,7 @@ class Rule::ActionTest < ActiveSupport::TestCase
|
|||
tag = @family.tags.create!(name: "Rule test tag")
|
||||
|
||||
# Does not modify transactions that are locked (user edited them)
|
||||
@txn1.lock!(:tag_ids)
|
||||
@txn1.lock_attr!(:tag_ids)
|
||||
|
||||
action = Rule::Action.new(
|
||||
rule: @transaction_rule,
|
||||
|
@ -63,7 +63,7 @@ class Rule::ActionTest < ActiveSupport::TestCase
|
|||
merchant = @family.merchants.create!(name: "Rule test merchant")
|
||||
|
||||
# Does not modify transactions that are locked (user edited them)
|
||||
@txn1.lock!(:merchant_id)
|
||||
@txn1.lock_attr!(:merchant_id)
|
||||
|
||||
action = Rule::Action.new(
|
||||
rule: @transaction_rule,
|
||||
|
@ -84,7 +84,7 @@ class Rule::ActionTest < ActiveSupport::TestCase
|
|||
new_name = "Renamed Transaction"
|
||||
|
||||
# Does not modify transactions that are locked (user edited them)
|
||||
@txn1.lock!(:name)
|
||||
@txn1.lock_attr!(:name)
|
||||
|
||||
action = Rule::Action.new(
|
||||
rule: @transaction_rule,
|
||||
|
|
|
@ -188,4 +188,24 @@ class SyncTest < ActiveSupport::TestCase
|
|||
assert_equal "stale", stale_pending.reload.status
|
||||
assert_equal "stale", stale_syncing.reload.status
|
||||
end
|
||||
|
||||
test "expand_window_if_needed widens start and end dates on a pending sync" do
|
||||
initial_start = 1.day.ago.to_date
|
||||
initial_end = 1.day.ago.to_date
|
||||
|
||||
sync = Sync.create!(
|
||||
syncable: accounts(:depository),
|
||||
window_start_date: initial_start,
|
||||
window_end_date: initial_end
|
||||
)
|
||||
|
||||
new_start = 5.days.ago.to_date
|
||||
new_end = Date.current
|
||||
|
||||
sync.expand_window_if_needed(new_start, new_end)
|
||||
sync.reload
|
||||
|
||||
assert_equal new_start, sync.window_start_date
|
||||
assert_equal new_end, sync.window_end_date
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue