Para arquivos offline use a gem Parseline
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! #retirando caracteres não imprimiveis #definindo os campos 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] #trantando os dados in_stock=(in_stock=='S') #=> true se 'S', false se 'N' date=date.split(/\//).reverse.join('-') #=> formata data para '2008-11-05' #attribuindo os valores no AR product=Product.new :code => code, :name => name, :in_stock => in_stock, :date => date, :price => price product.save # salvandoend
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| #definindo os campos code,name,in_stock,nothing,date,price = line.strip.split(';') #trantando in_stock=(in_stock=='S') #true se 'S', false se 'N' date=date.split(/\//).reverse.join('-') #formata data para '2008-11-05' #atribuindo os valores 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 #<Product id: nil, code: "1", name: "PRODUTO 1", in_stock: false, date: "2008-11-21", price: 90.0>
ou carregar automáticamente todo arquivo em um array de Product
@products=Product.load_lines "data.txt"
conteúdo de @products
[ #<Product id: 29, code: "1", name: "PRODUTO 1", in_stock: true, date: "2008-11-21", price: 90.0>, #<Product id: 30, code: "2", name: "PRODUTO 2", in_stock: false, date: "2008-11-22", price: 341.33>, #<Product id: 31, code: "3", name: "PRODUTO 3", in_stock: false, date: "2008-11-01", price: 1.99>, #<Product id: 32, code: "4", name: "PRODUTO 4", in_stock: true, date: "2008-11-15", price: 34.98>, #<Product id: 33, code: "5", name: "PRODUTO 5", in_stock: false, date: "2008-11-14", price: 130.44>, #<Product id: 34, code: "6", name: "PRODUTO 6", in_stock: true, date: "2008-11-05", price: 20.11> ]
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 #<Product id: nil, code: "1", name: "PRODUTO 1", in_stock: false, date: "2008-11-21", price: 90.0>
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
November 14th, 2008 at 5:24 pm
para “parsear” arquivos com largura fixa, eu uso costumo usar o metodo unpack.
Ex.
——
000001PRODUTO 1 S 21/11/2008000090.00
000002PRODUTO 2 N 22/11/2008000341.33
File.readlines(’data.txt’).each do |line|
codigo, nome, em_estoque, reservado, data, preco = line.unpack(”A6A15A1A1A9A8″)
…codigo do create
end
————
Mas a sua gem deixa mais bonito esse import. Gostei.
November 14th, 2008 at 5:31 pm
Fica bom usando unpack, regexp também é uma boa.
December 28th, 2008 at 2:10 pm
[...] Versão em Português aqui [...]
January 22nd, 2009 at 3:23 pm
Muito bom seu blog..
Valeu pelas informações
Abs
Fred
May 20th, 2010 at 10:26 am
Shairon muito legal isso, veio bem a calhar, mas como eu faria para em vez de:
@product=Product.load_line dados[0]
@product.save
@product.save sempre vai criar um registro, eu gostaria de verificar se esse cara já existe, se existir eu quero gravar como update para atualizar os dados, se não existi aí sim grava.