简体   繁体   中英

How to group_by this array of hashes

I have read data in CSV format from a file into the following array:

arr = [
["company", "location", "region", "service", "price", "duration", "disabled"], 
["Google", "Berlin", "EU", "Design with HTML/CSS", "120", "30", "false"], ["Google", "San Francisco", "US", "Design with HTML/CSS", "120", "30", "false"], 
["Google", "San Francisco", "US", "Restful API design", "1500", "120", "false"],
["IBM", "San Francisco", "US", "Design with HTML/CSS", "120", "30", "true"],
["Google<script>alert('hi')<script>", "Berlin", "EU", "Practical TDD", "300", "60", "false"],
["Œoogle", "San Francisco", "US", "Restful API design", "1500", "120", "false"],
["Apple", "Berlin", "EU", "Practical TDD", "300", "60", "true"],
["Apple", "London", "EU", "Advanced AngularJS", "1200", "180", "false"],
["Apple", "New York", "US", "Restful API design", "1500", "120", "false"]
]

which i want to import in the database. Based on the below mentioned associations

# company.rb
  has_many :regions
  has_many :services

# region.rb
  has_many :branches
  belongs_to :company

# branch.rb
  belongs_to :region
  has_many :services

# service.rb
  belongs_to :company
  belongs_to :branch

May be the below mentioned hash can be used:(not sure. Please suggest a good design if possible)

{"Google" : [ 
  :name => "Google",
  :regions_attributes => {
    :name => "US", 
    :locations_attributes => {
      :name => "San Francisco"
    }
  },
  :services_attributes: [{
    :name => "Restful API design",
    ...
  },
  {
    :name => "Design with HTML/CSS",
    ...
  }]
]}

My attempt for this:

companies = []
CSV.foreach(csv_file, headers: true) do |row|
  company = {}
  company[:name]   = row['company']
  company[:regions_attributes] = {}
  company[:regions_attributes][:name] = row['region']
  company[:regions_attributes][:branches_attributes] = {}
  company[:regions_attributes][:branches_attributes][:name] = row['location']
  company[:services_attributes] = {}
  company[:services_attributes][:name] = row['service']
  company[:services_attributes][:price] = row['price']
  company[:services_attributes][:duration] = row['duration']
  company[:services_attributes][:disabled] = row['disabled']
  companies << company
end

companies.uniq! { |c| c.values }
companies = companies.group_by { |c| c[:name] }

It groups by company name.

I want to group services which are in one region as mentioned in the above example that Sanfrancisco, US has two services under it.

Update

Based on Cary Swoveland's solution, i'm able to modify as per the requirements but the associations are not working as i thought of.

companies = CSV.read(csv_file, headers: true).group_by {|csv| csv["company"]}
final = []
companies.transform_values do |arr1|
  company = Company.new(name: arr1.pluck("company").first.encode(Encoding.find('ASCII'), encoding_options))
  services = arr1.map do |c|
    { name: c['service'], price: c['price'], duration: c['duration'], disabled: c['disabled'] }
  end.uniq
  company.services.build(services)
  regions = arr1.group_by { |csv| csv["region"] }.transform_values do |arr2|
    branches = []
    branches << arr2.pluck('location').uniq.map { |location| { name: location, services_attributes: services } }
    { name: arr2.pluck('region').uniq.first, branches_attributes: branches.flatten }
  end
  company.regions.build(regions.values)
  final << company
end

Company.import(final, recursive: true) #activerecord-import gem

Consider changing the structure of your hash and constructing it with the code below. The file 'tmp.csv' contains the first 20 or so rows of the csv file whose link is given by the OP. I've included its contents at the end.

require 'csv'

CSV.read('tmp.csv', headers: true).group_by { |csv| csv["company"] }.
    transform_values do |arr1|
      arr1.group_by { |csv| csv["region"] }.
           transform_values do |arr2|
             arr2.group_by { |csv| csv["location"] }.
                  transform_values do |arr2|
                    arr2.map { |csv| csv["service"] }.uniq
                  end
           end
    end

  #=> {"Google"=>{
         "EU"=>{
           "Berlin"=>["Design with HTML/CSS","Advanced AngularJS","Restful API design"],
           "London"=>["Restful API design"]
         },
         "US"=>{
            "San Francisco"=>["Design with HTML/CSS", "Restful API design"]
         }
       },
       "Apple"=>{
         "EU"=>{
           "London"=>["Design with HTML/CSS"],
           "Berlin"=>["Restful API design"]
         },
         "US"=>{
           "San Francisco"=>["Design with HTML/CSS"]
         }
       },
       "IBM"=>{
         "US"=>{
           "San Francisco"=>["Design with HTML/CSS"]
         },
         "EU"=>{
           "Berlin"=>["Restful API design"],
           "London"=>["Restful API design"]
         }
      }
     }

If this hash format is not suitable (but the content is what is needed), it could be easily changed to a different format.

See the docs for CSV::read , CSV::Row#[] , Enumerable#group_by and Hash#transform_values .

I was required to do some pre-processing on the linked csv file. The problem is that the company names were preceded by a "Byte Order Mark" for a UTF-8 file (Search for "Ok, figured it out" here .) I used the code given [here] by Nathan Long to remove those characters. The OP will have to write the CSV files without those marks or strip them off when reading the files.

The content of my reduced CSV test file is the following.

arr = ["company,location,region,service,price,duration,disabled\n",
       "Google,Berlin,EU,Design with HTML/CSS,120,30,FALSE\n",
       "Google,San Francisco,US,Design with HTML/CSS,120,30,FALSE\n",
       "Google,San Francisco,US,Restful API design,1500,120,FALSE\n",
       "Apple,London,EU,Design with HTML/CSS,120,30,FALSE\n",
       "Google,Berlin,EU,Design with HTML/CSS,120,30,FALSE\n",
       "Apple,Berlin,EU,Restful API design,1500,120,FALSE\n",
       "IBM,San Francisco,US,Design with HTML/CSS,120,30,TRUE\n",
       "Google,San Francisco,US,Design with HTML/CSS,120,30,FALSE\n",
       "IBM,Berlin,EU,Restful API design,1500,120,TRUE\n",
       "IBM,London,EU,Restful API design,1500,120,TRUE\n",
       "IBM,Berlin,EU,Restful API design,1500,120,TRUE\n",
       "IBM,London,EU,Restful API design,1500,120,TRUE\n",
       "IBM,San Francisco,US,Design with HTML/CSS,120,30,TRUE\n",
       "Google,Berlin,EU,Advanced AngularJS,1200,180,FALSE\n",
       "Google,Berlin,EU,Restful API design,1500,120,FALSE\n", 
       "Google,London,EU,Restful API design,1500,120,FALSE\n",
       "Apple,San Francisco,US,Design with HTML/CSS,120,30,FALSE\n",
       "Google,San Francisco,US,Restful API design,1500,120,FALSE\n",
       "IBM,Berlin,EU,Restful API design,1500,120,TRUE\n"]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM