HashCode

An organizer of symbols

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 # salvando :)                     

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|
  #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

5 Responses to “Para arquivos offline use a gem Parseline”

  1. Marcus Derencius Says:

    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.

  2. shairon Says:

    Fica bom usando unpack, regexp também é uma boa.

  3. HashCode » Blog Archive » ActiveRecord ParseLine no GitHub Says:

    [...] Versão em Português aqui [...]

  4. nike Says:

    Muito bom seu blog..

    Valeu pelas informações

    Abs

    Fred

  5. silfar Says:

    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.

Leave a Reply

Quanto é ?