0

I have a hash, e.g.:

hash = {field1: 'name', field2: 'street1', field3: 'street2', field4: 'city'}

and I would like to replace all of the key names with the values I have in a separate array, e.g.:

array = [:address1, :address2, :address3, :city]

How could I most nicely acheive this in Ruby?

Stefan
  • 109,145
  • 14
  • 143
  • 218
jbk
  • 1,911
  • 19
  • 36
  • Where do the key names in your hash come from? – Stefan Aug 01 '17 at 13:29
  • 1
    Scraped values from fields in a table which do not match the attrs of the model I'm trying to save them in, thus my manually having to rename them to fit my model attrs. – jbk Aug 01 '17 at 13:31
  • 2
    It would be safer to have an explicit mapping in that case, i.e. `{ field1: :address1, field2: :address2, ... }` instead of relying on the field's offset. – Stefan Aug 01 '17 at 13:34
  • I don't know if there is a way to replace actual keys of a hash? – Sagar Pandya Aug 01 '17 at 13:58
  • 1
    @sagarpandya82 only by modifying the key objects and calling `rehash`. But symbols are always immutable and even string keys can't be modified because they are frozen beforehand. In general, hash keys are not supposed to change. – Stefan Aug 01 '17 at 14:09
  • 1
    Since Ruby v1.9 it has been guaranteed that hash keys are ordered by their time of insertion. That property can be useful in some situations, though many Rubiests always treat hash keys as unordered, reflecting the origins of the concept of a hash. What you want to do, however, is fraught with peril. My advice: don't do that! If you want to change the keys use a hash for mapping them, as @Stefan suggests. If that's not practical, reorganize your code. – Cary Swoveland Aug 01 '17 at 17:25

3 Answers3

4
hash = {field1: 'name', field2: 'street1', field3: 'street2', field4: 'city'}
array = [:address1, :address2, :address3, :city]

h = Hash[array.zip hash.values]
# => {:address1=>"name", :address2=>"street1", :address3=>"street2", :city=>"city"}
guitarman
  • 3,290
  • 1
  • 17
  • 27
3
hash = {field1: 'name', field2: 'street1', field3: 'street2', field4: 'city'}
array = [:address1, :address2, :address3, :city]
hash.each_with_index.map { |(k, v), i| [array[i], v] }.to_h
#=> {:address1=>"name", :address2=>"street1", :address3=>"street2", :city=>"city"}
mdesantis
  • 8,257
  • 4
  • 31
  • 63
3

Your code depends on the order of the scraped table (or the order you are scraping the data off the table), which seems a little brittle. I would use an explicit mapping from table field names to model attribute names (or vice-versa, it doesn't really matter for a 1:1 mapping):

attrs = {
  :field1 => :address1,
  :field2 => :address2,
  :field3 => :address3,
  :field4 => :city
}

Given a hash:

hash = { field1: 'name', field2: 'street1', field3: 'street2', field4: 'city' }

it can be converted via:

attrs.map { |k, v| [v, hash[k]] }.to_h
#=> {:address1=>"name", :address2=>"street1", :address3=>"street2", :city=>"city"}
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • 1
    ...or `hash.map { |k,v| [attrs[k], v] }.to_h`.These both assume `attrs` and `hash` have the same keys. If `attrs` may contain only some of `hash.keys` and/or some keys that `hash` doesn't have, `hash.map { |k,v| [attrs.key?(k) ? attrs[k] : k, v] }.to_h` (or `[attrs[k] || k, v]`) if no keys of `hash` are `nil`) – Cary Swoveland Aug 01 '17 at 16:59
  • @CarySwoveland instead of `attrs[k] || k`, there's also `attrs.fetch(k, k)` which will even work for `nil` and `false` values. – Stefan Aug 01 '17 at 17:03
  • Yes, I like that better. Hadn't thought about it working with `false` and `nil`. – Cary Swoveland Aug 01 '17 at 17:10