Todd
Todd

Reputation: 3113

What is the most elegant Ruby expression for comparing and selecting values from a 2D Array?

I have some code that is chugging through a set of Rails Active Record models, and setting an attribute based on a related value from a 2D Array.

I am essentially setting a US State abbreviation code in a table of US States which was previously only storing the full names. A library of state names is being used to derive the abbreviations, and it contains a 2D Array with each sub-array having a full name, and an abbreviation (i.e., [['New York', 'NY']['Pennsylvania', 'PA'][etc]]). I compare the state name from each record in the database to each full text name in this Array, then grab the corresponding sibling Array cell when there is a match.

This code works fine, and produces the correct results, but its frumpy looking and not easily understood without reading many lines:

# For the following code, StatesWithNames is an Active Record model, which is
#  having a new column :code added to its table.
# Sates::USA represents a 2D Array as: [['StateName', 'NY']], and is used to 
#  populate the codes for StatesWithNames. 
# A comparison is made between StatesWithNames.name and the text name found in
#  States::USA, and if there is a match, the abbreviation from States::USA is
#  used
if StatesWithNames.any? 
  StatesWithNames.all.each do |named_state|
    if named_state.code.blank?
      States::USA.each do |s|
        if s[0] == named_state.name
          named_state.update_column(:code, s[1])
          break
        end
      end
    end
  end
end

What is the most Ruby style way of expressing assignments with logic like this? I experimented with a few different procs / blocks, but arrived at even cludgier expressions, or incorrect results. Is there a more simple way to express this in fewer lines and/or if-end conditionals?

Upvotes: 0

Views: 76

Answers (3)

Andrey Deineko
Andrey Deineko

Reputation: 52367

Yea, there is a few ifs and checks, that are not needed.

Since it is Rails even though it does not state so in question's tags, you might want to use find_each, which is one of the most efficient way to iterate over a AR collection:

  StatesWithNames.find_each do |named_state|
    next unless named_state.code.blank?
    States::USA.each do |s|
      named_state.update_column(:code, s[1]) if s[0] == named_state.name       
    end
  end

Also be aware, that update_column bypasses any validations, and if you wish to keep your objects valid, stick to update!. And last thing - wrap it all in transaction, so if anything goes wrong all the way - it would rollback any changes.

    StatesWithNames.transaction do
      StatesWithNames.find_each do |named_state|
        next unless named_state.code.blank?
        States::USA.each do |s|
          named_state.update!(:code, s[1]) if s[0] == named_state.name       
        end
      end
    end

Upvotes: 2

Mircea
Mircea

Reputation: 10566

the first thing you want to do is convert the lookup from an array of arrays to a hash.

state_hash = States::USA.to_h

if StatesWithNames.any?
  StatesWithNames.all.select{|state| state.code.blank?}.each do |named_state|
    named_state.update_column(:code, state_hash[named_state.name]) if state_hash[named_state.name]
  end
end

Upvotes: 0

yez
yez

Reputation: 2378

You might use a different data structure for this.

With your existing 2D array, you can call to_h on it to get a Hash where

a = [['California', 'CA'], ['Oregon', 'OR']].to_h
=> { 'California' => 'CA', 'Oregon' => 'OR' }

Then in your code you can do

state_hash = States::USA.to_h

if StatesWithNames.any? 
  StatesWithNames.all.each do |named_state|
    if named_state.code.blank?
      abbreviation = state_hash[named_state.name]
      if !abbreviation.nil?
        named_state.update_column(:code, abbreviation)
      end
    end
  end
end

Upvotes: 1

Related Questions