class Importer::Column
Columns represent the settings for importing a given column within a Sheet. They do not hold data, rather they capture the settings needed for identifying the column in the header, how to parse and validate each of their cell's data, and so forth.
Here's the complete list of column configuration options:
Importer.build do column :key do # Mark this column as optional, i.e. if the header isn't found, the import will # work without error and the imported row will simply not contain this column's data. optional! # Set a fixed position - may be a column number or a letter-based # column description, ie 'A' == 1. In most cases, you can leave # this defaulted to nil, which will mean "look for the proper header" position 'C' # Specify a regex to locate the header for this column, defaults to # finding a string containing the key, ignored if position is set. header /(price|cost)/i # Tells the data parser what type of data this column contains, one # of :integer, :string, :date, :float, :bool or :cents. Defaults to :string. type :cents # Instead of a type, you can set an explicit parse block. Be aware # that different source types may give you different raw values for what # seems like the "same" source value, for example an Excel source file # will give you a float value for all numeric types, even "integers", while # CSV and HTML values are always strings. By default, will take the raw # value of the row, but if used with #type, you can have the pre-processed # output of that type as your input. parse do |raw_value| val = raw_value.to_i + 1000 # NOTE: we're in a block, so don't do this: return val # Instead, use implied return: val end # You can also add a custom validator to check the value and add # an error if it's not within a given range, or whatever. To fail validation, # return false, raise an exception, or use #add_error validate do |parsed_value, row| add_error "Out of range" unless (parsed_value > 0 && parsed_value < 5000) end # Mark a column as _virtual_, meaning it won't be looked for in the source # file/stream, and instead will be calculated using #calculate. When set, # causes importer to ignore position/header/type/parse settings. virtual! # When #virtual! is set, gets called to calculate each row's value for this # column using the row's parsed values from other columns. calculate do |row| row[:other_col_key] + 5 end end end
Attributes
Core info
Public Class Methods
Convert a numeric index to an Excel-like column position, e.g. 3 => 'C'
# File lib/iron/import/column.rb, line 103 def self.index_to_pos(index) val = index.to_i raise 'Invalid column index: ' + index.inspect if (!index.is_a?(Fixnum) || index.to_i < 0) chars = ('A'..'Z').to_a str = '' while index > 25 str = chars[index % 26] + str index /= 26 index -= 1 end str = chars[index] + str str end
Create a new column definition with the key for the column, and an optional set of options. The options supported are the same as those supported in block/builder mode.
# File lib/iron/import/column.rb, line 121 def initialize(importer, key, options_hash = {}) # Save off our info @key = key @importer = importer # Are we optional? @optional = options_hash.delete(:optional) { false } # Are we virtual? @virtual = options_hash.delete(:virtual) { false } # Return it as a string, by default @type = options_hash.delete(:type) # Position can be explicitly set @position = options_hash.delete(:position) # By default, don't parse incoming data, just pass it through @parse = options_hash.delete(:parse) # Custom validation, anyone? @validate = options_hash.delete(:validate) # Custom validation, anyone? @calculate = options_hash.delete(:calculate) # Default matcher, looks for the presence of the column key as text anywhere # in the header string, ignoring case and treating underscores as spaces, ie # :order_id => /\A\s*order id\s*\z/i @header = options_hash.delete(:header) { Regexp.new('\A\s*' + key.to_s.gsub('_', ' ') + '\s*\z', Regexp::IGNORECASE) } # Reset our state to pre-load status reset end
# File lib/iron/import/column.rb, line 90 def self.pos_to_index(pos) raise 'Invalid column position: ' + pos.inspect unless pos.is_a?(String) && pos.match(/\A[a-z]{1,3}\z/i) vals = pos.upcase.bytes.collect {|b| b - 64} total = 0 multiplier = 1 vals.reverse.each do |val| total += val * multiplier multiplier *= 26 end total - 1 end
Public Instance Methods
Customize ourselves using block syntax
# File lib/iron/import/column.rb, line 159 def build(&block) DslProxy.exec(self, &block) end
# File lib/iron/import/column.rb, line 222 def calculate_value(row) return nil if @calculate.nil? res = nil had_error = Error.with_context(@importer, row, self, nil) do res = DslProxy.exec(@importer, row, &@calculate) end had_error ? nil : res end
# File lib/iron/import/column.rb, line 277 def calculates? !@calculate.nil? end
# File lib/iron/import/column.rb, line 285 def error_values errors.collect(&:value).uniq end
# File lib/iron/import/column.rb, line 289 def error_values? error_values.any? end
# File lib/iron/import/column.rb, line 281 def errors @data.errors end
Returns the fixed index of this column based on the set position. In other words, a position of 2 would return an index of 1 (as indicies are 0-based), where a position of 'C' would return 2.
# File lib/iron/import/column.rb, line 182 def fixed_index return nil if virtual? return nil unless @position if @position.is_a?(Fixnum) @position - 1 elsif @position.is_a?(String) Column.pos_to_index(@position) end end
Index of the column in the most recent import, if found, or nil if not present.
# File lib/iron/import/column.rb, line 251 def index @data.index end
Override normal dsl_accessor behavior to return our default type which will be :raw if a parse handler has been set, else :string
When true, our header definition or index match the passed text or column index.
# File lib/iron/import/column.rb, line 169 def match_header?(text, test_index) return false if virtual? return true if test_index == self.fixed_index if @header.is_a?(Regexp) return !@header.match(text).nil? else return @header.to_s.downcase == text end end
Sugar, simply the opposite of present?
# File lib/iron/import/column.rb, line 265 def missing? !present? end
Applies any custom parser defined to process the given value, capturing errors as needed
# File lib/iron/import/column.rb, line 212 def parse_value(row, raw_val) return raw_val if @parse.nil? res = nil had_error = Error.with_context(@importer, row, self, raw_val) do res = DslProxy.exec(@importer, raw_val, &@parse) end had_error ? nil : res end
# File lib/iron/import/column.rb, line 269 def parses? !@parse.nil? end
When true, column was found in the last import, eg:
importer.process do |row| puts "Size: #{row[:size]}" if column(:size).present? end
# File lib/iron/import/column.rb, line 260 def present? !@data.index.nil? end
Deletes all stored data in prep for an import run
# File lib/iron/import/column.rb, line 164 def reset @data = Data.new end
Extracts the imported values for this column and returns them in an array. Note that the array indices ARE NOT row indices, as the rows may have been filtered and any header rows have been skipped.
# File lib/iron/import/column.rb, line 307 def to_a @importer.data.rows.collect {|r| r[@key] } end
Extracts the values for this column and returns them in a hash of row num => value for all non-filtered, non-header rows.
# File lib/iron/import/column.rb, line 313 def to_h res = {} @importer.data.rows.collect {|r| res[r.num] = r[@key] } res end
# File lib/iron/import/column.rb, line 318 def to_hash ; to_h ; end
Pretty name for ourselves
# File lib/iron/import/column.rb, line 294 def to_s if !virtual? && @data.header_text.blank? "Column #{@data.pos}" else name = virtual? ? key.to_s : @data.header_text name = name.gsub(/(^[a-z]|\s[a-z])/) {|m| m.capitalize } "#{name} Column" end end
# File lib/iron/import/column.rb, line 195 def type(*args) if args.count > 0 internal_type(*args) else if @type # Explicitly set type @type else # Our default is generally :string, but if we have a parser, # default to the :raw value parses? ? :raw : :string end end end
Applies any validation to a parsed value
# File lib/iron/import/column.rb, line 232 def validate_value(row, parsed_val) return true unless @validate valid = false had_error = Error.with_context(@importer, row, self, parsed_val) do valid = DslProxy.exec(@importer, parsed_val, row, &@validate) end if had_error return false elsif valid.is_a?(FalseClass) @importer.add_error("Invalid value: #{parsed_val.inspect}", :row => row, :column => self, :value => parsed_val) return false else return true end end
# File lib/iron/import/column.rb, line 273 def validates? !@validate.nil? end