Class: CsvMadness::Sheet

Inherits:
Object
  • Object
show all
Defined in:
lib/csv_madness/sheet.rb

Constant Summary collapse

COLUMN_TYPES =
{
  number: Proc.new do |cell, record|
    if (cell || "").strip.match(/^\d*$/)
      cell.to_i
    else
      cell.to_f
    end
  end,
  
  integer: Proc.new do |cell, record|
    cell.to_i
  end,
  
  float:   Proc.new do |cell, record|
    cell.to_f
  end,
  
  date:    Proc.new do |cell, record|
    begin
      parse = Time.parse( cell )
    rescue ArgumentError
      parse = "Invalid Time Format: <#{cell}>"
    end
    parse
  end
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(spreadsheet, opts = {}) ⇒ Sheet

opts:

index: ( [:id, :id2 ] )
    columns you want mapped for quick 
    lookup of individual records

columns: ( [:fname, :lname, :age] )  
    an array of symbols, corresponding
    to the csv rows they represent (first, second, third)
    and designating the method for calling the cell in 
    a given record.  If not provided, it will guess based
    on the header row.

header:   false       
    anything else, we assume the csv file has a header row


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/csv_madness/sheet.rb', line 117

def initialize( spreadsheet, opts = {} )
  @spreadsheet_file = self.class.find_spreadsheet_in_filesystem( spreadsheet )
  @opts = opts
  @opts[:header] = (@opts[:header] == false ? false : true)  # true unless already explicitly set to false
  
  load_csv
  
  set_initial_columns( @opts[:columns] )
  

  create_record_class
  package
  
  @index_columns = case @opts[:index]
  when NilClass
    []
  when Symbol
    [ @opts[:index] ]
  when Array
    @opts[:index]
  end
    
  reindex
end

Instance Attribute Details

#columnsObject (readonly)

Returns the value of attribute columns.



102
103
104
# File 'lib/csv_madness/sheet.rb', line 102

def columns
  @columns
end

#record_classObject (readonly)

Returns the value of attribute record_class.



102
103
104
# File 'lib/csv_madness/sheet.rb', line 102

def record_class
  @record_class
end

#recordsObject (readonly)

Returns the value of attribute records.



102
103
104
# File 'lib/csv_madness/sheet.rb', line 102

def records
  @records
end

#spreadsheet_fileObject (readonly)

Returns the value of attribute spreadsheet_file.



102
103
104
# File 'lib/csv_madness/sheet.rb', line 102

def spreadsheet_file
  @spreadsheet_file
end

Class Method Details

.add_search_path(path) ⇒ Object

Paths to be searched when CsvMadness.load( “filename.csv” ) is called.



43
44
45
46
47
# File 'lib/csv_madness/sheet.rb', line 43

def self.add_search_path( path )
  @search_paths ||= []
  path = Pathname.new( path ).expand_path
  @search_paths << path unless @search_paths.include?( path )
end

.find_spreadsheet_in_filesystem(name) ⇒ Object

Search absolute/relative-to-current-dir before checking search paths.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/csv_madness/sheet.rb', line 59

def self.find_spreadsheet_in_filesystem( name )
  @search_paths ||= []

  expanded_path = Pathname.new( name ).expand_path
  if expanded_path.exist?
    return expanded_path
  else   # look for it in the search paths
    @search_paths.each do |p|
      file = p.join( name )
      if file.exist? && file.file?
        return p.join( name )
      end
    end
  end
  
  nil
end

.from(csv_file, opts = {}) ⇒ Object



49
50
51
52
53
54
55
# File 'lib/csv_madness/sheet.rb', line 49

def self.from( csv_file, opts = {} )
  if f = find_spreadsheet_in_filesystem( csv_file )
    Sheet.new( f, opts )
  else
    raise "File not found."
  end
end

.getter_name(name) ⇒ Object

Used to make getter/setter names out of the original header strings. “ hello;: world! ” => :hello_world



32
33
34
35
36
37
38
39
# File 'lib/csv_madness/sheet.rb', line 32

def self.getter_name( name )
  name = name.strip.gsub(/\s+/,"_").gsub(/(\W|_)+/, "" ).downcase
  if name.match( /^\d/ )
    name = "_#{name}"
  end
  
  name.to_sym
end

.to_csv(spreadsheet, opts = {}) ⇒ Object

opts are passed to underlying CSV (:row_sep, :encoding, :force_quotes)



78
79
80
81
82
83
# File 'lib/csv_madness/sheet.rb', line 78

def self.to_csv( spreadsheet, opts = {} )
  out = spreadsheet.columns.to_csv( opts )
  spreadsheet.records.inject( out ) do |output, record|
    output << record.to_csv( opts )
  end
end

.write_to_file(spreadsheet, file, opts = {}) ⇒ Object



85
86
87
88
89
90
# File 'lib/csv_madness/sheet.rb', line 85

def self.write_to_file( spreadsheet, file, opts = {} )
  file = Pathname.new(file).expand_path
  File.open( file, "w" ) do |f|
    f << spreadsheet.to_csv( opts )
  end
end

Instance Method Details

#[](offset) ⇒ Object



142
143
144
# File 'lib/csv_madness/sheet.rb', line 142

def [] offset
  @records[offset]
end

#add_column(column, &block) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/csv_madness/sheet.rb', line 212

def add_column( column, &block )
  raise "Column already exists" if @columns.include?( column )
  @columns << column
  
  # add empty column to each row
  @records.map{ |r|
    r.csv_data << {column => ""} 
  }
  
  update_data_accessor_module
  
  if block_given?
    alter_column( column ) do |val, record|
      yield val, record
    end
  end
end

#add_record_methods(mod = nil, &block) ⇒ Object

Note: If a block is given, the mod arg will be ignored.



249
250
251
252
253
254
255
# File 'lib/csv_madness/sheet.rb', line 249

def add_record_methods( mod = nil, &block )
  if block_given?
    mod = Module.new( &block )
  end
  @record_class.send( :include, mod )
  self
end

#alter_cells(blank = :undefined, &block) ⇒ Object

if blank is defined, only the records which are non-blank in that column will actually be yielded. The rest will be set to the provided default



193
194
195
196
197
# File 'lib/csv_madness/sheet.rb', line 193

def alter_cells( blank = :undefined, &block )
  @columns.each_with_index do |column, cindex|
    alter_column( column, blank, &block )
  end
end

#alter_column(column, blank = :undefined, &block) ⇒ Object

if column doesn’t exist, silently fails. Proper behavior? Dunno.



200
201
202
203
204
205
206
207
208
209
210
# File 'lib/csv_madness/sheet.rb', line 200

def alter_column( column, blank = :undefined, &block )
  if cindex = @columns.index( column )
    for record in @records
      if record.blank?(column) && blank != :undefined
        record[cindex] = blank
      else
        record[cindex] = yield( record[cindex], record )
      end
    end
  end
end

#column(col) ⇒ Object



177
178
179
# File 'lib/csv_madness/sheet.rb', line 177

def column col
  @records.map(&col)
end

#drop_column(column) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/csv_madness/sheet.rb', line 230

def drop_column( column )
  raise "Column does not exist" unless @columns.include?( column )
  
  @columns.delete( column )
  
  key = column.to_s
  
  @records.map{ |r|
    r.csv_data.delete( key )
  }
  
  update_data_accessor_module
end

#fetch(index_col, key) ⇒ Object

Fetches an indexed record based on the column indexed and the keying object. If key is an array of keying objects, returns an array of records in the same order that the keying objects appear. Index column should yield a different, unique value for each record.



150
151
152
153
154
155
156
# File 'lib/csv_madness/sheet.rb', line 150

def fetch( index_col, key )
  if key.is_a?(Array)
    key.map{ |key| @indexes[index_col][key] }
  else
    @indexes[index_col][key]
  end
end

#filter(&block) ⇒ Object

function should take an object, and return either true or false returns an array of objects that respond true when put through the meat grinder



161
162
163
164
165
166
167
168
# File 'lib/csv_madness/sheet.rb', line 161

def filter( &block )
  rval = []
  @records.each do |record|
    rval << record if ( yield record )
  end

  rval
end

#filter!(&block) ⇒ Object

removes rows which fail the given test from the spreadsheet.



171
172
173
174
175
# File 'lib/csv_madness/sheet.rb', line 171

def filter!( &block )
  @records = self.filter( &block )
  reindex
  @records
end

#multiple_columns(*args) ⇒ Object

retrieve multiple columns. Returns an array of the form

[record1:col1, record1:col2…], [record2:col1, record2:col2…

]



183
184
185
186
187
188
# File 'lib/csv_madness/sheet.rb', line 183

def multiple_columns(*args)
  @records.inject([]){ |memo, record|
    memo << args.map{ |arg| record.send(arg) }
    memo
  }
end

#nils_are_blank_stringsObject

Note: If implementation of Record[] changes, so must this.



258
259
260
261
262
# File 'lib/csv_madness/sheet.rb', line 258

def nils_are_blank_strings
  alter_cells do |value, record|
    value.nil? ? "" : value
  end
end

#set_column_type(column, type, blank = :undefined) ⇒ Object



244
245
246
# File 'lib/csv_madness/sheet.rb', line 244

def set_column_type( column, type, blank = :undefined )
  alter_column( column, blank, &COLUMN_TYPES[type] )
end

#to_csv(opts = {}) ⇒ Object



96
97
98
99
100
# File 'lib/csv_madness/sheet.rb', line 96

def to_csv( opts = {} )
  self.records.inject( self.columns.to_csv( opts ) ) do |output, record|
    output << record.to_csv( opts )
  end
end

#write_to_file(file, opts = {}) ⇒ Object



92
93
94
# File 'lib/csv_madness/sheet.rb', line 92

def write_to_file( file, opts = {} )
  self.class.write_to_file( self, file, opts )
end