Rails has_one association when there are multiple entries with foreign_key
If I understand correctly has_one
, the foreign key is in the corresponding record. This means that I could have multiple related records on the "foreign" side. When I use records has_one
to get a related record, how do I determine which one will be returned?
Here's an example:
class Job < ActiveRecord::Base
has_one :activity_state
end
class ActivityState < ActiveRecord::Base
belongs_to :job
end
Let's say there are two rows in a table activity_states
that have their foreign key set to Job # 1
ActivityState.where(job_id: 1).select(:id, :job_id)
#=> [#<ActivityState id: 1, job_id: 1>, #<ActivityState id: 2, job_id: 1]
When I try to get activity_state
from JobRequest, how do I determine which one is returned?
JobRequest.find(1).activity_state
#=> #<ActivityState id: 1, job_id: 1>
Right now, it looks like it is returning the first one found.
What if I want to return the last one that was created?
source to share
As noted in the comments, the main problem is that your system creates multiple objects ActivityState
for each job. As you mentioned, your original code did this:
ActivityState.create(job_id: @job.id)
The problem is that the old one ActivityState
still contains job_id
the job for this. Hence, when you executed @job.activity_state
, you actually saw the first (and old) state of activity, because it is the first in the database. Specifically, the relation has_one
executes the LIMIT 1
sql clause , so technically the "first" record depends on how your database ordered the records (usually in the order of entry)
Usually for a relation a has_one
, if you were to do
@job.activity_state = ActivityState.new(...activity state params...)
Rails tries to implement the "one association per write" concept by resetting the "old" associated column of the record job_id
to null. Your original code was unintentionally bypassing this enforcement. If you change it to this line of code, you let Rails work its magic and provide consistent behavior with your association.
source to share
In this case, you must have a has_many association. To return a specific record based on a column, create a method.
class Job < ActiveRecord::Base
has_many :activity_states
def latest_activity_state
self.activity_states.order("updated_at ASC").last # or created_at, depends
end
end
source to share