Just a quick note — when creating foreign keys in a Rails 5.1+ you should make sure to they are typed as bigint.
But why?
Starting in Rails 5.1 primary keys were defaulted to bigint. Check this commit from 2016. This means that referential foreign keys also need to be bigint. If you’ve gotten into the habit of rails g model Whatever user_id:integer,you need to break it.
Use user:references or user:belongs_to (which is an alias of references) instead. These will automatically use bigint and all will be well.
But why???
If you forget to do this or use some kind of gem that generates an integer foreign key your app won’t break yet…
bigint is yuge and as a result you could run into a situation where the primary key (a bigint) you’re trying to persist is bigger than the FK column (an integer) can hold.
Active Storage is a pretty nice addition to Rails. Essentially you’re given an out-of-the-box polymorphic solution to managing ANY type of attachment. Rails will handle the processing and storing of attachments along with extra stuff such as metadata extraction. Super cool.
The Problem
Management of Active Storage attachments in a way that feels Railsy is a different story though. Let’s say you have a Profile resource that uses a Picture attachment. When viewing profile#show wouldn’t it be nice to just delete the picture from there? Where would the actual act of destroying an attachment live?
Attachment management seems strange because it FEELS like it’s just another aspect of your resource but it’s not.
I tried a couple options that didn’t seem to be right…
Add logic to the resource controller’s destroy action to delete an instance of the resource OR optionally remove just the resource’s attachment(s)
A universal attachment controller. CRUD requests are sent to AttachmentController and operate using a combination of attachment ID and type
I think both of those options felt wrong because we either end up with controller actions concerned about things they shouldn’t really know about OR we end up with a “universal” controller that will likely become littered with callbacks for many different resources.
A Convention I’ve Adopted
For now when I have a resource using Active Storage attachments I create namespaced controllers (and routes) for each attachment type under the resource they support.
So in the case of the Profile and Picture example above I’d have something like this:
app/controllers/profiles_controller.rb
app/controllers/profile/pictures_controller.rb
This is nice because PicturesController can have callbacks or prerequisites (think authentication) that are specific to the app OR add things that are specific to pictures without polluting intent.
Code + Walk-through
Checkout the walletapp repo for a demo I made using this technique. For now please check the commit history and shortly I’m going to add a tutorial on how to setup an app from scratch that has attachments and attachment management using the nested controller approach.