I have a “event” model that has many “invitations”. Invitations are setup through checkboxes on the event form. When an event is updated, I wanted to compare the invitations before the update, to the invitations after the update. I want to do this as part of the validation for the event.
My problem is that I can’t seem to access the old invitations in any model callback or validation. The transaction has already began at this point and since invitations are not an attribute of the event model, I can’t use _was to get the old values.
I thought about trying to use a “after_initialize” callback to store this myself. These callbacks don’t seem to respect the “:on” option though so I can’t do this only :on :update. I don’t want to run this every time a object is initialized.
Is there a better approach to this problem?
Here is the code in my update controller:
def update
params[:event][:invited_user_ids] ||= []
if @event.update_attributes(params[:event])
redirect_to @event
else
render action: "edit"
end
end
My primary goal is to make it so you can add users to an event, but you can’t not remove users. I want to validate that the posted invited_user_ids contains all the users that currently are invited.
–Update
As a temporary solution I made use for the :before_remove option on the :has_many association. I set it such that it throws an ActiveRecord::RollBack exception which prevents users from being uninvited. Not exactly what I want because I can’t display a validation error but it does prevent it.
Thank you,
Corsen
Could you use
ActiveModel::Dirty? Something like this:Edit: I didn’t notice that the OP already discounted
ActiveModel::Dirtysince it doesn’t work on associations. My bad.Another possibility is overriding the
invited_user_ids=method to append the existing user IDs to the given array:This should still work for you since
update_attributesultimately calls the individualattribute=methods.Edit: @corsen asked in a comment why I used
alias_method_chaininstead ofsuperin this example.Calling
superonly works when you’re overriding a method that’s defined further up the inheritance chain. Mixing in a module or inheriting from another class provides a means to do this. That module or class doesn’t directly “add” methods to the deriving class. Instead, it inserts itself in that class’s inheritance chain. Then you can redefine methods in the deriving class without destroying the original definition of the methods (because they’re still in the superclass/module).In this case,
invited_user_idsis not defined on any ancestor ofEvent. It’s defined through metaprogramming directly on theEventclass as a part of ActiveRecord. Callingsuperwithininvited_user_idswill result in aNoMethodErrorbecause it has no superclass definition, and redefining the method loses its original definition. Soalias_method_chainis really the simplest way to acheivesuper-like behavior in this situation.Sometimes
alias_method_chainis overkill and pollutes your namespace and makes it hard to follow a stack trace. But sometimes it’s the best way to change the behavior of a method without losing the original behavior. You just need to understand the difference in order to know which is appropriate.