Header image by Lightscape on Unsplash
Validations, Relational Databases, Happiness and Fulfillment
So you're having a hot and heavy time with your ActiveRecord backed app, Hot Hot Happy Hour, the newest Happy Hour gathering planning app: everything is coming up Martini's and Rosé: all your tables have clean data, nothing is null, and you haven't had any lost records, yet!
But, what if your product manager comes to you saying,
"We need a new feature that will let the hosts of Hot Hot Happy Hours back out after they made the event. We keep having hosts get cold feet and delete their accounts after they get a big crowd signed up for the event! Currently, events are deleted if the host deletes their account! We need to have the events stay online even if the host deletes their account! That's easy enough, right?"
Easy enough...right.
Well, actually - it might be! Especially if you're currently connecting the join table of the Happy Hour Event records with the users table and employing dependent: :destroy
to ensure that if a host backs out, or deletes their account that the event itself gets removed.
ActiveRecord associations - with great power...
ActiveRecord associations create so many powerful connections that we need to ensure we are handling it all correctly. Ensuring data tied to another piece of data, such as our Hot Hot Happy Hour events being tied to our event hosts and users, means that when one piece of data is manipulated or destroyed, the associated data should similarly be. The Rails docs say:
has_many
,has_one
, andbelongs_to
associations support the:dependent
option. This allows you to specify that associated records should be deleted when the owner is deleted.
Our ERD...
What is the :dependent
option?
Rails provides a nice option for handling the removal of associated data with the :dependent
option. According to the Rails documentation the :dependent
option:
Controls what happens to the associated objects when their owner is destroyed.
We are currently implementing this as such
ruby
class Hot_Hot_User < ApplicationRecord
has_many :hot_hot_events, dependent: :destroy
end
So, in our case with Hot Hot Happy Hour we potentially have users who are creating an event in the join table, then deleting their accounts after the event has gotten too big.
Because we are using dependent: :destroy
, when the user deletes their account it also deletes the associated data with it - including any events they have created, but haven't happened yet. Big bummer for the Hot Hot Happy Hour community.
Options to the rescue!
Something cool about Rails and ActiveRecord is that is offers a seriously robust set of tools to handle errors and validations. The :dependent
option feels like just such a tool!
In our case we don't want to destroy the events immediately, in fact we'd prefer they stay around and we can just replace the host. Heck, maybe we could automate an invitation out the most active users who are associated with that event to be the new host! Assuming we like the course of automation, let's use this idea as our guiding star and see what our options are.
The options :dependent
offers our associated data!
Borrowed directly from the Rails Documentation:
nil do nothing (default).
:destroy causes all the associated objects to also be destroyed.
:destroy_async destroys all the associated objects in a background job. WARNING: Do not use this option if the association is backed by foreign key constraints in your database. The foreign key constraint actions will occur inside the same transaction that deletes its owner.
:delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
:nullify causes the foreign keys to be set to NULL. Polymorphic type will also be nullified on polymorphic associations. Callbacks are not executed.
:restrict_with_exception causes an ActiveRecord::DeleteRestrictionError exception to be raised if there are any associated records.
:restrict_with_error causes an error to be added to the owner if there are any associated objects.
To reiterate: we want an option that will allow our app to quietly have an error, then handle it with some cool new feature...darn that Product Manager and their good ideas...
Looks like there are a few ways we could go about this.
ActiveRecord & Rails as safety net
I'm a big fan of options. I'm also a big fan of best practices...and when you're first getting started more options can make it harder to establish and follow best practices. But, hopefully we'll be able to find a happy middle ground here.
Remember, our goal is to remove a record that has data associated with it while leaving the associated data in place and capable of being further manipulated.
Let's look at the options and do a bit of psuedo-code!
nil do nothing - this is the default if no option is specified.
Use Case: Host user deletes account, but event still exists, so no one is there to raise the first glass - or ensure that the bartenders don't stink. This will negatively impact users. And probably have other consequences such as error handling and further validation.:destroy remove all associated data with the
:destroy
command, running callbacks as needed:nullify sets associated fields to null
Use Case: I like this as an option, gives us a clear reference - and if we have a helper function that runs on a regular basis to flag anynull
fields in the db - even better!:restrict_with_exception causes an ActiveRecord::DeleteRestrictionError exception
Use Case: this seems pretty helpful, we could use that error to automatically flag that we need a new host and instead of deleting the event, maybe it locks new users for RSVPing until a new host is associated with the event.:restrict_with_error causes an error to be added to the owner if there are any associated objects
Use Case: could also offer an avenue of escape, but may be more useful in the generation and handling of messages than in automating the replacement of our event host.
Keep it hot
The reason all your users love Hot Hot Happy Hour so much is that the events are all liminal experiences beyond what humans thought possible. Which is why creating an event, and then having it cancelled at the last minute is such a bummer. You never know when your last minute will be.
Can we devise a plan to save our events?
Our new model might look like this:
class Hot_Hot_User < ApplicationRecord
has_many :hot_hot_events, dependent: :nullify
end
My approach to start would be to go with :nullify
and then create a helper function to alert our Customer Service department that an event lost its host. This can then allow a new host to be chosen from the currently available list of participants. If the event host is null
it should still be accessible in the database, and all the rest of the data should be intact and healthy. Once we replace the single null
field with a new host, we should be good to go!
Lost data is nothing to shrug off, and good validations can make your life much easier! So - keep that data clean kids, and make sure to tie your shoe laces.
my code coda
1) Thanks for reading, if I got something wrong let me know!
2) There are always things to improve - what could we do better here?
Top comments (0)