Query polymorphic tables easier using a view
Contents
Tables with polymorphic associations can be quite annoying to query with sql.
Polymorphic?
Some popular ORMs, like Rails’s Active Record, allow the use of polymorphic associations. Here’s an example of what this looks like:
You can see that the columns imageable_id
and imageable_type
can refer to either the employees
or products
table.
If you only work in ORM land, it’s quite convenient. If you want to grab a product’s pictures, you can retrieve it with @product.pictures
, similarly with an employee’s pictures with @employee.pictures
.
Problems
The problems start coming in if you need to query it with SQL. The below code uses PostgreSQL but the concept should be applicable to other SQL databases. If you only want an employee’s pictures, you need to use a where clause like this: where imageable_type = 'Employee' and imageable_id = :id
.
That doesn’t seem so bad right? What if there are two polymorphic associations in a single table? This is quite common in some Rails codebases. You would have to do something like:
You can start to imagine that it can get troublesome very quickly.
Possible solutions
What are some possible ways to make querying this data easier? create normalized tables for each polymoprhic association create views for each polymoprhic association Let’s take a brief look at each one.
a. Normalized tables
If we created normalized tables, then we would get foreign keys back. But, do we keep the polymoprhic table or migrate all data over to the new ones? If we migrate all the data and remove the polymoprhic table, it means we have to update all the code that references that table. This is the most complete solution if you can afford to do this. If you don’t want to update all the old code, you could set triggers so that inserts/updates/deletes on the polymorphic table are done on the new normalized tables instead. Although it results in a messier data scheme, this way does work.
b. Views
A scrapier alternative that doesn’t involve migrating data is to create views for each polymoprhic association. A view is not physically materialized, so it’s not a table. But it’s more than just a ‘shortcut’ to a longer query because it can also be updatable.
Using a view
Let’s look at an example. If you want to follow along, you can use this postgres-wasm tool in your browser!
Here we have our pictures
table that we’ve been looking at, along with some sample data for an employee and a product.
id | imageable_type | imageable_id | name |
---|---|---|---|
1 | Employee | 1 | someone |
2 | Product | 1 | thingy |
a. Creating a view
Next up we’re going to create the view.
Now when you query this employee_pictures_view
, you will only get the employee pictures, and not any other group. So much easier already!
picture_id | employee_id | name |
---|---|---|
1 | 1 | someone |
But wait, what if I want to insert/update/delete something for an employee’s pictures? You can also do that with a view. If we didn’t rename a column, we could just do a straight update into the view. But since we did, we will have to create a new function that converts our changed column name into the original column name. Then use that function as a trigger for inserts/updates/deletes for the view.
b. Updating a view
This example below only goes over the insert
, but the process is similar for updates/deletes.
Now you can easily do an insert
into this view.
picture_id | employee_id | name |
---|---|---|
1 | 1 | someone |
2 | 2 | person |
Now, take a look at the original pictures
table and see that it has the new data that you inserted into employee_pictures_view
.
id | imageable_type | imageable_id | name |
---|---|---|---|
1 | Employee | 1 | someone |
2 | Product | 1 | thingy |
2 | Employee | 2 | person |
c. Benefits and Tradeoffs
The main benefit of this method is that there’s still a single source of truth for all the pictures since there’s only one pictures
table. You don’t have to touch any of the old code at all. And your new code can use the new views and almost treat them as tables. Of course, you still don’t get to use foreign keys here, but that’s one of the tradeoffs.
views
are a very convenient method for these sorts of issues.
Have some thoughts on this post? Reply with an email.