I have an fairly typical Order model, that has_many
Lines
class Order < ActiveRecord::Base
has_many :lines
validates_associated :lines
Once the order is completed, it should not be possible to change any attributes, or related lines (though you can change the status to not completed).
validate do
if completed_at.nil? == false && completed_at_was.nil? == false
errors.add(:base, "You can't change once complete")
end
end
This works fine, but, if you add to, remove, or change the associated Lines, then this isn't prevented.
In my Line model, I have the following validation:
validate do
if order && order.completed_at.nil? == false
errors.add(:base, "Cannot change once order completed.")
end
end
This successfully stops lines in a completed order being modified, and prevents a line being added to a completed order.
So I need to also prevent lines being taken out of a completed order. I tried this in the Line model:
validate do
if order_id_was.nil? == false
if Order.find(order_id_was).completed_at.nil? == false
errors.add(:base, "Cannot change once order completed.")
end
end
end
This works fine to prevent a Line being taken out of an Order when modifying the Line directly. However when you are editing the Order and remove a Line, the validation never runs, as it has already been removed from the Order.
So... in short, how can I validate that the Lines associated with an Order do not change, and are not added to or removed?
I'm thinking I'm missing something obvious.
From the "Association Callbacks" section of ActiveRecord::Associations
, you'll see that there are several callbacks that you can add to your has_many
definition:
before_add
after_add
before_remove
after_remove
Also from the same docs:
Should any of the
before_add
callbacks throw an exception, the object does not get added to the collection. Same with thebefore_remove
callbacks; if an exception is thrown the object doesn't get removed.
Perhaps you can add a callback method to before_add
and before_remove
that makes sure the order isn't frozen and throws an exception if it's not allowed.
has_many :lines,
before_add: :validate_editable!,
before_remove: :validate_editable!
private
def validate_editable_lines!(line)
# Define the logic of how `editable?` works based on your requirements
raise ActiveRecord::RecordNotSaved unless editable?(line)
end
Another thing worth trying would be to add a validation error and return false
within validate_editable_lines!
if your validation test fails. If that works, I'd recommend changing the method name to validate_editable_lines
(sans !
bang), of course. :)
Maybe add a locked
attribute to the model, and, after the order is completed set the value of locked
to true
.
Then, in the controller, add a before_filter
that will be triggered before the update action so it would check the value of the locked
flag. If it is set to true
then raise an error/notification/whatever to the user that that line item cannot be changed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With