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
_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.
To enable SGIDs in your Rails application, add the following line to your
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
Photo models. We’re concatenating the
Photo collections using the
+ operator and passing the resulting array to
: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.
: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.
Using signed global IDs with polymorphic associations in Rails is a powerful technique that allows you to create select fields that reference multiple models.