Class: Importer::DataReader

Inherits:
Object
  • Object
show all
Defined in:
lib/iron/import/data_reader.rb

Overview

Base class for our input reading - dealing with the raw file/stream, and extracting raw values. In addition, we provide the base data coercion/parsing for our derived classes.

Direct Known Subclasses

CsvReader, XlsReader, XlsxReader

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(importer, format) ⇒ DataReader

Returns a new instance of DataReader.



60
61
62
63
64
# File 'lib/iron/import/data_reader.rb', line 60

def initialize(importer, format)
  @importer = importer
  @format = format
  @multisheet = true
end

Instance Attribute Details

#formatObject (readonly)

Attributes



9
10
11
# File 'lib/iron/import/data_reader.rb', line 9

def format
  @format
end

Class Method Details

.for_format(importer, format) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/iron/import/data_reader.rb', line 17

def self.for_format(importer, format)
  case format
  when :csv
    CsvReader.new(importer)
  when :xls
    verify_roo!
    XlsReader.new(importer)
  when :xlsx
    verify_roo!
    XlsxReader.new(importer)
  else
    nil
  end
end

.for_path(importer, path) ⇒ Object



32
33
34
35
36
37
38
39
40
# File 'lib/iron/import/data_reader.rb', line 32

def self.for_path(importer, path)
  format = path.to_s.extract(/\.(csv|xlsx?)\z/i)
  if format
    format = format.downcase.to_sym
    for_format(importer, format)
  else
    nil
  end
end

.for_stream(importer, stream) ⇒ Object



42
43
44
45
# File 'lib/iron/import/data_reader.rb', line 42

def self.for_stream(importer, stream)
  path = path_from_stream(stream)
  for_path(importer, path)
end

.path_from_stream(stream) ⇒ Object

Try to find the original file name for the given stream, as in the case where a file is uploaded to Rails and we’re dealing with an ActionDispatch::Http::UploadedFile.



50
51
52
53
54
55
56
57
58
# File 'lib/iron/import/data_reader.rb', line 50

def self.path_from_stream(stream)
  if stream.respond_to?(:original_filename)
    stream.original_filename
  elsif stream.respond_to?(:path)
    stream.path
  else
    nil
  end
end

.verify_roo!Object



11
12
13
14
15
# File 'lib/iron/import/data_reader.rb', line 11

def self.verify_roo!
  if Gem::Specification.find_all_by_name('roo', '~> 1.13.0').empty?
    raise "You are attempting to use the iron-import gem to import an Excel file.  Doing so requires installing the roo gem, version 1.13.0 or later."
  end
end

Instance Method Details

#load(path_or_stream) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/iron/import/data_reader.rb', line 66

def load(path_or_stream)
  # Figure out what we've been passed, and handle it
  if path_or_stream.respond_to?(:read)
    # We have a stream (open file, upload, whatever)
    if respond_to?(:load_stream)
      # Stream loader defined, run it
      load_stream(path_or_stream)
    else
      # Write to temp file, as some of our readers only read physical files, annoyingly
      file = Tempfile.new(['importer', ".#{format}"])
      file.binmode
      begin
        file.write path_or_stream.read
        file.close
        load_file(file.path)
      ensure
        file.close
        file.unlink
      end
    end
    
  elsif path_or_stream.is_a?(String)
    # Assume it's a path
    if respond_to?(:load_file)
      # We're all set, load up the given path
      load_file(path_or_stream)
    else
      # No file handler, so open the file and run the stream processor
      file = File.open(path_or_stream, 'rb')
      load_stream(file)
    end
    
  else
    raise "Unable to load data: #{path_or_stream.inspect}"
  end
  
  # Return our status
  !@importer.has_errors?
end

#parse_value(val, type) ⇒ Object

Provides default value parsing/coersion for all derived data readers. Attempts to be clever and handle edge cases like converting ‘5.00’ to 5 when in integer mode, etc. If you find your inputs aren’t being parsed correctly, add a custom #parse block on your Column definition.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/iron/import/data_reader.rb', line 109

def parse_value(val, type)
  return nil if val.nil? || val.to_s == ''
  
  case type
  when :string then
    val = val.to_s.strip
    val.blank? ? nil : val
    
  when :integer, :int then 
    if val.class < Numeric
      # If numeric, verify that there's no decimal places to worry about
      if (val.to_f % 1.0 == 0.0)
        val.to_i
      else
        nil
      end
    else 
      # Convert to string, strip off trailing decimal zeros
      val = val.to_s.strip.gsub(/\.0*$/, '')
      if val.integer?
        val.to_i
      else
        nil
      end
    end
    
  when :float then
    if val.class < Numeric
      val.to_f
    else 
      # Convert to string, strip off trailing decimal zeros
      val = val.to_s.strip
      if val.match(/\A-?[0-9]+(?:\.[0-9]+)?\z/)
        val.to_f
      else
        nil
      end
    end
    
  when :cents then
    if val.is_a?(String)
      val = val.gsub(/\s*\$\s*/, '')
    end
    intval = parse_value(val, :integer)
    if !val.is_a?(Float) && intval
      intval * 100
    else
      floatval = parse_value(val, :float)
      if floatval
        (floatval * 100).to_i
      else
        nil
      end
    end
    
  when :date then
    # Pull out the date part of the string and convert
    date_str = val.to_s.extract(/[0-9]+[\-\/][0-9]+[\-\/][0-9]+/)
    date_str.to_date rescue nil
    
  else
    raise "Unknown column type #{type.inspect} - unimplemented?"
  end
end