Polymorphic associations allow a single association to reference multiple models. In a polymorphic association, the association name is used to store the name of the associated model, and the _id
and _type
attributes are used to store the ID and type of the associated record. However, polymorphic associations can be challenging to work with when it comes to using select fields, especially when there are multiple types of associated models.
Rails provides a solution to this problem in the form of signed global IDs (SGIDs). SGIDs allow you to securely reference objects across different Rails applications. In this article, we’ll look at how to use SGIDs with polymorphic associations to create select fields in your Rails application.
Enabling SGIDs
To enable SGIDs in your Rails application, add the following line to your config/application.rb
file:
1
config.global_id_signed = true
This will enable SGIDs for your application and ensure that they are used whenever an object is serialized or deserialized.
Defining a Polymorphic Association
For the purposes of this article, let’s assume that we have a Comment
model that has a polymorphic association called commentable
, which can be associated with either a Post
or a Photo
model. We can define this association as follows:
1
2
3
4
5
6
7
8
9
10
11
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Photo < ApplicationRecord
has_many :comments, as: :commentable
end
Usage with a Select Field
To create a select field that allows the user to choose between different types of associated models, we can use the select
form helper provided by Rails. Here’s an example:
1
2
3
4
5
6
7
<%= form_for @comment do |f| %>
<%= f.label :commentable %>
<%= f.select :commentable, options_from_collection_for_select(Post.all + Photo.all, :to_global_id, :title), include_blank: true %>
<%= f.label :body %>
<%= f.text_area :body %>
<%= f.submit %>
<% end %>
In this example, we’re using options_from_collection_for_select
to generate the select options for the Post
and Photo
models. We’re concatenating the Post
and Photo
collections using the +
operator and passing the resulting array to options_from_collection_for_select
.
We’re using :to_global_id
as the value_method
option to specify that the signed global ID of each associated object should be used as the value for the select options.
We’re using :title
as the text_method
option to specify that the title attribute of each model should be used as the label for the select options.
Handling the Submitted Form Data
Finally, we need to update the controller to handle the submitted form data. Here’s an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CommentsController < ApplicationController
def create
@comment = Comment.new(comment_params)
@comment.commentable = GlobalID::Locator.locate(comment_params.dig(:comment, :commentable))
if @comment.save
redirect_to @comment.commentable, notice: 'Comment was successfully created.'
else
render :new
end
end
private
def comment_params
params.require(:comment).permit(:commentable, :body)
end
end
Here, the controller creates a new Comment object and sets its attributes to the submitted form data. We use the GlobalID::Locator.locate
method to locate the associated object based on the submitted signed global ID. This method securely deserializes the object from the signed global ID, allowing us to use the object as if it were a regular object in our Rails application.
If the comment is saved successfully, we redirect to the associated object’s show page with a success notice. Otherwise, we render the new view to allow the user to try again.
Conclusion
Using signed global IDs with polymorphic associations in Rails is a powerful technique that allows you to create select fields that reference multiple models.