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?

+3


source to share


2 answers


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.

+4


source


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

      

+3


source







All Articles