Recently, I needed to write an API endpoint in Ruby on Rails. I don’t write code in Ruby very often, and this case, it had been — maybe a year?

I needed to return JSON with a list of objects. The first layer of objects mapped cleanly to a Rails model, but I needed to embed an additional list of objects representing some other model in each parent object. For this post, let’s say this was an application for a corporate gym conglomerate, and I needed to return a list of instructors and embed the exercises each participant did in a course that included a specific lift.

GitHub Copilot suggested something like the following:

def instructor
	account = Account.find(params[:account])
	lift = Lift.find(params[:lift])

	@instructors = account.users.where(is_instructor: true)

	@instructors.each do |instructor|
		instructor.teaching_courses.where(lift: lift).each do |course|
			instructor.participant_exercises << course.participants.map{| participant | participant.participant_exercises}.flatten
		end
	end
end

At a glance, the code seemed like it could work. I had not written a unit test. I ran it in my development environment, and on the first page load, it did work. But once I left the page and came back, suddenly it didn’t work.

After setting up dummy data and going through this process a couple times, I realized that this code was actually modifying my database. I think I laughed out loud. It felt as much like a bug in Rails as some kind of Copilot blooper. Even if the << operator mutated a data structure, I would not have guessed it would modify Rails relationships that saved all the way back to the database.

This seemed like a case of classically bad AI generated code. It looked reasonable, but had entirely unexpected and dangerous side effects.