13

My table field names are lowercase and the field names I get from CSV files are camelcase. Is there anyway I can convert the keys of an array of hashes to lowercase?

Here is the code I have right now:

    CSV.foreach(file, :headers => true) do |row|
      Users.create!(row.to_hash)
    end

This is failing because the keys are camel case (I've verified this by manually editing the file to make the header row all lowercase).

PS. Also I would love to know why the hell rails takes table field names' case sensitivity into play to begin with?

Hopstream
  • 6,391
  • 11
  • 50
  • 81

9 Answers9

26

You can just use the header_converters option with CSV:

CSV.foreach(file, :headers => true, :header_converters => lambda { |h| h.try(:downcase) }) do |row|
  Users.create!(row.to_hash)
end

Important to put the .try in there since an empty header will throw an exception. Much better (and faster) than doing this on every row.

dalison
  • 261
  • 3
  • 3
16

You can use something like this:

CSV.foreach(file, :headers => true) do |row|
  new_hash = {}
  row.to_hash.each_pair do |k,v|
   new_hash.merge!({k.downcase => v}) 
  end

  Users.create!(new_hash)
end

I had no time to test it but, you can take idea of it.
Hope it will help

bor1s
  • 4,081
  • 18
  • 25
  • That works! But I'm also curious as to why the heck rails would enforce case here (my file system isn't case sensitive so that is not the issue). – Hopstream Nov 02 '11 at 12:36
  • Excuse me, but I do not unserstand what do you mean saying: rails takes table field names' case sensitivity into play to begin with? :) (Maybe this is my bad english knowledge :) ) – bor1s Nov 02 '11 at 13:47
  • Meaning why does rails require case of field names in table to match the keys in the hash during insertion – Hopstream Nov 02 '11 at 13:52
  • 3
    Why wouldn't it? Your DB is (probably) case-sensitive, and Rails just passes the names through. – Marnen Laibow-Koser Nov 02 '11 at 13:57
10

You can simple do

hash.transform_keys(&:downcase)

to change hash keys to lowercase, or you can also transform hash values to lowercase or upper case as per your requirement.

hash.transform_values(&:downcase) or hash.transform_values(&:upcase)

hash = {:A=>"b", :C=>"d", :E=>"f"}
hash.transform_keys(&:downcase)
=> {:a=>"b", :c=>"d", :e=>"f"}
Touseef Murtaza
  • 1,548
  • 14
  • 20
6

Since this was tagged with Rails.

With ActiveSupport starting vom version 3.0 you can use a HashWithIndifferentAccess.

That will allow lower/uppercase/symbol writing to access the keys or your Hash.

my_hash = { "camelCase": "some value" }
my_hash.with_indifferent_access[:camelcase] # 'some value'
my_hash.with_indifferent_access['camelcase'] # 'some value'
my_hash.with_indifferent_access['camelCase'] # 'some value'
my_hash.with_indifferent_access['CAMELCASE'] # 'some value'

ActiveSupport 4.0.2 also introduced this:

my_hash.deep_transform_keys!(&:downcase)
# or if your hash isn't nested:
my_hash.transform_keys!(&:downcase)
neongrau
  • 933
  • 9
  • 12
4

Ruby 1.9 provides quite a nice way to achieve this, using each_with_object to avoiding initialising a temporary variable.

CSV.foreach(file, :headers => true) do |row|
  new_hash = row.each_with_object({}) do |(k, v), h|
    h[k.downcase] = v
  end

  Users.create!(new_hash)
end
qnm
  • 521
  • 3
  • 14
3

I find this approach more elegant:

hash_with_camelcase_keys.to_a.map { |pair| [pair.first.downcase, pair.last] }.to_h
Vlatka
  • 33
  • 1
  • 2
  • 1
    `to_a.map { |key, value| [key.upcase.to_s, value] }.to_h` looks cleaner to me. – Sig May 18 '20 at 08:16
1

I would add the hash directly, more efficient than merge! because you're not creating a new hash for every pair.

CSV.foreach(file, :headers => true) do |row|
  new_hash = {}
  row.to_hash.each_pair do |k,v|
   new_hash[k.downcase] = v 
  end

  Users.create!(new_hash)
end
Travis Reeder
  • 38,611
  • 12
  • 87
  • 87
1

I found that this solution is significantly faster and a bit more "Rubyish".

CSV.foreach(file, :headers => true) do |row|
  new_hash = Hash[row.to_hash.map { |k, v| [k.downcase, v] }]
  Users.create!(new_hash)
end
  • Not sure it is more rubyish - it uses a class or module method which even uses a method ending in ! – shevy Jun 12 '14 at 20:25
1

You could consider using underscore instead of downcase because this would also transform the CamelCase to camel_case notation, which is more Rubylike.

My personal preferences goes to using deep_transform_keys: hash.deep_transform_keys{|key| key.underscore.to_sym }

As transform_keys do not traverse the whole hash.

Dennis
  • 780
  • 6
  • 17