Referencing the same model twice in Rails?

I’m building an API for a survey app in Rails for my final project at generalassemb.ly at the moment, but having a bit of a problem in getting one of my migrations to run successfully.

The migration I’ve tried to set up includes has two references back to a user table. I need to use these as a foreign keys, so I can have different users occupy differing roles (owner & admin) in a survey.

I found this suggested approach at SO, but so far without success - Ruby on Rails - Reference the same model twice?

In the terminal, I’m running this command to set up the new Survey model, and this creates my migration file without issue, but as mentioned above, it errors when run.

rails g model Survey survey_title:string survey_code:string survey_owner:references survey_admin:references start_date:date finish_date:date

In the survey.rb model, I was adding the lines

belongs_to :survey_owner, :class_name => "User", foreign_key: "user_id"
belongs_to :survey_admin, :class_name => "User", foreign_key: "user_id"

and in the user.rb model, I had the line

has_many :surveys

When I run db:migrate though, I get this…

It’s telling that survey_owners doesn’t exist, which I know, so I’ve guessing the changes I’ve made to the models are not taking effect, but I’m not at sure why. Can anyone suggest what I might do to fix things?

Hey Chris,

It’s been a while since I did anything like this, but here’s what I’d do:

rails new survey_app
cd survey_app
rails g model Survey title:string owner:references admin:references
rails g model User name:string

Next open up the create_surveys.rb migration file and make one small edit:

Change this:

class CreateSurveys < ActiveRecord::Migration[5.0]
  def change
    create_table :surveys do |t|
      t.string :title
      t.references :owner, foreign_key: true
      t.references :admin, foreign_key: true

      t.timestamps
    end
  end
end

To this:

class CreateSurveys < ActiveRecord::Migration[5.0]
  def change
    create_table :surveys do |t|
      t.string :title
      t.references :owner
      t.references :admin

      t.timestamps
    end
  end
end

We need to knock out foreign_key: true on both :owner and :admin as otherwise Rails will be looking for tables of these names

Then create and migrate the database:

rake db:create
rake db:migrate

Here you’re creating the models and specifying that there are two columns in the Survey table that will be referred to as :owner and :admin and which hold references to another table.

Next edit the model files, thus:

class Survey < ApplicationRecord
  belongs_to :owner, :class_name => 'User'
  belongs_to :admin, :class_name => 'User'
end
class User < ApplicationRecord
  has_many :owned_surveys, :class_name => 'Survey', :foreign_key => 'owner_id'
  has_many :admin_surveys, :class_name => 'Survey', :foreign_key => 'admin_id'
end

Here you are creating a property on the Survey model named :owner, then specifying that this property is related to the User class. Rails, seeing the belongs_to :owner, will look for a column in the surveys table called “owner_id” and use that to store the foreign key. Then you’re doing the exact same thing for the admin.

Then you are creating a property on the User Model named :owned_surveys, specifying that this property is related to the Survey model, and that the foreign key on the Survey model which relates it to this property is called ‘owner_id’. Then you are doing the same thing for admin surveys.

I copied most of that explanation from here.

And that’s it. To test:

rails c
User.create(name: "Jim")
User.create(name: "Chris")
Survey.create(title: "How to baffle cats", owner_id: 1, admin_id: 2)

Survey.first().owner
=>  #<User id: 1, name: "Jim", created_at: "2017-02-20 13:23:40", updated_at: "2017-02-20 13:23:40">

Survey.first().admin
=> #<User id: 2, name: "Chris", created_at: "2017-02-20 13:23:46", updated_at: "2017-02-20 13:23:46">

User.first().owned_surveys
=> #<ActiveRecord::Associations::CollectionProxy [#<Survey id: 1, title: "How to baffle cats", owner_id: 1, admin_id: 2, created_at: "2017-02-20 13:23:49", updated_at: "2017-02-20 13:23:49">]>

User.find(2).admin_surveys
=> #<ActiveRecord::Associations::CollectionProxy [#<Survey id: 1, title: "How to baffle cats", owner_id: 1, admin_id: 2, created_at: "2017-02-20 13:23:49", updated_at: "2017-02-20 13:23:49">]>

Hope that helps some.

3 Likes

Thanks @James_Hibbard, that looks remarkably close to what I was trying to do. For the moment, our instructor has talked me down off the ledge, and I only have one reference across to the user table for the moment. If I need to add it later, I’ll reconstruct it a bit. I’ve now got all my other tables in place and the db seeded, so I’ll get to work on the Angular front end and see if I can get it talking to the API successfully.

2 Likes

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.