Para arquivos offline use a gem Parseline
Friday, November 14th, 2008Parsear 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