Ruby on Rails: Setup multiple associations with the same model04 Apr 2018
When I was just starting to learn Ruby on Rails, I had trouble setting up multiple associations with the same model due to my lack of understanding and reliance on Rail’s “magical” generators. Here I’ll show you how to implement it and analyze how it happens. On this example there are two models, User and TransferRequest. TransferRequest has attributes sender and receiver which are instances of User.
User was generated by executing the command:
The User model will look like this:
Lets generate the TransferRequest model by executing the command:
The code for TransferRequest should look like this:
A migration file to create the table for TransferRequest will also be generated.
Lets run a migration to apply the changes to our database.
When I was new to Ruby on Rails, I thought this would be enough to setup the model TransferAsset and its association with sender and receiver.
Lets write a unit test that checks the belongs_to association but before that lets first install the shoulda-matchers gem. Add these to your Gemfile:
Lets now write our unit test for the TransferRequest model. It should look like the code below:
Now the run the test to check if the association was set up properly. Run the command below.
The test will fail.
Lets analyze how this happened by first looking at the CreateTransferRequest migration file and the changes it contributed to the db/schema.rb file after running the migration. Running the migration modifies the db/schema.rb. This piece of code will be added to it. Take note of the last two lines on the code below.
This instructs Rails Active Record to create the table tansfer_request with columns sender_id and receiver_id which is represented by TransferRequest as a model. The last two lines call the method add_foreign_key which takes a table where the foreign keys reside as the first argument and another table that will be references by the foreign key as the second argument. Because of this Rails assumes that a table called sender and receiver actually exists which isn’t true.
To fix this, we need to remove the last two lines on our code snippet to prevent Rails from referencing the tables that don’t exist however we can’t just remove the last two lines on the db/schema.rb because it will be inconsistent with our migration file. We need to modify the CreateTransferRequest migration but before that we must undo some changes to the database by rolling it back to the state where the transfer_request table doesn’t exist. Execute the command below to revert the last migration and undo the changes to the databse.
This would revert the database and the db/schema.rb file to its previous state. On the CreateTransferRequest migration file the value of the foreign_key should be false or we can just ommit the part where the foreign_key is passed since it is false by default like the code below.
Now lets run the migration again.
The db/schema.rb should be appended with the code below. It is expected not to call the add_foreign_key method.
TransferRequest still doesn’t know that its attributes sender and receiver are instances of User so we pass class_name: ‘User’ as an argument to the method belongs_to at the TransferRequest model.
User doesn’t know that it has many senders and receivers. Both from the table transfer_request. Modify the User model to associate it with TransferRequest. The User should look like this:
Here the method has_many was called passing a symbol ending with _transfer_request as the first argument following the naming convention for Active Record association. The second argument tells Rails that it is an instance of TranferRequest. The third argument specifies which the column of the of the table transfer_request is the foreign key.
If you run the test now then it would succeed.