Mavvie
Mavvie

Reputation: 320

Postgres/ActiveRecord bug?

I have the following method in an ActiveRecord, with some simplified classes/associations detailed as well:

class LineItem < ApplicationRecord
  has_many :line_item_parts

  def media
    best = line_item_parts.detect do |part|
      part.part.medias.any?
    end
    best_media = best && best.part.medias.first
    best_media || product.medias.first
  end
end

class LineItemPart < ApplicationRecord
  belongs_to :line_item
  belongs_to :part
end

class Part < ApplicationRecord
  belongs_to :product
  has_many :medias
end

class Media < ApplicationRecord
  belongs_to :product
  belongs_to :part, optional: true
end

When running it on the server in a binding.pry, I do the following:

> line_item.media #=> nil
> line_item.media #=> <Media: ...>

It sometimes will return nil the first time, but when I run it a second time it always returns the correct object. I see the following line in the logs for the first run:

  Products::Media Exists (1.0ms)  SELECT  1 AS one FROM "products_medias" WHERE "products_medias"."part_id" = $1 LIMIT $2  [["part_id", 472], ["LIMIT", 1]]

Executing the query manually returns a Media result, as expected. And in the second run, it's the same query but uses AR's CACHE, but on the second run it returns the correct result (a non-nil Media object).

How is this possible? Is this an ActiveRecord bug? I don't even know where to start with googling something like this.

Gems:

Postgres version: psql (PostgreSQL) 10.2 (Debian 10.2-1.pgdg90+1)

Running in Docker for Mac.

Upvotes: 0

Views: 49

Answers (1)

Jacob Vanus
Jacob Vanus

Reputation: 569

I don't see the error, but that looks like it will result in several queries anyway. Might I suggest an alternative?

 def media
   best_media = Media
      .joins(parts: [line_item_parts: :line_items])
      .where('line_items.id = ?', id)
      .first
   best_media || product.medias.first
 end

If you want to stick with using the associations directly, you might try adding .to_a before the enumerator method so you are explicitly operating on the results.

def media
  best = line_item_parts.to_a.detect do |lip|
    lip.part.medias.any?
  end
  best_media = best && best.part.medias.first
  best_media || product.medias.first
end

Upvotes: 1

Related Questions