Parsear um arquivo texto em Ruby é algo fácil, persistir os dados no banco com Rails é trivial.
Vou demostrar como carregar arquivos csv e arquivos com layout largura fixa no braço, e mais a frente a nova gem Parseline.
Caso 1: Largura fixa
Suponhamos que seja necessário carregar dados de produtos de um arquivo denominado “data.txt” com o conteúdo
000001PRODUTO 1 S 21/11/2008000090.00
000002PRODUTO 2 N 22/11/2008000341.33
000003PRODUTO 3 N 01/11/2008000001.99
000004PRODUTO 4 S 15/11/2008000034.98
000005PRODUTO 5 N 14/11/2008000130.44
000006PRODUTO 6 S 05/11/2008000020.11
sendo o layout definido dessa forma:
- posição 0 até 5 = código do produto 00221
- posiçao 6 até 21 = nome do produto
- posição 22 = ‘S’ se tem em estoque e ‘N’ não tem
- posição 23 = reservado para uso futuro
- posição 24 até 33 = a data do cadastro
- posição 34 até 42 = preço do produto
Nossa tabela/model será Product definida por
class CreateProducts < ActiveRecord::Migration
def self.up
create_table :products do |t|
t.integer :code
t.string :name
t.boolean :in_stock
t.date :date
t.float :price
end
end
def self.down
drop_table :products
end
end
Vamos percorrer cada linha do arquivo fazendo parser dos campos, como descrito no layout acima
File.readlines('data.txt').each do |line|
line.strip!
code = line[0..5]
name = line[6..21].strip
in_stock = line[22..22]
nothing = line[23..23]
date = line[24..33]
price = line[34..42]
in_stock=(in_stock=='S')
date=date.split(/\//).reverse.join('-')
product=Product.new :code => code,
:name => name,
:in_stock => in_stock,
:date => date,
:price => price
product.save
end
Pronto, tá lá no banco.
Caso 2: Arquivos CSV
Arquivos delimitados por um caracter geralmente , ou ;
Esse será o nosso arquivo “data.csv” com o conteúdo
1;PRODUTO 1;S;;21/11/2008;90.00
2;PRODUTO 2;N;;22/11/2008;341.33
3;PRODUTO 3;N;;01/11/2008;1.99
4;PRODUTO 4;S;;15/11/2008;34.98
5;PRODUTO 5;N;;14/11/2008;130.44
6;PRODUTO 6;S;;05/11/2008;20.11
temos o layout para cada linha
código do produto;nome;se tem em estoque;*reservado*;data;preço
Utilizaremos a mesma migration CreateProducts. Vamos ao código
File.readlines("data.csv").each do |line|
code,name,in_stock,nothing,date,price = line.strip.split(';')
in_stock=(in_stock=='S')
date=date.split(/\//).reverse.join('-')
product=Product.new :code => code,
:name => name,
:in_stock => in_stock,
:date => date,
:price => price
product.save
end
Nova gem ParseLine
O que ela faz? Nos ajuda a carregar dados externos de arquivos offline de uma forma mais elegante.
Suporta CSV ou largura fixa.
Funcionamento
Basta extender o módulo ParseLine::CSV ou ParseLine::FixedWidth e definir o layout.
Caso 1: Largura fixa
Utilizaremos o módulo ParseLine::FixedWidth com o arquivo “data.txt”, a sintáxe é
parse.field :nome_do_campo, intervalo, lambda{|campo| fomatador}
Olha como é simples
require 'parseline'
class Product < ActiveRecord::Base
extend ParseLine::FixedWidth
fixed_width_layout do |parse|
parse.field :code , 0..5
parse.field :name, 6..21
parse.field :in_stock, 22..22, lambda {|f| f == 'S' }
parse.field :date , 24..33, lambda {|d| d.split(/\//).reverse.join('-') }
parse.field :price, 34..42
end
end
então posso carregar apenas uma linha
dados=File.readlines("data.txt")
@product=Product.load_line dados[0]
@product.save
ou carregar automáticamente todo arquivo em um array de Product
@products=Product.load_lines "data.txt"
conteúdo de @products
[
]
Caso 2: Arquivos CSV
Utilizaremos o módulo ParseLine::CSV com o arquivo “data.csv”, a sintáxe é
parse.field :nome_do_campo, lambda{|campo| fomatador}
para ignorar um campo como aquele “reservado para uso futuro” basta invocar o método ignore_field
Veja
class Product < ActiveRecord::Base
extend ParseLine::CSV
csv_layout :delimiter => ";" do |parse|
parse.field :code
parse.field :name
parse.ignore_field
parse.field :in_stock, lambda {|f| f == 'S' }
parse.field :date , lambda {|d| d.split(/\//).reverse.join('-') }
parse.field :price
end
end
O ParseLine::CSV também tem os métodos Model.load_line e Model.load_lines.
Então temos para uma linha de dados
dados=File.readlines("data.csv")
@product=Product.load_line dados[0]
@product.save
Ou carregar tudo de um array
@products=Product.load_lines "data.txt"
Instalação
#rubyforge
sudo gem install parseline
Se ainda não estiver disponível baixe aqui
Espero que gostem.
ps. em breve no GitHub em inglês