- Read Tutorial
Typically I like to keep the default options that ship with rails when it comes to the database configuration. However there are times when overriding the default behavior makes for a more elegant implementation. Let's look at a base case. Usually when you have a has_many
and belongs_to
setup it will look something like this:
# Model: User # Attributes: name email # Model: Post # Attributes: title body user_id
The user_id
functions like the foreign key so that Rails knows the relationship between users
and posts
. This type of setup allows you to make database queries such as post.last.user
and have it bring up the post
owner. And each post
would have a user_id
that would be the id
for the user
.
However that wouldn't be a natural fit for our app. We have clients
and notifications
. We don't want to have to have outside applications pass in the ActiveRecord
id
in order to verify the relationship.
Overriding the Foreign Key
Instead we're going to tell rails that it should use the source_app
attribute as the foreign key that connects both models.
Creating Tests for Separate Foreign Key
Since this is a vital behavior element in our application, we want to ensure that it's fully tested, so let's write some specs that will create an expectation for what needs to happen. There's a pretty straightforward way to test relationships in Rails, let's add the following describe
block to the Notification
model
spec:
# spec/models/notification_spec.rb describe 'relationship' do it 'has a connection to a client based on the source_app attribute' do client = Client.create(source_app: "myapp", api_key: "RbZHfHtD1h9XZvs4fGPJUgtt") notification = client.notifications.create!(phone: '9999999999', body: 'message content') expect(notification.source_app).to eq('myapp') end end
Let's take a second to see what's going on here. I'm choosing not to use a factory because I want to be very explicit with what we're doing. I'm using the line:
client = Client.create(source_app: "myapp", api_key: "RbZHfHtD1h9XZvs4fGPJUgtt")
To create a new client
and add them to the test
database.
notification = client.notifications.create!(phone: '9999999999', body: 'message content')
Next, instead of creating a new Notification
from scratch, I'm creating one directly from the client
variable. This type of implementation would break and throw an error if the relationship isn't set properly.
expect(notification.source_app).to eq('myapp')
Lastly, I'm checking to make sure that the new notification
has the same source_app
value as the client
.
If I run the tests now they will complain that notifications
is an undefined method.
Let's fix this with the implementation.
Custom Foreign Key Implementation
Let's update the Notification
model file as shown here:
# app/models/notification.rb belongs_to :client, foreign_key: 'source_app', primary_key: 'source_app'
Next, we need to update the Client
class:
# app/models/client.rb has_many :notifications, foreign_key: 'source_app', primary_key: 'source_app'
This is s slightly different implementation than you may be used to when connecting models. The reason is because we're overriding the default behavior of both the foreign key and the primary key in both model files.
If you run the specs now you'll see that they're all passing.
Testing Out a Different Foreign Key in the Rails Console
This is a slightly different rails convention, so let's try this out in the console as well. Opening up a rails c
session, let's create a new Client
:
client = Client.create!(source_app: "anotherapp", api_key: "RbZHfHtD1h9XZvs4fGPJUgtt")
Now let's be very explicit with our foreign key and manually create a notification:
notification = Notification.create!(phone: '8888888888', body: 'message body', source_app: 'anotherapp')
Notice how I manually entered in the source_app
value, the same source_app
as from the client
. That worked, now let's run a query:
notification.client
Will return the client
object. Now let's test out the other side of the relationship:
client.notifications
Will successfully query the database and bring back the correct set of notifications
.
Summary
This was pretty cool! We were able, in only a few lines of code, to override the default foreign_key pattern in rails to build an implementation that better fits our needs. Also, notice how we didn't have to change the database schema to get this working. Usually we would have had to add a client_id
column in the notifications
database, however with this setup we can keep the table clean.
What's Next
With our connection between the model complete, let's start working on the Client
functionality, starting with validations.