Infinite recursion in Rails associations

Infinite recursion in Rails associations

June 20, 2017

I’m working on a Rails 5.0 project that is using an existing database. This kind of project is more difficult than creating a database from scratch using Rails migrations. One problem is that the existing database doesn’t use Rails conventions for the names of its columns. That led me to a bone-headed mistake in my model associations that caused Rails to get into an infinite recursion.

The database I was given was designed to represent courses in a free cabin-fever “university” for our small town (a common thing in Vermont). There was a people table and a courses table. Each course could have up to two professors and one greeter, so there there three columns in the courses table that were IDs referencing the people table.

(If I had designed the tables from scratch, I would have created separate professors and greeters tables that would have expressed the relationships between a course and its professors and greeter. Then each row in the professors and greeters tables would have had exactly one reference to people. That would simplify expressing the associations in the Rails models.)

In addition to the “three person IDs in one course record” problem, the names of the person ID columns did not have the “_id” suffix in accordance with the Rails conventions.

This led me to making the following bone-headed mistakes in my Course model:

belongs_to :professor_1, class_name: "Person", foreign_key: "professor_1"
belongs_to :professor_2, class_name: "Person", foreign_key: "professor_2", optional: true
belongs_to :greeter,     class_name: "Person", foreign_key: "greeter",     optional: true

This caused Rails to get into an infinite recursion and report a stack overflow when it tried to resolve the associations. It took me a long time to figure out the problem, and Google wasn’t a lot of help, apparently because most people aren’t as inexperienced with Rails as I.

The problem here was that the name of each association (the first argument to belongs_to) was the same as the name of its foreign_key. The solution was to rename those foreign keys to follow Rails conventions, so that professor_1 was renamed to professor_1_id, and so forth. Then the associations could be expressed more simply (and without infinite recursion!):

belongs_to :professor_1, class_name: "Person"
belongs_to :professor_2, class_name: "Person", optional: true
belongs_to :greeter,     class_name: "Person", optional: true