Dan L
Dan L

Reputation: 4439

Rails has_one :through with different model name

A transaction_record has many workflows, and each workflow has many milestones. One of the milestones is marked current: true, and I want to go from the transaction_record to the current_milestone:

class TransactionRecord < ApplicationRecord
  has_many :workflows
  has_many :milestones, through: :workflows

  # DOES NOT WORK, but what I want to do...
  has_one :current_milestone, through: :workflows, class: Milestone, source: :milestones

  # Works, but want to make an association for including
  def current_milestone
    milestones.where(current: true).first
  end
end

class Workflow < ApplicationRecord
  belongs_to :transaction_record
  has_many :milestones
end

class Milestone < ApplicationRecord
  belongs_to :workflow
end

I can create a method that returns the desired milestone, but I want to make it an actual association so I can include it for DB performance.

I have a transaction_records#index page where I list the transaction_records and the current_milestone for each one. That's an n+1 unless I can figure this out.

I really want to be able to do something like:

@transaction_records = TransactionRecord.includes(:current_milestone)

<% @transaction_records.each do |transaction_record| %>
  <%= transaction_record.name %> - <%= transaction_record.current_milestone.name %>
<% end %>

update

I can specify a direction relationship between transaction_record and milestone, and then do transaction_record has_one :current_milestone, -> { where(current: true) }, class_name: Milestone. But now I'm changing my DB schema for a more efficient load query. Not the end of the world, but not my preference if I already have an association.

Upvotes: 0

Views: 380

Answers (1)

AntonTkachov
AntonTkachov

Reputation: 1784

To be honest I don't like the concept, that transaction_record has some kind of active_milestone without any mentioning about joining current_workflow.

Good solution is to think about both workflow and milestone to have an ability to be current and then:

class TransactionRecord < ApplicationRecord
  has_one :current_workflow, ....
  has_one :current_milestone, through: current_workflow, ....
end

class Workflow < ApplicationRecord
  has_one :current_milestone, condition: ....
end

This is far better for me, but you still need to add additional current flag attribute in workflow.

That's why better solution is rework your concept at all. Remove current from milestone and add current_milestone_id to workflow. If it's nil, then this workflow has no current_milestone. If it contains some id, then this is your current_workflow and current_milestone_id.

Code will look pretty the same, but it will don't have ugly condition in Workflow

Upvotes: 0

Related Questions