changeset 27:2ea3b5e9b41b

Implement day 3 self-study extensions: * `each` method to iterate over rows * `method_missing` to handle ".header_name" as value retrieval
author IBBoard <dev@ibboard.co.uk>
date Tue, 05 Sep 2017 20:24:03 +0100
parents d50ff917c163
children 25e66d77e1fe
files 1-Ruby/acts_as_csv_module-irb.output 1-Ruby/acts_as_csv_module-ruby.output 1-Ruby/acts_as_csv_module.rb
diffstat 3 files changed, 129 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/1-Ruby/acts_as_csv_module-irb.output	Tue Sep 05 20:24:03 2017 +0100
@@ -0,0 +1,93 @@
+acts_as_csv_module.rb(main):001:0> #---
+acts_as_csv_module.rb(main):002:0* # Excerpted from "Seven Languages in Seven Weeks",
+acts_as_csv_module.rb(main):003:0* # published by The Pragmatic Bookshelf.
+acts_as_csv_module.rb(main):004:0* # Copyrights apply to this code. It may not be used to create training material, 
+acts_as_csv_module.rb(main):005:0* # courses, books, articles, and the like. Contact us if you are in doubt.
+acts_as_csv_module.rb(main):006:0* # We make no guarantees that this code is fit for any purpose. 
+acts_as_csv_module.rb(main):007:0* # Visit http://www.pragmaticprogrammer.com/titles/btlang for more book information.
+acts_as_csv_module.rb(main):008:0* #---
+acts_as_csv_module.rb(main):009:0* module ActsAsCsv
+acts_as_csv_module.rb(main):010:1>   def self.included(base)
+acts_as_csv_module.rb(main):011:2>     base.extend ClassMethods
+acts_as_csv_module.rb(main):012:2>     # The book doesn't do this, but it is possible to add the methods
+acts_as_csv_module.rb(main):013:2*     # without having the inheriting class call a second function to do it
+acts_as_csv_module.rb(main):014:2*     base.acts_as_csv
+acts_as_csv_module.rb(main):015:2>   end
+acts_as_csv_module.rb(main):016:1>   
+acts_as_csv_module.rb(main):017:1*   module ClassMethods
+acts_as_csv_module.rb(main):018:2>     def acts_as_csv
+acts_as_csv_module.rb(main):019:3>       include InstanceMethods
+acts_as_csv_module.rb(main):020:3>     end
+acts_as_csv_module.rb(main):021:2>   end
+acts_as_csv_module.rb(main):022:1>   
+acts_as_csv_module.rb(main):023:1*   module InstanceMethods  
+acts_as_csv_module.rb(main):024:2> 
+acts_as_csv_module.rb(main):025:2*     def read
+acts_as_csv_module.rb(main):026:3>       self.csv_contents = []
+acts_as_csv_module.rb(main):027:3>       filename = self.class.to_s.downcase + '.txt'
+acts_as_csv_module.rb(main):028:3>       file = File.new(filename)
+acts_as_csv_module.rb(main):029:3>       @headers = file.gets.chomp.split(', ')
+acts_as_csv_module.rb(main):030:3> 
+acts_as_csv_module.rb(main):031:3*       file.each do |row|
+acts_as_csv_module.rb(main):032:4*         csv_row = CsvRow.new(headers, row.chomp.split(', '))
+acts_as_csv_module.rb(main):033:4>         @csv_contents << csv_row
+acts_as_csv_module.rb(main):034:4>       end
+acts_as_csv_module.rb(main):035:3>     end
+acts_as_csv_module.rb(main):036:2>     
+acts_as_csv_module.rb(main):037:2*     attr_accessor :headers, :csv_contents
+acts_as_csv_module.rb(main):038:2>     def initialize
+acts_as_csv_module.rb(main):039:3>       read
+acts_as_csv_module.rb(main):040:3>     end
+acts_as_csv_module.rb(main):041:2> 
+acts_as_csv_module.rb(main):042:2*     def each()
+acts_as_csv_module.rb(main):043:3>       self.csv_contents.each { |row| yield row }
+acts_as_csv_module.rb(main):044:3>     end
+acts_as_csv_module.rb(main):045:2>   end
+acts_as_csv_module.rb(main):046:1> end
+=> :each
+acts_as_csv_module.rb(main):047:0> 
+acts_as_csv_module.rb(main):048:0* class RubyCsv  # no inheritance! You can mix it in
+acts_as_csv_module.rb(main):049:1>   include ActsAsCsv
+acts_as_csv_module.rb(main):050:1>   # Also, nothing to do beyond the "include" - it automatically defines the methods!
+acts_as_csv_module.rb(main):051:1* end
+=> RubyCsv
+acts_as_csv_module.rb(main):052:0> 
+acts_as_csv_module.rb(main):053:0* class CsvRow
+acts_as_csv_module.rb(main):054:1>   attr_reader :values
+acts_as_csv_module.rb(main):055:1> 
+acts_as_csv_module.rb(main):056:1*   def initialize(headers, values)
+acts_as_csv_module.rb(main):057:2>     @values = Hash[headers.zip(values)]
+acts_as_csv_module.rb(main):058:2>   end
+acts_as_csv_module.rb(main):059:1> 
+acts_as_csv_module.rb(main):060:1*   def method_missing(methId, *args)
+acts_as_csv_module.rb(main):061:2>     str = methId.id2name
+acts_as_csv_module.rb(main):062:2>     if @values.key?(str)
+acts_as_csv_module.rb(main):063:3>       return @values[str]
+acts_as_csv_module.rb(main):064:3>     else
+acts_as_csv_module.rb(main):065:3*       super.method_missing(methId)
+acts_as_csv_module.rb(main):066:3>     end
+acts_as_csv_module.rb(main):067:2>   end
+acts_as_csv_module.rb(main):068:1> end
+=> :method_missing
+acts_as_csv_module.rb(main):069:0> 
+acts_as_csv_module.rb(main):070:0* m = RubyCsv.new
+=> #<RubyCsv:0x0000000212caf8 @csv_contents=[#<CsvRow:0x0000000212c558 @values={"first"=>"Yukihiro", "last"=>"Matsumoto", "country"=>"Japan"}>, #<CsvRow:0x0000000212c170 @values={"first"=>"Bruce", "last"=>"Tate", "country"=>"USA"}>], @headers=["first", "last", "country"]>
+acts_as_csv_module.rb(main):071:0> puts m.headers.inspect
+["first", "last", "country"]
+=> nil
+acts_as_csv_module.rb(main):072:0> puts m.csv_contents.inspect
+[#<CsvRow:0x0000000212c558 @values={"first"=>"Yukihiro", "last"=>"Matsumoto", "country"=>"Japan"}>, #<CsvRow:0x0000000212c170 @values={"first"=>"Bruce", "last"=>"Tate", "country"=>"USA"}>]
+=> nil
+acts_as_csv_module.rb(main):073:0> # Use method_missing to implement heading-as-a-method
+acts_as_csv_module.rb(main):074:0* puts m.csv_contents[0].country
+Japan
+=> nil
+acts_as_csv_module.rb(main):075:0> # And show that we still get normal failure behaviour
+acts_as_csv_module.rb(main):076:0* puts m.csv_contents[0].Country
+NoMethodError: undefined method `Country' for #<CsvRow:0x0000000212c558>
+	from acts_as_csv_module.rb:65:in `method_missing'
+	from acts_as_csv_module.rb:76
+	from /usr/bin/irb:11:in `<main>'
+acts_as_csv_module.rb(main):077:0> 
+acts_as_csv_module.rb(main):078:0* 
+acts_as_csv_module.rb(main):078:0> 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/1-Ruby/acts_as_csv_module-ruby.output	Tue Sep 05 20:24:03 2017 +0100
@@ -0,0 +1,5 @@
+acts_as_csv_module.rb:65:in `method_missing': undefined method `Country' for #<CsvRow:0x00000002342d10> (NoMethodError)
+	from acts_as_csv_module.rb:76:in `<main>'
+["first", "last", "country"]
+[#<CsvRow:0x00000002342d10 @values={"first"=>"Yukihiro", "last"=>"Matsumoto", "country"=>"Japan"}>, #<CsvRow:0x00000002342a68 @values={"first"=>"Bruce", "last"=>"Tate", "country"=>"USA"}>]
+Japan
--- a/1-Ruby/acts_as_csv_module.rb	Fri Jan 20 21:03:13 2017 +0000
+++ b/1-Ruby/acts_as_csv_module.rb	Tue Sep 05 20:24:03 2017 +0100
@@ -20,7 +20,7 @@
     end
   end
   
-  module InstanceMethods   
+  module InstanceMethods  
     def read
       @csv_contents = []
       filename = self.class.to_s.downcase + '.txt'
@@ -28,13 +28,18 @@
       @headers = file.gets.chomp.split(', ')
 
       file.each do |row|
-        @csv_contents << row.chomp.split(', ')
+        csv_row = CsvRow.new(headers, row.chomp.split(', '))
+        @csv_contents << csv_row
       end
     end
     
     attr_accessor :headers, :csv_contents
     def initialize
-      read 
+      read
+    end
+
+    def each(&block)
+      @csv_contents.each { |row| block.call row }
     end
   end
 end
@@ -44,7 +49,30 @@
   # Also, nothing to do beyond the "include" - it automatically defines the methods!
 end
 
+class CsvRow
+  attr_reader :values
+
+  def initialize(headers, values)
+    @values = Hash[headers.zip(values)]
+  end
+
+  def method_missing(methId, *args)
+    str = methId.id2name
+    if @values.key?(str)
+      return @values[str]
+    else
+      super.method_missing(methId)
+    end
+  end
+end
+
 m = RubyCsv.new
 puts m.headers.inspect
 puts m.csv_contents.inspect
+# Show the "each" method
+m.each { |row| puts row.inspect }
+# Use method_missing to implement heading-as-a-method
+puts m.csv_contents[0].country
+# And show that we still get normal failure behaviour
+puts m.csv_contents[0].Country