Renaming foreign key column causes forms to fail

Using scaffold, I generated two models, Person and Offer, and the forms to manage them. Offer has a belong_to association with Person. After running the generator, all the forms immediately worked fine.
But then I discovered an external requirement that the foreign key column in the offers table, which was named person_id per Rails conventions, needed to be renamed as worker_id. Since then, the Offer forms don’t work. They evoke a variety of errors, all related to the renamed column. It seems that the system no longer recognizes it as implementing the Offer to Person association.

I figure I need to make some declaration somewhere to restore this associative functionality, but I can’t figure out how to do this. Can you help?

Here are some details:

The generate commands I used were:
rails generate scaffold Person name:string
rails generate scaffold Offer terms:string person:belongs_to rake db:migrate

``


<% @offers.each do |offer| %>


<%= offer.terms %>
<%= offer.person.name %>
<%= link_to ‘Show’, offer %>
<%= link_to ‘Edit’, edit_offer_path(offer) %>
<%= link_to ‘Destroy’, offer, method: :delete, data: { confirm: ‘Are you sure?’ } %>

<% end %>

``

I tweaked the Offer forms a bit to show People’s names in display pages and provide a drop-down on edit forms. The tweaked Offer index.html.erb looked like this:
``and the tweaked Offer _form.html.erb looked like this:
<%= form_with(model: offer, local: true) do |form| %>

<%= form.label :terms %> <%= form.text_field :terms, id: :offer_terms %>
<%= form.label :person_id %> <%= form.collection_select :person_id, Person.order(:name), :id, :name %>
...

``

At this point, everything worked fine.

Then I wrote and executed this migration:
class RenameOfferPersonId < ActiveRecord::Migration[5.1]
def change
rename_column :offers :person_id :worker_id
end
end

``

Since then, all of my Offer forms get errors, always related to the renamed foreign key column. Here’s the error for index.html.erb:
Showing /home/ec2-user/environment/ww1/app/views/offers/index.html.erb where line #18 raised:

undefined method `name' for nil:NilClass
...
        <td><%= offer.person.name %></td>

``

If I change the offending line to:

<%= offer.worker_id %>

``

the page displays, but only the value of worker_id is displayed, not the name of the referenced Person.
The Offer edit form displays, including its dropdown box containing Person names. But when I try to save an update, I get:
ActiveModel::UnknownAttributeError in OffersController#update
unknown attribute ‘person_id’ for Offer.
Extracted source (around line #44):

44 if @offer.update(offer_params)

``

If I change these lines in _form.html.erb
`    <%= form.label :person_id %>
    <%= form.collection_select :person_id, Person.order(:name), :id, :name %>
`

``

to
`    <%= form.label :worker_id %>
    <%= form.collection_select :worker_id, Person.order(:name), :id, :name %>
`

``

the form still displays OK, but when I try to save an update, I get a different error:
1 error prohibited this offer from being saved:
- Person must exist

``

~ Thanks in advance for your help
~ Ken

​belongs_to (and other assocns) has options for class name and foreign
key.​ see ri or doc.
otoh, i wonder if it would have been easier to create just another Worker
resource (and probly delete Person); but ymmv prolly.

best regards --botp

Using scaffold, I generated two models, Person and Offer, and the forms to manage them. Offer has a belong_to association with Person. After running the generator, all the forms immediately worked fine.

But then I discovered an external requirement that the foreign key column in the offers table, which was named person_id per Rails conventions, needed to be renamed as worker_id. Since then, the Offer forms don't work. They evoke a variety of errors, all related to the renamed column. It seems that the system no longer recognizes it as implementing the Offer to Person association.

You need to tell the has_many/belongs_to associations to use the new key, ie

(In Offer)

belongs_to :person, foreign_key: “worker_id”

And similarly for the has_many in the Person class. The form helpers need to be using the worker_id , since person_id doesn’t exist anymore.

Fred

Thanks botp and Frederick ~

​belongs_to (and other assocns) has options for class name and foreign key.​ see ri or doc.

otoh, i wonder if it would have been easier to create just another Worker resource (and probly delete Person); but ymmv prolly.

best regards --botp

I added class_name: “Worker”, foreign_key: “worker_id” to both Person and Offer, so the class definitions look like:

class Person < ApplicationRecord
has_many :offers, class_name: “Worker”, foreign_key: “worker_id”
end

class Offer < ApplicationRecord
belongs_to :person, class_name: “Worker”, foreign_key: “worker_id”
end

``

but the forms still misbehave as described earlier.

Uh, well -- this will probably help:

http://guides.rubyonrails.org/association_basics.html#bi-directional-associations

Thanks, Hassan ~

The link you provided produced some progress, and also some more perplexity.

From that article i learned that I had the association definitions in my model definitions backwards. What I had as this:
class Offer < ApplicationRecord
belongs_to :person, class_name: “Worker”, foreign_key: “worker_id”
end

``

should have been this:
class Offer < ApplicationRecord
belongs_to :worker, class_name: “Person”, foreign_key: “worker_id”

``

In retrospect, that makes more sense: The name of the association should not have to be the name of the associated class; if it had to be, we could not declare two semantically distinct associations between the same two classes. The class_name parameter lets us declare the name of the associated class, so Rails doesn’t have to infer it from the association name. And the foreign_key parameter lets us be explicit about the column that refers to the associated class.
The good news is that, with that changed, I can get the display of an Offer (in index.html.erb and show.html.erb) to show the name of the Person, rather than just the object reference. To do this, I set the relevant line in those files to read:
<%= @offer.worker.name %>

``

The perplexing news is that it remains impossible to create or update an Offer. When I try to change the Person who is associated with an existing Offer, the odd result is that no error is returned: the “show” page is displayed with the happy message “Offer was successfully updated.” But the associated Person is still the pre-existing one; the change was not saved.
And when I try to create a new Offer, I get an error that has appeared before:
1 error prohibited this offer from being saved:
Worker must exist

``

Both of these results say to me that the id of the Person that I’m trying to associate with this (existing or new) Offer is somehow not getting passed on to where it needs to go.

Further suggestions?

~ Tx, Ken

Is it possible to make a small standalone repo to demo this? That
would make it a lot easier to help.

If not, I suggest writing a test to create an Offer and working with
that until you get a working Offer.create statement.

Hassan, that’s an excellent idea!

Where would you suggest I post this repo to make it most easily accessible to you and others? I could post the code onto GitHub, or… ? Or is there some other place where Rails developers like to share their work? (I ask that as a Rails newbie.)

~ Ken

OK, I have posted the code to a repo at https://github.com/kenatsun/ww2. A bit of explanation on this: In this version, there are two “Offer” classes, one named Offer and the other named BadOffer:

  • Offer was generated using all the steps in my original post, up to the point where I renamed the foreign key column. I include this to let you confirm that at that point everything works OK.
  • BadOffer was generated in the same way, but I then went on to rename the foreign key, and then did all the attempted fixes described above.
    ~ Ken