Monthly Archives: June 2016

Reverse polymorphism, aka polymorphic join.

I’ve built and implemented a bunch of stuff I’m excited about with the forest, but this post will focus on one aspect: reverse polymorphism. I don’t have it working perfectly yet (EVEN THOUGH IT SHOULD BE WORKING WHAT THE HECK) but I think it is the solution to the craziness I had been encountering with the ActiveRecord associations I need. The best part about it is that it’s not Single Table Inheritance (STI), another potential “solution”.

I’ll describe the problem that needed solving, and how this solves it, and why it’s awesome. Then I’ll describe what’s not quite working yet, the workarounds I’m using, and plea for help from those more knowledgeable.

First, though, here is the gist which introduced me to RP in Rails. I’ll explain how it works in a bit.

The forest’s fundamental blocks are Location objects. They’re normal AR objects. Locations have_many objects, and objects can be multiple kinds of things. The toy examples in the app right now are Wolves and Trees. Wolves can move around, and Trees can’t, but both have_one location.

Ideally, I’d like to be able to call Location#objects and receive a list (an ActiveRecord::Relation) that looks like [wolf1, wolf2, tree1]. I’d like this to work at the ORM level (i.e. I want ActiveRecord to do it for me) and not have to write that scope or method myself.

Polymorphism to the rescue

This sounds like a job for polymorphism, but it’s not a classic case. The normal case is an object being able to belong_to one of multiple kinds of things. I need an object to be able to have_many of many types of things.

It took me a day to grok this, but the solution is a join table. In my case, a LocationObject.

  • Location has_many LocationObjects
  • LocationObjects (the join table) have a polymorphic have_one association with Wolves, Trees, etc.
  • Wolves and Trees have_one LocationObject as object
  • Location has_many Wolves and Trees through LocationObjects.

So the associations are all set up. Location#location_object AND Wolf#location_object work.

Someone might say “well Vincent, this is totally more complicated than Single Table Inheritance!” and I would say “ugh come on”. Classical inheritance sounds like a great solution until you see what it does to your database. Even though Wolves and Dogs intuitively could both inherit from a hypothetical Canid class, which inherits from a Mammal class, and so on, this means that everything that inherits from the base class shares the same table. Every time a single child needs a new attribute, it gets added as a column to the ancestor table.

Because the forest will hopefully have a widely-branching tree of life, this is not the proper solution for this project. That single table would probably get huge, and track information for everything from number of leaves to most recent meal. Or whatever. It would be painful.

To keep shared behavior manageable, I’m going to be relying on composing with modules. To continue the previous example, I’d have a Mammal module, and a Canid module, both of which would get mixed in to the lowest-level Wolf and Dog classes. I’m keeping these modules in lib/.

Hell yeah. This seems like the perfect solution.

The Problem

The problem I’m having is the join. Location#objects and Wolf#location do not work. I’ve created the following fix, which I really don’t want to be permanent:

# app/models/location.rb
... 
# FIXME
def objects
   self.location_objects.includes(:object).map do |location_object|
     location_object.object
   end
 end
...

While it DOES avoid a potential N+1 issue, I’m pretty sure ActiveRecord is capable of doing this for me. And as long as this is a pet project, I want the code to be beautiful. I’ve even got failing tests demonstrating this behavior.

Another symptom of this is being unable to create the objects as I’d like. Wolf.create(name: "joe", location: some_location) just doesn’t work!

The plea

WHYYYYY?

It may be the ActiveRecord just doesn’t handle those helper methods over polymorphic join tables. But the writing I’ve seen online doesn’t suggest that.

I have a terrible fear that this is a naming or pluralization issue. ActiveRecord’s errors hint at that: Could not find the association :location_objects in model Wolf.

Anyways, here’s the relevant code. I invite feedback!

location.rb
location_object.rb
wolf.rb

Advertisements

1 Comment

Filed under code, Uncategorized

It’s just an API for now.

Alright, episode two in the development blog of my little forest project.

Here’s the forest code.

If you look at my Github contribution tracker, you’ll notice a month gap there. There’s a couple reasons for that, but the relevant one is that I was trying to work on the front end. I was learning how to get React + Redux running with Webpack and Babel and all that fun stuff. I was learning about modern JavaScript development and deployment.

I spent a couple weeks following Survive.js’s awesome Webpack and React tutorial. I don’t think I will continue down that road for right now. I’m just doing Ruby for now. I am capable of learning the modern front-end, but it will probably have to be with more time or mentorship. The complex mixture of compilers and languages and package managers is too much for me to handle at the moment.

So that’s that. I’m building an API for now. With Ruby on Rails. Once it’s fleshed out I’ll start looking at a front-end. I’d love for it to imitate the style of text adventures like Zork, except the world is dynamic and alive. Every movement (e.g. north, south) would constitute an API call. Clearly, web technologies are not the best way to build this. I think Z-machine is the way to go for that.

So that’s a status update. I’ll write a follow-up post real soon re: technical issues on the back-end.

Leave a comment

Filed under code, Uncategorized