0

I have a Product model which has many Items. The application lists unique items which belong to a product. So think of items as inventory. The following query grabs featured items for a product and removes the first item (irrelevant, but it becomes a featured item, displayed separately, if you're curious).

# product.rb
has_many :items_in_stock, -> { Item.in_stock }, class_name: 'Item'
def featured_items
  items_in_stock.select("DISTINCT ON (condition) id, items.*")
    .order(:condition, :price)
    .sort_by { |item| item[:price] }[1..-1]
end

# item.rb
scope :in_stock, -> { where(status: 'in_stock') }

The trouble is when the feaured_items are empty, the method returns nil, and not a relation object. This means I get an error if I call @product.featured_items.any? on a product that has no items. If I remove the sort_by block, I get an empty relation object.

Is there a good way to handle this other than:

items = items_in_stock.select("DISTINCT ON (condition) id, items.*").order(:condition, :price)
if items.any?
  items.sort_by { |item| item[:price] }[1..-1]
end

I can't reverse the ordering of the query because I get an error saying the order of the conditions in the order by statement must match the group conditions.

dee
  • 1,848
  • 1
  • 22
  • 34

2 Answers2

3

I'm confused...why call .any? on it then since nil is treated as false in ruby. If what you get back is nil then you know that you don't have any featured_items.

I ran this in irb and I think your issue is the [1..-1].

a = []
# => []
a.sort_by { |w| w.length }
# => []
a.sort_by { |w| w.length }[1..-1]
# => nil

The easiest way is to just do

items = items_in_stock.select("DISTINCT ON (condition) id, items.*")
  .order(:condition, :price)
  .sort_by { |item| item[:price] }
items.any? ? items[1..-1] : items

Then you don't actually have to do a check in other parts of your code unless it's necessary.

dasnixon
  • 988
  • 7
  • 18
  • I suppose that's true, but for the sake of consistency. Usually, ActiveRecord returns a relation object. Calling `any?` tells you if the array has any elements. – dee Jul 15 '13 at 23:50
  • Thanks. Any suggestions on plucking the first value from the array, but returning an array, even if its empty? – dee Jul 16 '13 at 00:04
  • By the way, on other occasions I might call `.size` or anything else on it, so it will fail anyway, unless it's not empty. So `any?` is not the only issue. Like I said, it's a question of consistency. – dee Jul 16 '13 at 00:06
  • I guess that's pretty much to be expected. It feels hackish, though. I might have to revise how to treat the "featured item" and "featured items" as they get separate handling in the view. – dee Jul 16 '13 at 00:26
  • Yea it does feel hackish. You might want to create some instance methods to handle this all for you, and leave the lookup as a method for itself. – dasnixon Jul 16 '13 at 00:29
2

instead of if items.any? you can use unless items.blank? if it's nil or empty, it won't run the condition

items.blank? checks both items.empty? and items.nil?

And of course you can use it in your featured_items

items = items_in_stock.select("DISTINCT ON (condition) id, items.*")
  .order(:condition, :price)
  .sort_by { |item| item[:price] }[1..-1]
return Array.new if items.blank?

That way you know that result will be an array, no matter what

And for the proof, you can use .blank? on a nil object, and it works on nil itself, nil.blank? returns true

Louis Kottmann
  • 16,268
  • 4
  • 64
  • 88
OMY
  • 402
  • 1
  • 8
  • 18
  • You can actually do `nil.blank?` and it will return `true`. Just go test it. And for the proof: http://stackoverflow.com/a/888877/1434075 – OMY Jul 18 '13 at 10:05
  • Can you just make an edit to your post, that way I can: `You last voted on this answer 19 hours ago Your vote is now locked in unless this answer is edited` – dasnixon Jul 18 '13 at 21:50
  • Sweet up-voted :). Sorry for that I was looking at Ruby, not Rails. – dasnixon Jul 19 '13 at 19:42