0

I have an array PARTITION which stores days.

I want to group_by my posts (ActiveRecord::Relation) according to how old are they and in which partition they lie.

Example: PARTITION = [0, 40, 60, 90]

I want to group posts which are 0 to 40 days old, 40 to 60 days old, 60 to 90 days old and older than 90 days.

Please note that I will get array data from an external source and I don't want to use a where clause because I am using includes and where fires db query making includes useless.

How can I do this?

SRack
  • 11,495
  • 5
  • 47
  • 60
Umesh Malhotra
  • 967
  • 1
  • 10
  • 25

3 Answers3

1

Here's a simple approach:

posts.each_with_object(Hash.new { |h, k| h[k] = [] }) do |post, hash|
  days_old = (Date.today - post.created_at.to_date).to_i
  case days_old
  when 0..39
    hash[0] << post
  when 40..59
    hash[40] << post
  when 60..89
    hash[60] << post
  when 90..Float::INFINITY # or 90.. in the newest Ruby versions
    hash[90] << post
  end
end

This iterates through the posts, along with a hash which has a default value of an empty array.

Then, we simply check how many days ago a post was created and add it to relevant key of the hash.

This hash is then returned when all posts have been processed.

You can use whatever you want for the keys (e.g. hash["< 40"]), though I've used your partitions for illustrative purposes.

The result will be something akin to the following:

{ 0:  [post_1, post_3, etc],
  40: [etc],
  60: [etc],
  90: [etc] }

Hope this helps - let me know if you've got any questions.


Edit: it's a little trickier if your PARTITIONS are coming from an external source, though the following would work:

# transform the PARTITIONS into an array of ranges
ranges = PARTITIONS.map.with_index do |p, i|
  return 0..(p - 1) if i == 0 # first range is 0..partition minus 1
  return i..Float::INFINITY if i + 1 == PARTITIONS.length # last range is partition to infinity
  p..(PARTITIONS[i + 1] - 1)
end

# loop through the posts with a hash with arrays as the default value
posts.each_with_object(Hash.new { |h, k| h[k] = [] }) do |post, hash|
  # loop through the new ranges
  ranges.each do |range|
    days_old = Date.today - post.created_at.to_date
    hash[range] << post if range.include?(days_old) # add the post to the hash key for the range if it's present within the range
  end
end

A final edit:

Bit silly using each_with_object when group_by will handle this perfectly. Example below:

posts.group_by |post|
  days_old = (Date.today - post.created_at.to_date).to_i
  case days_old
  when 0..39
    0
  when 40..59
    40
  when 60..89
    60
  when 90..Float::INFINITY # or 90.. in the newest Ruby versions
    90
  end
end
SRack
  • 11,495
  • 5
  • 47
  • 60
0

Assumptions:

  1. This partitioning is for display purposes.
  2. The attribute you want to group by is days
  3. You want to the result a hash

{ 0 => [<Post1>], 40 => [<Post12>], 60 => [<Post41>], 90 => [<Post101>] }


add these methods to your model

# post.rb

def self.age_partitioned
  group_by(&:age_partition)
end

def age_partition
  [90, 60, 40, 0].find(days) # replace days by the correct attribute name
end

# Now to use it

Post.where(filters).includes(:all_what_you_want).age_partitioned
khaled_gomaa
  • 3,382
  • 21
  • 24
-1

As per the description given in the post, something done as below could help you group the data:

result_array_0_40 = [];result_array_40_60 = [];result_array_60_90 = [];result_array_90 = [];
result_json = {}

Now, we need to iterate over values and manually group them into dynamic key value pairs

PARTITION.each do |x|
    result_array_0_40.push(x) if (0..40).include?(x)
    result_array_40_60.push(x) if (40..60).include?(x)
    result_array_60_90.push(x) if (60..90).include?(x)
    result_array_90.push(x) if x > 90
    result_json["0..40"]  = result_array_0_40
    result_json["40..60"] = result_array_40_60
    result_json["60..90"] = result_array_60_90
    result_json["90+"]    = result_array_90
end

Hope it Helps!!

Rohan
  • 2,681
  • 1
  • 12
  • 18