How to give an order to some values in a multiple Class array in Rails?

Viewed 121

I have an Array, composed by multiple Class objects, sorted by shuffle :

@streams = (Product.all + List.all + Post.all).shuffle

In a feed page (like the timeline page on Facebook), the content of the object is displayed with .each, and on each Class, a specific partial is applied :

@streams.each do |stream|
  <% if stream.is_a?(Product) %>
   <%= render 'product_partial', object: stream %>
  <% elsif stream.is_a?(List) %>
   <%= render 'list_partial', object: stream %>
  <% end %>
 <% end %>

Goal : because there is in the app a lot of Products (e.g : 200), and less Posts (100) and even less Lists (10), I want to give an order to each content, with the Class. With this, the Posts & Lists will not be drowned on Products.

In one sentence : for 20 products, show 2 posts, 1 list.

Any ideas ? Many thanks.

2 Answers

I would use probabilities and a native ruby enumerator:

@streams =
  [Product, List, Post].map(&:all).map(&:shuffle)

type =
  case rand 23
  when 0..19 then 0
  when 20..21 then 1
  else 2
  end

@streams[type].pop # pop one element from the respective array

This has a drawback one type might end before others and you probably will need to explicitly check for this and use still non-exhausted types, but it seems to be better than an explicit 1-per-2-per-20 because it still has a pseudo-random order to some extent.

You could do something like:

@streams = custom_order(Products.all, Lists.all, Posts.all)

def custom_order(products, lists, posts)
  products_blocks = products.in_groups_of(20, false)
  lists_blocks = lists.in_groups_of(2, false)
  posts_blocks = posts.in_groups_of(1, false)

  result = []

  biggest_array = [products_blocks.length, lists_blocks.length, posts_blocks.length].max

  1.upto(biggest_array) do |_|
    # here we're pushing the blocks, result will be something like [[product, product .. product] [list, list] [posts]]
    result << products_blocks.shift
    result << lists_blocks.shift
    result << posts_blocks.shift
    # is ok if one of the blocks is nil, we'll use compact later
  end

  # result => [[product product product] [list list] [post] [product product] [list] nil]
  # compact to remove the nils
  # result => [[product product product] [list list] [post] [product product] [list]]
  # after flatten
  # result => [product product product list list post product product list]
  result.compact.flatten
end

EDIT: changed compact and flatten

Related