Home > Software engineering >  Combine active_record models and sort by shared properties
Combine active_record models and sort by shared properties

Time:08-06

I'm displaying Authors on a page (Rails view) like so:

Author.where(featured: true).where.not(username: nil).order(:priority, :name)

However I have recently added a new model Photographer and I would like to combine the photographers with the authors and sort them similarly. They share most of the same columns.

I've tried:

combined = (Author.where(featured: true).where.not(username: nil)   Photographer.where(featured: true).where.not(username: nil))

combined.order(:priority, name)
=> NoMethodError (undefined method `order' for #<Array:0x00005573bc2ab3b0>)

combined.sort_by {|item| [item.priority, item.name]}
=> ArgumentError (comparison of Array with Array failed)

One caveat is that in some cases priority would be nil. So in that event it should order by name instead.

Any help is appreciated!

CodePudding user response:

The issue is that nil is only comparable with nil:

       nil <=> nil  # => 0         # -1, 0, 1 are good
"priority" <=> nil  # => nil       # `nil` means not comparable 
                                   # and you get an error from `sort_by`

Solution is to avoid comparing nil with anything else or explicitly handle the comparison.

You can use sort and sort the nils yourself:

ary = (Author.all   Photographer.all)

ary.sort do |a, b|
  if a.priority && b.priority              # priority <=> priority
    a.priority <=> b.priority
  elsif a.priority                         # priority <=> nil
    -1                # nil is lower 
  elsif b.priority                         #      nil <=> priority
    1                 # priority is higher
  else                                     #      nil <=> nil
    a.name <=> b.name # break the tie
  end
end

You can split array in two, with priority and without priority, and sort each one. That way priority is compared to priority; nil is compared to nil, which is 0, and it falls back to comparing name (like the sort above, but elsif's never match):

ary.partition(&:priority).flat_map do |part|
  part.sort_by { |i| [i.priority, i.name] }
end

Or a dumber way of doing this (it's also faster) is to make nil be something last:

ary.sort_by { |i| [i.priority||"zzz", i.name] }
  • Related