Можно ли отсортировать список объектов в зависимости от ответа отдельного объекта на метод?

Я хочу показать галерею products, где я включаю продукты, которые продаются и не продаются. Только я хочу, чтобы products, которые продаются, появятся в начале списка, а объекты, которые не продаются, появятся в конце списка.

Простым способом для этого является создание двух списков, затем их объединение (один список объектов on_sale? и один список объектов not_sale?):

available_products = []
sold_products = []
@products.each do |product|
  if product.on_sale?
    available_products << product
  else
    sold_products << product
  end
end

., Но для структуры моего существующего приложения это потребует чрезмерного количества рефакторинга из-за странности моего кода (я теряю свою разбивку на страницы, и я бы предпочел не рефакторинг). Было бы проще, если бы был способ отсортировать существующий список объектов с помощью моего метода product model on_sale?, который возвращает логическое значение.

Возможно ли более элегантно перебирать существующий список и сортировать его по этому истинному или ложному значению в рельсах? Я только спрашиваю, потому что так много я не знаю о скрытых в этом framework/language (ruby), и я хотел бы знать, были ли они работают, было сделано передо мной.

Ответ 1

Конечно. В идеале мы сделали бы что-то подобное, используя sort_by!:

@products.sort_by! {|product| product.on_sale?}

или мерцающий

@products.sort_by!(&:on_sale?)

но, к сожалению, <=> не работает для булевых (см. Почему сортировка или оператор космического корабля (летающего тарелки) (< = > ) не работают на булевых в Ruby?) и sort_by не работает для булевых значений, поэтому нам нужно использовать этот трюк (спасибо rohit89!)

@products.sort_by! {|product| product.on_sale? ? 0 : 1}

Если вы хотите получить fancier, метод sort принимает блок, и внутри этого блока вы можете использовать любую логику, которая вам нравится, включая преобразование типов и несколько ключей. Попробуйте что-то вроде этого:

@products.sort! do |a,b|
  a_value = a.on_sale? ? 0 : 1
  b_value = b.on_sale? ? 0 : 1
  a_value <=> b_value
end

или это:

@products.sort! do |a,b|
  b.on_sale?.to_s <=> a.on_sale?.to_s
end

(помещая b перед a, потому что вы хотите, чтобы значения "true" приходили раньше "false")

или если у вас есть вторичная сортировка:

@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

Обратите внимание, что sort возвращает новую коллекцию, которая обычно является более чистым, менее подверженным ошибкам решением, но sort! изменяет содержимое оригинальной коллекции, о которой вы говорили, было требованием.

Ответ 2

@products.sort_by {|product| product.on_sale? ? 0 : 1}

Это то, что я сделал, когда мне приходилось сортировать на основе булевых элементов.

Ответ 3

Не нужно сортировать:

products_grouped = @products.partition(&:on_sale?).flatten(1)

Ответ 4

Восходящий и нисходящий могут быть сделаны путем изменения "ложных" и "истинных"

Products.sort_by {|product| product.on_sale? ==  true ? "false" : "true" }