Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails has_one association when there are multiple records with foreign_key

If I understand has_one correctly, the foreign key is on the associated record. This means that I could have multiple related records on the "foreign" side. When I use the has_one records to get the associated 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 2 rows in the activity_states table 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 the activity_state from the 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 it finds.

What if I wanted to return the latest one that was created?

like image 265
mehulkar Avatar asked Oct 24 '25 00:10

mehulkar


2 Answers

As discussed in the comments, the main issue is that your system is creating multiple ActivityState objects for each job. As you mentioned, your original code was doing this:

ActivityState.create(job_id: @job.id)

The problem is that the old ActivityState still contains the job_id for that job. Consequently, when you executed @job.activity_state, you were indeed seeing the first (and old) activity state because it is the first in the database. Specifically, the has_one relationship executes a LIMIT 1 sql clause, so technically the "first" record is dependent on however your database was ordering the records (usually by order of entry)

Normally, for a has_one relationship, if you were to do

@job.activity_state = ActivityState.new(...activity state params...)

Rails attempts to enforce the "one association per record" concept by resetting the "old" associated record's job_id column to null. Your original code was unintentionally circumventing that enforcement. If you change it to this line of code instead, you will allow Rails to work its magic and ensure consistent behaviour with your association.

like image 195
Paul Richter Avatar answered Oct 26 '25 14:10

Paul Richter


You should have a has_many association in this case. To return a particular 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
like image 42
SHS Avatar answered Oct 26 '25 15:10

SHS



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!