Order Hash by Key then Values

I’m using group_by to return a hash that I would like to order by key first, and then order the values under that key.

Here’s the code I’ve come up with so far. Everything works fine, except I can’t figure out how to sort the values after they’re grouped.

I have a method, ‘pp_accounts_with_properties’ which shows my intent and prints out everything in the correct order (thanks to the properties.sort_by! call)… But it seems like I should be able to accomplish this somehow in the ‘accounts_with_properties’ method.

class ProofList
  attr_reader :client

  def initialize(client)
    @client = client
  end

  def properties
    @client.properties.joins(invoices: [charges: [:charge_credits]])
                      .where(charge_credits: { approved: false }).uniq
  end

  def accounts_with_properties
    unsorted_accounts_with_properties.sort_by { |account, properties| account[:acct_number] }
  end

  def unsorted_accounts_with_properties
    properties.group_by { |property| property.account }
  end

  def pp_accounts_with_properties
    accounts_with_properties.each do |account, properties|
      puts account.name
      properties.sort_by! { |p| p[:legal_description] }
      properties.each do |property|
        puts property.name
      end
    end
  end
  
end

Also, thanks @cpytel for setting me in the right direction w/ the group_by…

@crispincornett, there are several things happening here. Let’s follow the code at each step to see what happens.

Stepping through the code

ProofList#properties

This method uses ActiveRecord query methods to return an ActiveRecord::Relation set of unique properties properties that belong to @client and meet certain conditions.

ProofList#unsorted_accounts_with_properties

This method takes the ActiveRecord::Relation returned by properties and converts it into a hash where property account keys point to arrays of properties. For example (for simplicity, I will assume that accounts are integers):

{ 
  1234 => [
    #<Property:0x007>,
    #<Property:0x006>
  ],
  9876 => [
    #<Property:0x008>
  ]
}

ProofList#accounts_with_properties

So far so good. Here comes the tricky part. accounts_with_properties attempts to sort the hash returned by unsorted_accounts_with_properties. However, sorting a hash returns an Array! Both sort and sort_by will return something like this:

[
  [1234, [#<Property:0x007>, #<Property:0x006>]],
  [9876, [#<Property:0x008>]]
]

ProofList#pp_accounts_with_properties

Finally, you loop over the array returned by accounts_with_properties and grab the array of properties from each sub-array. You sort this array of properties and output it’s contents. Simple enough.

Why?

The two main structures for dealing with lists in ruby are Array and Hash. Arrays are for ordered sets of data and access is position-based. Hashes on the other-hand are more free-form. They typically are used for un-ordered sets of data where a relation between a key and value is important. When using my_hash[:my_key] we don’t care whether the value pointed to by :my_key is in position 1, 2, or n. Hashes do have a very basic sense of order though, key/value pairs in the hash are in order of insertion. So what if we need information from a hash in an ordered fashion? Hash#sort and Hash#sort_by allow us to do so by converting the hash into a structure that does care about position, an Array.

Now that you know what kind of structure your method is returning, you can modify the code that consumes it to iterate correctly over it (pp_accounts_with_properties already does so!). If you are more interested in iterating over a sorted set than in using keys to grab particular values, an Array is probably a better choice than a hash anyways.

2 Likes