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.