Using Signed Global IDs with Polymorphic Select Fields in Rails Forms

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.