In an app I was working on recently, there was a requirement for a document (these were agreements of one type or another) to have many parties. In turn each of these parties needed to have an owner—and here’s where things began to get a little more complex—the party owners could be one of several model types. Ugh. Building a working select box for this functionality was going to be a mess. Or that’s what I thought initially. That’s when I happened across a few blogs about using Global IDs in Ruby on Rails. The Global IDs module became an official part of Ruby on Rails with the release of version 4.2. This module only includes a few methods and the one I ended up using most often is #to_global_id.
Here’s what #to_global_id looks like in action:
> Contacts.first.to_global_id Contact Load (1.0ms) SELECT "contacts".* FROM "contacts" ORDER BY "contacts"."id" ASC LIMIT 1 => #<GlobalID:0x007fe3494b0698 @uri=#<URI::GID gid://predictive/Contact/1>> > Contact.first.to_global_id.to_s Contact Load (0.4ms) SELECT "contacts".* FROM "contacts" ORDER BY "contacts"."id" ASC LIMIT 1 => "gid://predictive/Contact/1"
Okay, I admit. Nothing too exciting yet, but here’s the cool part: With the string returned from #to_global_id.to_s (the global id), you can find the Contact in question. The method we’ll use for finding the Contact is a bit different than what you might be used to—Contact.find(1)—but it’s not complicated either. It looks like this:
> GlobalID::Locator.locate "gid://predictive/Contact/1"
And bam! You’ve got your contact. And as you might have guessed by now, this pattern will work for any resource in your system. If I have a resource’s Global ID, then I can retrieve it from the database.
So that’s good and all, but it’s not something you’d use. Find functionality already exists after all! But there is a place where using Global ID functionality becomes legitimately useful and that’s when you need to build a polymorphic select. Let’s take a step-by-step look at the process involved in building a functioning polymorphic select using Global IDs.
So in my particular case, I was trying to set an owner for each party. The association between owner and party needed to be polymorphic so I could set owners as either contacts or entities. So my model looked like this:
class Party < ActiveRecord::Base belongs_to :document belongs_to :owner, polymorphic: true end
In the form, I built the owner select as follows:
= f.input :global_owner, collection: current_account.contacts + current_account.entities, :label_method => :name, :value_method => :to_global_id, label: false, prompt: 'Select Entity or Contact'
Obviously, there are some items of note here. First, if this looks weird to you, note that I’m using HAML and SimpleForm here. Second, bear in mind that this single field would be part of the Party form object. I’ve removed the field from this context so I can focus on the relevant details.
Let’s take a closer look at what’s going on in this single line of code:
You’ve probably already noticed that I called this field ‘global_owner’ which doesn’t actually exist on our model yet. We’ll add this (as a method that sets the Party owner association) in a moment.
You can see that the collection for this select box is a combination of the Contacts and Entities that exist on this account—so two different types of resources.
The label_method (part of SimpleForm functionality) will actually call the specified method on each object in the collection and display the results. So in this case, we just need to make sure that each resource involved has a #name method that returns something appropriate to display. So for my project, this meant that both the Entity and Contact models need to have #name methods defined.
The final item of note here is the #value_method which defines a method to be called on each item in our collection in order to set the value. In this code, this #value_method is set equal to the #to_global_id method. So we’ll be displaying the ‘name’ for each option in our select box (based on the #label_method), but the value for each option will be equal to the Contact or Entity’s Global ID.
Ouila! Almost there!
Don’t forget strong params here! We’ll need to add ‘global_owner’ as an accepted attribute on Party or our owner (global or not!) will not be allowed to pass. In this case our strong params method ended up looking something like the following:
def document_params params.require(:document).permit(:type_of, :is_amendment, parties_attributes: [:global_owner]) end
Okay, now we’ve come full circle. We’re back to our Model to put the finishing touches on our Global ID functionality. We need to define two methods here—a getter and a setter. The first method below is our getter. It simply retreives the global_id of our owner if one is present on our Party. The second method, and really the one that makes all of this go, is our setter method: It takes in the Global ID for a resource (a Contact or Entity, for instance) and sets owner equal to that. In other words, it sets the association. So when we send in a ‘global_owner’ value through the controller, this setter will get called and automatically set the relationship—whether it’s a Contact, Entity, or some other resource.
def global_owner self.owner.to_global_id if self.owner.present? end def global_owner=(owner) self.owner = GlobalID::Locator.locate owner end
And that’s the story on how using Global IDs can simplify polymorphic selects. It’s been a big help in a couple of spots for me!