Tag Archives: dev blog

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

Developing a forest.

This is the first post in a series describing and documenting the development process of a personal project. The process, and the blog posts themselves, might be fairly unorganized, but I will try to build up a Github streak at the very least. No promises about regular blog updates. This is something I’m doing in my free time to have fun, keep my skills sharp, and learn new ones.

That’s a pretty liberating set of constraints. I don’t have to build anything useful; I’m focusing instead on silly and fun. That’s my jam. The idea itself is still in development, but I’ll describe the MVP I’m aiming for in this post. I’ll get more ideas as I work.

I want to be able to boot up my browser and explore a forest. I want to be able to walk through the forest and see what’s in each area. It’ll behave like a lot like text adventure, except for now it won’t have a point. Maybe a bit like Minecraft or Dwarf Fortress.

It’ll be a SPA with routing and all that cool stuff so that I can really get my React chops good.

The architecture is a Rails API back-end with a React + Reflux front-end. I’m currently working on how to create associations between a location and any of a list of objects.

Here are some stretch goals and general concepts: I want the user to be able to zoom in to any given object and get a nice amount of detail. I want that zooming to be what the user wants to do. A tree’s age, how many branches it has, and how it looks. The bugs that live on it. The color of that bug. The fruit. Maybe the forest will go through seasons. It’ll be fairly procedural and random generation, but I’d love to be able to reproduce some of the strange beauty of, for example, Minecraft’s landscapes or Dwarf Fortress’s histories and cultures.

At first, any number of trees or wolves will be able to exist in any given location, but I want to be able to add different kinds of objects to that list easily, without having to set up a has_many association in Location each time. This means using polymorphic relationships in the opposite direction than they are usually used. There might be a better way to implement this but I’m learning a lot by taking this path.

Here’s the Gist I read to flesh out my understanding of these relationships:  https://gist.github.com/runemadsen/1242485
It’s complicated enough that I know I’m probably doing something unnecessary, but that’s okay for now. I’ll fix it if it becomes a problem. I’ll learn in the meantime.

Here are some questions I need to answer eventually, and just free form notes:

  • How can I make it easy for myself as a developer to add new behavior and content?
    • Especially keeping in mind that I want objects to contain other objects to pretty much arbitrary depth. There’s no reason the same kind of bug couldn’t be on a tree or wolf. Or on the ground.
  • What will the front-end look like? I need to design and wireframe the UI.
  • Will the user be able to destructively interact with objects, or will it be read-only?
  • What will the API endpoints look like? Nested resources can quickly become a pain.
  • What’s the best scheme for building JSON? Each model having a #to_json method? Keeping that logic somewhere else?
  • What should I use to build JSON?
    • Probably JBuilder
  • What’s the most RESTful route for accessing a location object through its x and y coordinates rather than its ID? Should it be “locations/x/:x/y/:y”? No. What about “locations?x=4&y=5”? Maybe? But that base route is usually the #index method, when this is clearly a show page.
  • How do we make this about storytelling later on? How to keep track of each object’s existence?
    • Probably logging somehow.
  • Gosh I should write tests.
  • What will this app look like when it’s deployed?
    • Do I want to deploy it, or should I just put it on Github and make it a “run Rails locally and play with it on localhost” type thing? I mean that’s a little lame.
    • If it’s public then there will have to be users and multiple accounts, each with their own forests, and ughhh.
    • Unless the whole forest is “read only”, then it CAN be deployed publicly! Nice. Fun.
  • What’s the best way to have the world continue to “live” even when the user isn’t playing with it? Chron jobs? Separate processes?
    • What if every relevant object had a #live! method that triggered it to make decisions on growth, movement, etc. And that #live! method is called on each living object.
    • I like that idea.

Well that’s it for now! I should make sure to focus on getting some tiny MVP ready. Goodnight.

Leave a comment

Filed under code, Uncategorized