ActiveRecord Dependent Options
Tags: activerecord, ruby, database, associations
Published: 2022-03-22 13:49:39

Header image by <a href="https://unsplash.com/@lightscape?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Lightscape</a> on <a href="https://unsplash.com/s/photos/happy-hour?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
  
## 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](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Deleting+from+associations) say: 
> `has_many`, `has_one`, and `belongs_to` associations support the `:dependent` option. This allows you to specify that associated records should be deleted when the owner is deleted.

### Our ERD...

![An entity relationship diagram showing three database tables and their connections](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80xztjwpp478l9x4l66g.png)

## 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](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many-label-Options):

> 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:](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many-label-Deleting-from-associations)

> - **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 any `null` 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:

```ruby
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?