Reputation: 57
Here's the assignment:
Modify the CSV application to support an each method to return a CsvRow object. Use method_missing on that CsvRow to return the value for the column for a given heading. For example, for the file:
one, two
lions, tigers
allow an API that works like this:
csv = Ruby.Csv
csv.each {|row| puts row.one}
This should print 'lions'.
My attempt is shown below. No errors, it just returns... nothing. I've done my best to check the components piece by piece, and it seems like everything is behaving as expected except that last step, the method_missing method.
I'd appreciate it if somebody could help me understand where I took a wrong turn.
module ActsAsCsv
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_as_csv
include InstanceMethods
end
end
module InstanceMethods
attr_accessor :headers, :csv_contents
def initialize
read
end
def read
filename = self.class.to_s.downcase + '.txt'
file = File.new(filename)
@headers = file.gets.chomp.split(', ')
@csv_contents = []
file.each do |aRow|
@csv_contents << aRow.chomp.split(', ')
end
i = 0
@rows = {}
@headers.each do |a|
column = []
@csv_contents.each {|b| column << b[i]}
@rows[a] = column
i = i+1
end
@reader = CSVRow.new(@rows)
return @reader
end
end
class CSVRow
attr_accessor :content
def initialize (content)
@content = content
end
def method_missing (name)
@content[name.to_str]
end
end
end
class RubyCsv
include ActsAsCsv
acts_as_csv
end
m = RubyCsv.new
puts m.headers.inspect
m.each {|rows| puts rows.a}
I also wondered: why not just have the each method return rows, and modify rows.missing_method to return the value associated to the key of the missing method's name?. I tried to implement this various ways, none of which worked. This most recent version at least doesn't return any errors:
def each
def @rows.method_missing (name)
return self[name.to_str]
end
return @rows
end
Upvotes: 0
Views: 77
Reputation: 121020
Basically, the idea would be to lookup headers on the missing method call and if there is such a header, find the value that corresponds the index of this header in the row and return it.
Let’s start with preparing CSV.
content =
CSV.parse(<<~ROWS, headers: true)
one,two
lions,tigers
ROWS
#⇒ #<CSV::Table mode:col_or_row row_count:2>
So far, so good. Let’s monkeypatch the opened class CSV::Row
to respond to method_missing
in a custom way.
class CSV::Row
def method_missing(m, *_args, &_cb)
(idx = headers.index(m.to_s)) ? self[idx] : super
end
end
Here we check if there is such a header, assigning its index to the local variable if found. If not found, we fall back to the default response with super
.
Let’s see how it works.
content.first.one
#⇒ "lions"
content.first.two
#⇒ "tigers"
content.first.three
# NoMethodError: undefined method `three' for
#<CSV::Row one:"lions" two:"tigers"
The code above monkeypatches the core ruby CSV
class, but it should not be hard to port to any custom CSV implementation.
Upvotes: 2