-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow admin to remove VAT rate #413
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
module Taxes | ||
VatRateAlreadyExists = Class.new(StandardError) | ||
VatRateNotApplicable = Class.new(StandardError) | ||
VatRateNotExists = Class.new(StandardError) | ||
class SetVatRateHandler | ||
def initialize(event_store) | ||
@repository = Infra::AggregateRootRepository.new(event_store) | ||
|
@@ -41,20 +42,32 @@ def stream_name(order_id) | |
end | ||
|
||
class AddAvailableVatRateHandler | ||
include Infra::Retry | ||
|
||
def initialize(event_store) | ||
@catalog = VatRateCatalog.new(event_store) | ||
@event_store = event_store | ||
end | ||
|
||
def call(cmd) | ||
raise VatRateAlreadyExists if catalog.vat_rate_by_code(cmd.vat_rate.code) | ||
with_retry do | ||
event = last_available_vat_rate_event(cmd.vat_rate.code) | ||
raise VatRateAlreadyExists if event.instance_of?(AvailableVatRateAdded) | ||
|
||
event_store.publish(available_vat_rate_added_event(cmd), stream_name: stream_name(cmd)) | ||
expected_version = event ? event_store.position_in_stream(event.event_id, stream_name(cmd)) : -1 | ||
event_store.publish(available_vat_rate_added_event(cmd), stream_name: stream_name(cmd), expected_version: expected_version) | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :event_store, :catalog | ||
attr_reader :event_store | ||
|
||
def last_available_vat_rate_event(vat_rate_code) | ||
@event_store | ||
.read | ||
.stream("Taxes::AvailableVatRate$#{vat_rate_code}") | ||
.last | ||
end | ||
|
||
def available_vat_rate_added_event(cmd) | ||
AvailableVatRateAdded.new( | ||
|
@@ -69,4 +82,46 @@ def stream_name(cmd) | |
"Taxes::AvailableVatRate$#{cmd.vat_rate.code}" | ||
end | ||
end | ||
|
||
class RemoveAvailableVatRateHandler | ||
include Infra::Retry | ||
|
||
def initialize(event_store) | ||
@event_store = event_store | ||
end | ||
|
||
def call(cmd) | ||
with_retry do | ||
event = last_available_vat_rate_event(cmd.vat_rate_code) | ||
raise VatRateNotExists if event.nil? | ||
|
||
event_store.publish( | ||
available_vat_rate_removed_event(cmd), | ||
stream_name: stream_name(cmd), | ||
expected_version: event_store.position_in_stream(event.event_id, stream_name(cmd)) | ||
) | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :event_store | ||
|
||
def last_available_vat_rate_event(vat_rate_code) | ||
event = event_store | ||
.read | ||
.stream("Taxes::AvailableVatRate$#{vat_rate_code}") | ||
.last | ||
|
||
event if event.instance_of?(AvailableVatRateAdded) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that the method name does something different than the name declares. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I refactored it a bit. I hope it is better now. |
||
end | ||
|
||
def available_vat_rate_removed_event(cmd) | ||
AvailableVatRateRemoved.new(data: { vat_rate_code: cmd.vat_rate_code }) | ||
end | ||
|
||
def stream_name(cmd) | ||
"Taxes::AvailableVatRate$#{cmd.vat_rate_code}" | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,13 +16,17 @@ def vat_rate_for(product_id) | |
end | ||
|
||
def vat_rate_by_code(vat_rate_code) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Outside of scope of this task, but this method got me thinking. This method is called in the AddAvailableVatRateHandler It is called to check if tax rate with given code already exists. If not, then the handler publishes an event. First of all, it breaks the tell don't ask rule. In current implementation there is possibility that two The question is if that is a major or minor problem. Since this if statement has been added for some reason, let's assume it would a major issue if the vat rate code is duplicated. To reproduce this behaviour you can follow those steps: rails-application(dev)* fiber = Fiber.new do
rails-application(dev)* unless event_store.read.stream("AvailableVatRate$#{vat_rate_code}").last
rails-application(dev)* Fiber.yield
rails-application(dev)* event_store.publish(AvailableVatRateAdded.new(data: { vat_rate_code: }), stream_name: "AvailableVatRate$#{vat_rate_code}")
rails-application(dev)* end
rails-application(dev)> end
rails-application(dev)> fiber.resume
RubyEventStore::ActiveRecord::EventInStream Load (0.6ms) SELECT "event_store_events_in_streams".* FROM "event_store_events_in_streams" WHERE "event_store_events_in_streams"."stream" = $1 ORDER BY "event_store_events_in_streams"."id" DESC LIMIT $2 [["stream", "AvailableVatRate$60"], ["LIMIT", 1]]
=> nil # <========= The if statement was checked here, there was no event in stream
rails-application(dev)> event_store.publish(AvailableVatRateAdded.new(data: { vat_rate_code: }), stream_name: "AvailableVatRate$#{vat_rate_code}")
rails-application(dev)> event_store.read.stream("AvailableVatRate$#{vat_rate_code}").to_a.count
=> 2 How do you think we could deal with that? (tip: look at the core concepts section in RES documentation) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the implementation of command handlers not to use I think my code still breaks tell don't ask principle (bc I'm asking what was the last event version) but I'm not sure how can I do it otherwise. Thank you for spotting this and please let me know what you think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, exactly. Good job :) I think that code is in ok shape now. I wouldn't optimize it anymore at this stage. There are no business rules or invariants that would justify introducing an aggregate here. I would leave it as it is and observe how this code changes over time. If, say there is another place that needs the piece of code that: reads the vat rate stream, checks if last vat rate event is X and does something, then I'd think about introducing some concept (not necessarily an aggregate) to keep it together. Perhaps Domain Service? |
||
@event_store | ||
last_available_vat_rate_event = @event_store | ||
.read | ||
.stream("Taxes::AvailableVatRate$#{vat_rate_code}") | ||
.last | ||
&.data | ||
&.fetch(:vat_rate) | ||
&.then { |vat_rate| Infra::Types::VatRate.new(vat_rate) } | ||
|
||
if last_available_vat_rate_event.instance_of?(AvailableVatRateAdded) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you only care about the the if statement wouldn't be necessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually care that the last event is |
||
last_available_vat_rate_event | ||
.data | ||
.fetch(:vat_rate) | ||
.then { |vat_rate| Infra::Types::VatRate.new(vat_rate) } | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module VatRates | ||
class RemoveAvailableVatRate | ||
def call(event) | ||
AvailableVatRate.destroy_by(code: event.data.fetch(:vat_rate_code)) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,14 +13,20 @@ | |
<tr> | ||
<th class="text-left py-2">Code</th> | ||
<th class="text-right py-2">Rate</th> | ||
<th class="text-right py-2"></th> | ||
</tr> | ||
</thead> | ||
|
||
<tbody> | ||
<% @available_vat_rates.each do |available_vat_rate| %> | ||
<tr class="border-t"> | ||
<td class="py-2"><%= available_vat_rate.code %></td> | ||
<td class="py-2 text-right"><%= available_vat_rate.rate %></td> | ||
<td class="text-right py-2"><%= available_vat_rate.rate %></td> | ||
<td class="text-right py-2"> | ||
<%= form_with url: available_vat_rates_path, method: :delete do |f| %> | ||
<%= f.hidden_field :vat_rate_code, value: available_vat_rate.code %> | ||
<%= f.submit 'Delete' %> | ||
<% end %> | ||
Comment on lines
+26
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used To avoid this issue, I decided to send the parameter through the form instead. |
||
</tr> | ||
<% end %> | ||
</tbody> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I excluded them from mutant because I think the amount of weird tests I would have to write does not justify the benefit we get.