Модуль CSV в Рубин


Я закончила Руби главу в семь языков за семь недель. Он пытается заставить вас знакомы с основными концепциями нескольких языках довольно быстро. Я послушно сделала все упражнения, но, скорее всего, они могут быть улучшены, чтобы быть более ярко-как.

Дано: CSV-файл структурированный с первой строки с заголовками и последующие строки с данными.

one, two
lions, tigers

Создать модуль, который загружает заголовок и значений из csv-файла на основе имени класса реализации. (RubyCSV -> "rubycsv.txt") поддерживают каждого метод, который возвращает CsvRow объект. Использовать и method_missing , чтобы вернуть значение столбца данной рубрики. Е. Г. использование которая будет печатать "Львы":

m = RubyCsv.new
m.each { |row| p row.one }

Моя реализация:

class CsvRow
  attr :row_hash

  def initialize( row_hash )
    @row_hash = row_hash
  end

  def method_missing( name, *args )
    @row_hash[ name.to_s ]
  end
end

module ActsAsCsv
  attr_accessor :headers, :csv_contents

  def self.included( base )
    base.extend ClassMethods
  end

  module ClassMethods
    def acts_as_csv
      include InstanceMethods
    end
  end

  module InstanceMethods
    def read
      @csv_contents = []
      filename = self.class.to_s.downcase + '.txt'
      file = File.new( filename )
      @headers = file.gets.chomp.split( ', ' )

      file.each do |row|
        @csv_contents << row.chomp.split( ', ' )
      end
    end               

    def initialize
      read
    end

    def each      
      @csv_contents.each do |content|        
        hash = {}
        @headers.zip( content ).each { |i| hash[ i[0] ] = i[1] }
        yield CsvRow.new hash
      end
    end
  end
end

class RubyCsv  # No inheritance! You can mix it in.
  include ActsAsCsv
  acts_as_csv
end


976
5
задан 28 апреля 2011 в 02:04 Источник Поделиться
Комментарии
1 ответ

def method_missing( name, *args )
@row_hash[ name.to_s ]
end

Делать это, как это означает, что если пользователь вызывает метод строку с аргументами, аргументы будут молча игнорироваться. Также, если пользователь вызывает любой метод, который не существует и для которых не существует ни строк, он будет просто вернуться к нулю. Я думаю, что в обоих случаях должно генерироваться исключение, так что я бы реализовать и method_missing такой:

def method_missing( name, *args )
if @row_hash.has_key?(name.to_s)
if args.empty?
@row_hash[ name.to_s ]
else
raise ArgumentError, "wrong number of arguments(#{ args.size } for 0)"
end
else
super
end
end


module ActsAsCsv
# ...

def self.included( base )
base.extend ClassMethods
end

module ClassMethods
def acts_as_csv
include InstanceMethods
end
end

module InstanceMethods
...
end
end

Эта установка кажется излишне сложным. Поскольку все acts_as_csv не включает методов экземпляра (за исключением заголовков и csv_contents, которая будет присутствовать даже если acts_as_csv не называется - что кажется несколько произвольным для меня) и нет никаких причин, почему пользователь хотел бы включить ActsAsCsv без получения экземпляра методы, я не вижу повода для acts_as_csv существовать вообще. Экземпляр методы должны быть непосредственно в ActsAsCsv модуль и ClassMethods и InstanceMethods модули не должны существовать.

В этом случае код будет менее сложной, и вам нужна только одна линия включает ActsAsCsv вместо двух, чтобы включить в CSV функциональность.


def read
@csv_contents = []
filename = self.class.to_s.downcase + '.txt'
file = File.new( filename )
@headers = file.gets.chomp.split( ', ' )

file.each do |row|
@csv_contents << row.chomp.split( ', ' )
end
end

Прежде всего вы открываете файл и не закрывать его. Вы должны использовать файл.откройте блок вместо.

Во-вторых, вы должны использовать функции высшего порядка, как на карте. Используя карту вы можете создать @csv_contents такой, вместо того чтобы добавить его в каждый цикл:

def read
filename = self.class.to_s.downcase + '.txt'
file = File.open( filename ) do |file|
@headers = file.gets.chomp.split( ', ' )
@csv_contents = file.map {|row| row.chomp.split( ', ' )}
end
end


Это, как говорится, я не думаю, что это хорошая идея, чтобы прочитать весь файл в память заранее (или вообще), как это сделает вашу библиотеку непригодными для использования на больших файлах (которые могут даже не поместиться в памяти).

Так что я бы избавиться от читать методики и просто открыть и прочитать файл в каждый метод, как это:

def each
filename = self.class.to_s.downcase + '.txt'
File.open(filename) do |file|
headers = file.gets.chomp.split( ', ' )
file.each do |content|
hash = {}
headers.zip( content.chomp.split(', ') ).each { |i| hash[ i[0] ] = i[1] }
yield CsvRow.new hash
end
end
end


Наконец, вместо

hash = {}
headers.zip( content ).each { |i| hash[ i[0] ] = i[1] }

Вы также можете написать хэш = хэш - [ headers.zip( содержание ) ].


В целом, ваш код не совсем корректно парсить CSV-файлов. Ваш код предполагает, что поля разделены запятой и пробелом. Однако это не требуется, что запятые на самом деле следуют пробелы (и РЧЦ на самом деле говорит, что любой пробел после запятой является частью содержимого поля и не должны быть проигнорированы). Ты же не цитируешь (например, фу, бар", баз", залив, который представляет собой строку, содержащую три, не четыре, полей) на все.

3
ответ дан 28 апреля 2011 в 03:04 Источник Поделиться