I am wanting to display a gallery of products
where I include products that are both for sale and not for sale. Only I want the products
that are for sale to appear at the front of the list and the objects that are not for sale will appear at the end of the list.
An easy way for me to accomplish this is to make two lists, then merge them (one list of on_sale? objects and one list of not on_sale? objects):
available_products = []
sold_products = []
@products.each do |product|
if product.on_sale?
available_products << product
else
sold_products << product
end
end
. . . But do to the structure of my existing app, this would require an excessive amount of refactoring due to an oddity in my code (I lose my pagination, and I would rather not refactor). It would be easier if there were a way to sort the existing list of objects by my product
model's method on_sale?
which returns a boolean value.
Is it possible to more elegantly iterate through an existing list and sort it by this true or false value in rails? I only ask because there is so much I'm not aware of hidden within this framework / language (ruby) and I'd like to know if they work has been done before me.
Sure. Ideally we'd do something like this using sort_by!
:
@products.sort_by! {|product| product.on_sale?}
or the snazzier
@products.sort_by!(&:on_sale?)
but sadly, <=>
doesn't work for booleans (see Why doesn't sort or the spaceship (flying saucer) operator (<=>) work on booleans in Ruby?) and sort_by
doesn't work for boolean values, so we need to use this trick (thanks rohit89!)
@products.sort_by! {|product| product.on_sale? ? 0 : 1}
If you want to get fancier, the sort
method takes a block, and inside that block you can use whatever logic you like, including type conversion and multiple keys. Try something like this:
@products.sort! do |a,b|
a_value = a.on_sale? ? 0 : 1
b_value = b.on_sale? ? 0 : 1
a_value <=> b_value
end
or this:
@products.sort! do |a,b|
b.on_sale?.to_s <=> a.on_sale?.to_s
end
(putting b before a because you want "true"
values to come before "false"
)
or if you have a secondary sort:
@products.sort! do |a,b|
if a.on_sale? != b.on_sale?
b.on_sale?.to_s <=> a.on_sale?.to_s
else
a.name <=> b.name
end
end
Note that sort
returns a new collection, which is usually a cleaner, less error-prone solution, but sort!
modifies the contents of the original collection, which you said was a requirement.
No need to sort:
products_grouped = @products.partition(&:on_sale?).flatten(1)
@products.sort_by {|product| product.on_sale? ? 0 : 1}
This is what I did when I had to sort based on booleans.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With