HELLO, I’M SERHAT AND THIS IS MY FANCY TITLE.

Rails'te CSV İşlemleri - Roo ve SmarterCSV GEM'leri

Roo GEM

CSV, XLS ve ODS'yi başarıyla okuyor, ancak tüm yaptığı bundan ibaret - başka bir özelliği yok.

Roo GEM CSV Load

require 'roo'
s = Roo::CSV.new("/home/msdundar/Masaüstü/import.csv")
=> #<Roo::CSV:0x9db2adc @filename="/home/msdundar/Masaüstü/import.csv", @options={}, @cell={}, @cell_type={}, @cells_read={}, @first_row={}, @last_row={}, @first_column={}, @last_column={}, @header_line=1, @default_sheet="default">

Roo GEM CSV Read

# encoding: utf-8
require 'roo'
s = Roo::CSV.new("/home/msdundar/Masaüstü/import.csv")

for i in (s.first_row..s.last_row)
  puts s.cell(i,3)
end

SmarterCSV GEM

SmarterCSV sadece CSV ile çalışıyor ancak özellik bakımından çok daha üstün.

SmarterCSV (CSV Read)

# encoding: utf-8
require 'smarter_csv'

pets_by_owner = SmarterCSV.process('/home/msdundar/Masaüstü/import.csv')
puts pets_by_owner

process redis'ten tanıdık. Bu betik tamda istenilen şekilde hash döndürür:

{:tc_kimlik_no=>11111186950, :Öğrenci_no=>10000541, :ad=>"ALİ", :soyad=>"DOĞAN", :sınav_İli=>"Samsun", :bina_adı=>"OMU FEN EDEBİYAT FAKÜLTESİ B BLOK-1", :bina_kodu=>"55-FENB-002", :salon_adı=>"1.KAT A101 ANFİSİ", :salon_kodu=>"55-A101-001", :sıra_no=>1, :oturum_adı=>"1.Oturum", :saat=>"09:00", :süre=>"30 dakika", :ders_1=>"X", :ders_2=>"X", :ders_3=>"X", :ders_4=>"X", :ders_5=>"Büyüme ve Gelişme", :ders_6=>"X", :ders_7=>"X", :ders_8=>"X", :ders_9=>"X", :ders_10=>"X"}
{:tc_kimlik_no=>22222261726, :Öğrenci_no=>10001430, :ad=>"VELİ", :soyad=>"DOĞAN", :sınav_İli=>"Samsun", :bina_adı=>"OMU FEN EDEBİYAT FAKÜLTESİ B BLOK-1", :bina_kodu=>"55-FENB-002", :salon_adı=>"1.KAT A101 ANFİSİ", :salon_kodu=>"55-A101-001", :sıra_no=>2, :oturum_adı=>"1.Oturum", :saat=>"09:00", :süre=>"30 dakika", :ders_1=>"X", :ders_2=>"X", :ders_3=>"Matematik", :ders_4=>"X", :ders_5=>"X", :ders_6=>"X", :ders_7=>"X", :ders_8=>"X", :ders_9=>"X", :ders_10=>"X"}

Burada göründüğü üzere smarter_csv bir takım normalizasyon işlemleride yapıyor. Örneğin "TC Kimlik No" başlığını downcase ve snakecase yaparak tc_kimlik_no şekline sokmuş ancak "Öğrenci No" için aynısını başarılı yapamamış "Öğrenci_no" gibi birşey ortaya çıkmış. Bu durum key_mapping yaparken önemli.

SmarterCSV (Key Mapping)

# encoding: utf-8
require 'smarter_csv'

pets_by_owner = SmarterCSV.process('/home/msdundars/Masaüstü/import.csv', {:key_mapping => {:tc_kimlik_no => :identity_number, :Öğrenci_no => :student_number, :ad => :first_name, :soyad => :last_name}})
puts pets_by_owner

:Öğrenci_no yüzünden çalışmayacağını düşündüğüm betik sorunsuz çalışıyor ve şunu döndürüyor:

{:identity_number=>11111161726, :student_number=>10001430, :first_name=>"ALİ", :last_name=>"KALMAM", :sınav_İli=>"Samsun", :bina_adı=>"OMU FEN EDEBİYAT FAKÜLTESİ B BLOK-1", :bina_kodu=>"55-FENB-002", :salon_adı=>"1.KAT A101 ANFİSİ", :salon_kodu=>"55-A101-001", :sıra_no=>2, :oturum_adı=>"1.Oturum", :saat=>"09:00", :süre=>"30 dakika", :ders_1=>"X", :ders_2=>"X", :ders_3=>"Kadın Sağlığı ve Hastalıkları", :ders_4=>"X", :ders_5=>"X", :ders_6=>"X", :ders_7=>"X", :ders_8=>"X", :ders_9=>"X", :ders_10=>"X"}

SmarterCSV (Chunk Size)

Bu parametre her bir dizide bulunacak value:key ikilisinin sayısını belirtiyor ve sonuçları buna göre döndürüyor.

# encoding: utf-8
require 'smarter_csv'

pets_by_owner = SmarterCSV.process('/home/msdundars/Masaüstü/import.csv', {:chunk_size => 2, :key_mapping => {:tc_kimlik_no => :identity_number, :Öğrenci_no => :student_number, :ad => :first_name, :soyad => :last_name}})

puts pets_by_owner

SmarterCSV (Chunk Processing ve Virtual Attributes)

CSV dosyamızı bizim belirlediğimiz miktarda yığınlara (chunk) bölerek işletebiliriz. CSV dosyamız:

"TC Kimlik No","Ogrenci No","Ad","Soyad","Sınav İli"
11111186950,10800541,"EMSAL","aaa","Samsun"
22222261726,10801430,"ARZU","bbb","Samsun"
33333343420,10801740,"SEVGİ","ccc","Samsun"
44444494150,12800685,"SELDA","ddd","Samsun"
55555530048,10803182,"NAZMİYE","eee","Samsun"
66666629902,10804021,"NURGÜL","fff","Samsun"

Ruby betiği:

# encoding: utf-8
require 'smarter_csv'

total_chunks = SmarterCSV.process('/home/msdundars/Masaüstü/import.csv', {:chunk_size => 3, :key_mapping => {:tc_kimlik_no => :identity_number, :ogrenci_no => :student_number, :ad => :first_name, :soyad => :last_name}}) do |chunk|
     chunk.each do |h|
       # Virtual attribute
       h[:full_name] = [h[:first_name],h[:last_name]].join(' ')

       # first_name ve last_name key'li değerleri siler - döndürmez.
       h.delete(:first_name) ; h.delete(:last_name)
     end
     puts chunk.inspect   # bu noktada chunk'ı resque'ye paslayabiliriz.
   end

# Chunk sayısını verir
# CSV'de 6 satır var, chunk_size 3 belirttiğimiz için 6/3 = 2 chunk döndürür.
puts total_chunks

Dönen yanıt:

## İlk chunk
[{:identity_number=>11111186950, :student_number=>10800541, :sınav_İli=>"Samsun", :full_name=>"EMSAL aaa"},
{:identity_number=>22222261726, :student_number=>10801430, :sınav_İli=>"Samsun", :full_name=>"ARZU bbb"},
{:identity_number=>33333343420, :student_number=>10801740, :sınav_İli=>"Samsun", :full_name=>"SEVGİ ccc"}]

## İkinci chunk
[{:identity_number=>44444494150, :student_number=>12800685, :sınav_İli=>"Samsun", :full_name=>"SELDA ddd"},
{:identity_number=>55555530048, :student_number=>10803182, :sınav_İli=>"Samsun", :full_name=>"NAZMİYE eee"},
{:identity_number=>66666629902, :student_number=>10804021, :sınav_İli=>"Samsun", :full_name=>"NURGÜL fff"}]

SmarterCSV (Single Chunk Processing)

Aynı CSV'yi parçalara bölmeden direk tek parça olarak işletmek için:

# encoding: utf-8
require 'smarter_csv'

recordsA = SmarterCSV.process('/home/msdundars/Masaüstü/import.csv', {:col_sep => ",", :row_sep => "\n"})  # no block given

puts recordsA

Yanıt olarak bu döner:

{:tc_kimlik_no=>11111186950, :ogrenci_no=>10800541, :ad=>"EMSAL", :soyad=>"aaa", :sınav_İli=>"Samsun"}
{:tc_kimlik_no=>22222261726, :ogrenci_no=>10801430, :ad=>"ARZU", :soyad=>"bbb", :sınav_İli=>"Samsun"}
{:tc_kimlik_no=>33333343420, :ogrenci_no=>10801740, :ad=>"SEVGİ", :soyad=>"ccc", :sınav_İli=>"Samsun"}
{:tc_kimlik_no=>44444394150, :ogrenci_no=>12800685, :ad=>"SELDA", :soyad=>"ddd", :sınav_İli=>"Samsun"}
{:tc_kimlik_no=>55555530048, :ogrenci_no=>10803182, :ad=>"NAZMİYE", :soyad=>"eee", :sınav_İli=>"Samsun"}
{:tc_kimlik_no=>66666629902, :ogrenci_no=>10804021, :ad=>"NURGÜL", :soyad=>"fff", :sınav_İli=>"Samsun"}

SmarterCSV (Modellere Yazmak - Toplu)

Import dosyası:

"user_id","examination_id","language_preference","exam_center_preference"
1,1,"1","3"
1,2,"2","2"
2,1,"3","1"
2,2,"4","3"
2,3,"5","2"
3,4,"6","2"

Deneme amacıyla ilgili işlemleri Participation modelinin index controller'ına ekledim.

def index
  @participations = @examination.participations
  respond_to do |format|
    format.html

    n = SmarterCSV.process('/home/msdundars/Masaüstü/import.csv', {:key_mapping => {:tc_kimlik_no => :user_id, :ogrenci_no => :student_number, :ad => :first_name, :soyad => :last_name, :sinav_ili => :exam_center}}) do |array|
    Participation.create( array.first )
    end
  end
end

Bu action'dan beklenen, Participations sayfası her ziyaret edildiğinde beklenen şey CSV'nin işlenmesiydi. Sayfayı ziyaret ettiğimde kayıtlar eklenmedi, çünkü modelde bir takım validasyonlar buna engel oluyordu.

Bu kısıtlamamdan dolayı CSV işlenmesine rağmen kayıtlar işlenmedi -- ancak ve ancak request arka planda asılı kalmış bekliyordu. Bir süre sonra sistemde zaten olan kayıtları temizledim ve CSV'den gelen kayıtlar tıkır tıkır veritabanına yazıldı.

Yani özetle herhangi bir sebeple process tamamlanamazsa iptal olmuyor ve askıda kalıyor, şartlar uygunlaşıncada işlem kaldığı yerden devam ediyordu. Bu hem iyi, hemde tehlikeli bir durum!

SmarterCSV (Modellere Yazmak - Parça Parça)

Aynı işlemi chunk'lar halinde parça parça veritabanına yazmak için şöyle yapılabilir:

def index
  @participations = @examination.participations
  respond_to do |format|
    format.html

    n = SmarterCSV.process('/home/msdundars/Masaüstü/import.csv', {:chunk_size => 100, :key_mapping => {:tc_kimlik_no => :user_id, :ogrenci_no => :student_number, :ad => :first_name, :soyad => :last_name, :sinav_ili => :exam_center}}) do |chunk|
    MyModel.collection.insert( chunk )
    end
  end
end

SmarterCSV (Modellere Yazmak - Resque ile)

Henüz bunu denemedim fakat dökümantasyona göre bu şekilde olması gerekiyor:

def index
  @participations = @examination.participations
  respond_to do |format|
    format.html
    n = SmarterCSV.process('/home/msdundars/Masaüstü/import.csv', {:chunk_size => 100, :key_mapping => {:tc_kimlik_no => :user_id, :ogrenci_no => :student_number, :ad => :first_name, :soyad => :last_name, :sinav_ili => :exam_center}}) do |chunk|
    Resque.enque( ResqueWorkerClass, chunk )
    end
  end
end

BOM'lu CSV'lerde Unicode

Windows'ta aksi belirtilmediği sürece tüm dosyala BOM'lu yazılıyor. BOM'lu CSV dosyalarında unicode problem çıkarttığı için şöyle bir şey kullanılmalıymış:

f = File.open(filename, "r:bom|utf-8");
data = SmarterCSV.process(f);
f.close

Başarılar.


Share this post!


Blog Comments powered by Disqus.