I recently saw a question on Reddit asking how to structure a reusable (polymorphic) model without creating a mess of nearly duplicate routes and controllers:
# config/routes.rb
resources :accounts do
resources :notes
end
resources :posts do
resources :notes
end
# ...repeat for every other model with notes
# app/controllers/accounts/notes_controller.rb
# duplicate this for every noteable type
class Accounts::NotesController < ApplicationController
def create
@account = Account.find(params[:account_id])
@note = @account.notes.build(note_params)
@note.save!
redirect_to @account
end
private
def note_params
params.require(:note).permit(:title, :body)
end
end
They also expressed concern about exposing the related model IDs in the form, since those can be manipulated on the frontend.
In these cases, I’m a big fan of using a Global ID to identify the noteable object:
puts Post.find(15).to_gid
# gid://MY_APP/Post/15
Even better than a plain Global ID is a signed Global ID. This is a Global ID that’s cryptographically signed on the server, so you can trust it hasn’t been tampered with.
This lets us simplify both our routing and our controller structure:
# config/routes.rb
resources :accounts
resources :notes
# app/models/note.rb
class Note < ApplicationRecord
belongs_to :noteable, polymorphic: true
def noteable_sgid
noteable.to_sgid
end
def noteable_sgid=(sgid)
self.noteable = GlobalID::Locator.locate_signed(sgid)
end
end
Now, instead of manually nesting routes and duplicating controllers, we can handle all notes through a single controller:
# app/controllers/notes_controller.rb
class NotesController < ApplicationController
def create
@note = Note.create!(note_params)
redirect_to @note.noteable
end
private
def note_params
params.require(:note).permit(:title, :body, :noteable_sgid)
end
end
We include the noteable_sgid
in a hidden field when rendering the form. This is automatically pulled from the note.noteable_sgid
:
<%# app/views/accounts/show.html.erb %>
<%= render "notes/form", note: @account.notes.build %>
<%# app/views/notes/_form.html.erb %>
<%# locals: (note:) %>
<%= form_with model: note do |form| %>
<%= form.hidden_field :noteable_sgid %>
<%= form.text_field :title %>
<%= form.text_area :body %>
<%= form.submit %>
<% end %>
This approach lets you use conventional Rails patterns, reduce duplication, and securely identify your associated records.