Ruby: Using Array#assoc to keep ActiveRecords in order

Lately, I’ve had the need to sort ActiveRecords by a certain attribute, but the order is completely arbitrary, and supplied by the user.

Take, for example, the following call to a model in Rails:

input = %w(MN CO TX CA)
State.find_by_state_code(input)

This generates SQL that looks something like:

SELECT * FROM states WHERE state_code IN('MN','CO','TX', 'CA')

state_code
----------
CA
CO
MN
TX

Seemingly random results (actually alphabetized, but still worthless to me), but I have a specific requirement to process the results in the order the user specified (MN CO TX CA).

We can solve this by using Array#assoc to build a simple index of States and their codes:

input = %w(MN CO TX CA) # user-supplied
states = State.find_by_state_code(input)
states.map! { |state| [state.state_code, state] }
# states now looks something like:
# [["CA", #<State state_code="CA">], ["CO", #<State state_code="CO">], ["MN", #<State state_code="MN">], ["TX", #<State state_code="TX">]]

Now we iterate over the user-supplied list, and use assoc to pluck out the state that we want:

input.each { |s| puts states.assoc(s).last.inspect }
#<State state_code="CA" ... >
#<State state_code="CO" ... >
#<State state_code="MN" ... >
#<State state_code="TX" ... >

Previously, I was doing some ugly mumbo-jumbo with creating a hash, but assoc is a much cleaner solution. This also allows me to stick to using the SQL IN() operator, instead of iterating over the user input and doing a single call to the database, which obviously gets expensive.

This sort of reminds me of a Schwartzian Transform, although it’s not exactly the same.

Leave a Reply

You must be logged in to post a comment.