Polymorphic Associations The Smart Way: Using Global Ids

Global IDs have previously been a feature of rails with the help of a gem but as of Rails 4.2, it’s officially baked into the platform.  I just recently used the new addition to create polymorphic associations within an app and I have to say, it’s fantastic.  I did, however, find very little help out there for first-time users of this feature so here’s a few notes on how to do it.

What is a global id

Global Id is a library that produce Uniform Resource Identifiers (URIs) for any piece of data you’d like. They tend to look something like this:
gid://name-of-app/Person/1

Global IDs have previously been a feature of rails with the help of a gem but as of Rails 4.2, it’s officially baked into the platform.  I just recently used the new addition to create polymorphic associations within an app and I have to say, it’s fantastic.  I did, however, find very little help out there for first-time users of this feature so here’s a few notes on how to do it.

What is a global id

Global Id is a library that produce Uniform Resource Identifiers (URIs) for any activerecord object. They tend to look something like this:
gid://name-of-app/Person/1

So global IDs allow all of this information to be expressed in a single string very quickly.  With a couple methods in the model we can use this to create a polymorphic association.

Cool, so how do I make this happen?

Let’s pretend that we want to set up an association to indicate whether a Stockholder is a Person or a Company.

First, we need to generate our stockholder model

We will need two columns: entity_id and entity_type. Just like in the standard polymorphic association, the latter stores the model name, the former stores the id within that model.  We can generate both with a single line in our migration as follows:

class CreateStocholders < ActiveRecord::Migration
def change
create_table :optionees do |t|
t.date :grant_date
t.integer :shares_outstanding
t.belongs_to :option, index: true, foreign_key: true
t.references :entity, polymorphic: true, index: true
t.timestamps null: false
end
end
end

Next, we set up our models to indicate a polymorphic relationship

Company Model:
class Company < ActiveRecord::Base
has_many :stockholders, as: :entity
end

Person Model:
class Person < ActiveRecord::Base
has_many :stockholders, as: :entity
end

Stockholder Model:
class Stocholder < ActiveRecord::Base
belongs_to :entity, polymorphic:true
end

In the above case, :entity is just catch-all field that represents people and companies. Of course, you can call yours whatever you’d like.

Now we can set up our form for the stockholder

I use simple_form but the principle is the same for other methods; adjust your syntax accordingly. Something like this.

...
<%= f.grouped_collection_select :entity_id, [Company, Person], :all, :model_name, :to_global_id, :email %>
<%= f.input :issue_date, label: false %>
<%= f.input :shares_issued, label: false %>
...

So obviously, the exciting part here is the grouped_collection_select which will render a nice select menu with both companies AND people in it.

To break it down:

  • This is a grouped collection for a client
  • Over the models Company and Person
  • It will use all of the records in each model
  • and group them according to their model_name.
  • It will use the global_id to get the values
  • and display the email of each in the menu

The above will give you a grouped selector similar to the one shown below (note I used Org instead of Company, but you get the idea).
groupedDropdown

But you might rightly ask: “Why did you use email addresses and not names?”  The tricky thing about these sorts of associations is that people have both a first name and a last name where companies just have a name.  In order to get names to render in such a situation, you will need to use a lambda, which I’ll go over at the end of the post.

Making Global Ids work with Polymorphic Associations

If you’ve been keeping track though you should see that we have created two key problems for integrating the global id with polymorphic associations:

  1. We need :entity_type as well as :entity_id
  2. :entity_id needs to be an integer, not a string as is a global id.

So, to rephrase the situation, we need to break the global id into two pieces:

  1. Its model name (a string)
  2. Its id for that model (an integer)

Fortunately we don’t need to do this manually, rails polymorphic associations will store the model name and id automatically if it knows which object to reference.  Therefore, we specify getter/setter methods to specify the object, using global ids.

So, editing the Stockholder model again:

class Stocholder < ActiveRecord::Base
belongs_to :entity, polymorphic:true

def global_entity
self.entity.to_global_id if self.entity.present?
end

def global_entity=(entity)
self.entity=GlobalID::Locator.locate entity
end
end

Then change the grouped_collection_select to:
<%= f.grouped_collection_select :global_entity, [Company, Person], :all, :model_name, :to_global_id, :email %>
<%= f.input :issue_date, label: false %>
<%= f.input :shares_issued, label: false %>

Then just be sure to add :global_entity to your strong params and that’s it!

 

Using the lambda for names

Ok, often when doing polymorphic associations, one will use different pieces of information as keys for the two different models (after all, they’re not always going to have the same columns, right?)  In such a case, we use a lambda, making our select like this:
<%= f.grouped_collection_select :entity_id, [Company, Person], :all, :model_name, :to_global_id, lambda {|company_or_person_object| company_or_person_object.instance_of? Company? rescue company_or_person_object.fname + " " + company_or_person_object.lname rescue company_or_person_object.name}, label:"Stockholder", class: "names"%>

As an explanation of lambdas is a bit beyond the scope of this tutorial, I’ll just leave you with the example above.  There is plenty of documentation available out there on the matter so if the lambda’s confusing, just google it up.

Anyway, that’s it; you’re done!

 

Advertisements
Polymorphic Associations The Smart Way: Using Global Ids