Renaming foreign key column causes forms to fail

29 views
Skip to first unread message

kenatsun

unread,
Jan 19, 2018, 10:54:27 PM1/19/18
to Ruby on Rails: Talk
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

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:
...
   
<% @offers.each do |offer| %>
     
<tr>
       
<td><%= offer.terms %></td>
       
<td><%= offer.person.name %></td>
       
<td><%= link_to 'Show', offer %></td>
       
<td><%= link_to 'Edit', edit_offer_path(offer) %></td>
       
<td><%= link_to 'Destroy', offer, method: :delete, data: { confirm: 'Are you sure?' } %></td>
     
</tr>
   
<% end %>
...
and the tweaked Offer _form.html.erb looked like this:
<%= form_with(model: offer, local: true) do |form| %>
...
  <div class="field">
    <%= form.label :terms %>
    <%= form.text_field :terms, id: :offer_terms %>
  </div>

  <div class="field">
    <%= form.label :person_id %>
    <%= form.collection_select :person_id, Person.order(:name), :id, :name %>
  </div>
...

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:
        <td><%= offer.worker_id %></td>
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



botp

unread,
Jan 20, 2018, 1:29:32 AM1/20/18
to rubyonra...@googlegroups.com
On Sat, Jan 20, 2018 at 11:54 AM, kenatsun <kena...@gmail.com> wrote:
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?


​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
 

Frederick Cheung

unread,
Jan 20, 2018, 11:57:30 AM1/20/18
to Ruby on Rails: Talk
On Saturday, January 20, 2018 at 3:54:27 AM UTC, kenatsun wrote:
> 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







> 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
>
> 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:
>
>
>
> ...
>    <% @offers.each do |offer| %>
>       <tr>
>         <td><%= offer.terms %></td>
>         <td><%= offer.person.name %></td>
>         <td><%= link_to 'Show', offer %></td>
>         <td><%= link_to 'Edit', edit_offer_path(offer) %></td>
>         <td><%= link_to 'Destroy', offer, method: :delete, data: { confirm: 'Are you sure?' } %></td>
>       </tr>
>     <% end %>
> ...and the tweaked Offer _form.html.erb looked like this:

kenatsun

unread,
Jan 20, 2018, 1:16:41 PM1/20/18
to Ruby on Rails: Talk
Thanks botp and Frederick ~


On Saturday, January 20, 2018 at 1:29:32 AM UTC-5, bot Peña wrote:

​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.

 

Hassan Schroeder

unread,
Jan 20, 2018, 1:48:03 PM1/20/18
to rubyonrails-talk
On Sat, Jan 20, 2018 at 10:16 AM, kenatsun <kena...@gmail.com> wrote:

> 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

--
Hassan Schroeder ------------------------ hassan.s...@gmail.com
twitter: @hassan
Consulting Availability : Silicon Valley or remote

kenatsun

unread,
Jan 20, 2018, 9:51:29 PM1/20/18
to Ruby on Rails: Talk
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

Hassan Schroeder

unread,
Jan 21, 2018, 2:52:10 PM1/21/18
to rubyonrails-talk
On Sat, Jan 20, 2018 at 6:51 PM, kenatsun <kena...@gmail.com> wrote:

> 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

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.

kenatsun

unread,
Jan 21, 2018, 3:26:28 PM1/21/18
to Ruby on Rails: Talk
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

kenatsun

unread,
Jan 21, 2018, 4:37:42 PM1/21/18
to Ruby on Rails: Talk
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
Reply all
Reply to author
Forward
0 new messages