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.