class Cucumber::MultilineArgument::DataTable
This will store [['a', 'b'], ['c', 'd']]
in the data
variable.
end
data = table.raw
Given /I have:/ do |table|
And a matching StepDefinition:
| c | d |
| a | b |
Given I have:
For example:
in different ways.
table parsed from a feature file and lets you access and manipulate the data
will receive it as an instance of Table. A Table object holds the data of a
Step Definitions that match a plain text Step with a multiline argument table
def self.default_arg_name
def self.default_arg_name 'table' end
def append_to(array)
def append_to(array) array << self end
def build_hashes
def build_hashes convert_headers! convert_columns! cells_rows[1..].map(&:to_hash) end
def cells_rows
def cells_rows @rows ||= cell_matrix.map do |cell_row| Cells.new(self, cell_row) end end
def cells_to_hash(cells)
def cells_to_hash(cells) hash = Hash.new do |hash_inner, key| hash_inner[key.to_s] if key.is_a?(Symbol) end column_names.each_with_index do |column_name, column_index| hash[column_name] = cells.value(column_index) end hash end
def clear_cache!
def clear_cache! @hashes = @rows_hash = @column_names = @rows = @columns = nil end
def col_width(col)
def col_width(col) columns[col].__send__(:width) end
def column_names
def column_names @column_names ||= cell_matrix[0].map(&:value) end
def columns
def columns @columns ||= cell_matrix.transpose.map do |cell_row| Cells.new(self, cell_row) end end
def convert_columns!
def convert_columns! @conversion_procs.each do |column_name, conversion_proc| verify_column(column_name) if conversion_proc[:strict] end cell_matrix.transpose.each do |col| column_name = col[0].value conversion_proc = @conversion_procs[column_name][:proc] col[1..].each do |cell| cell.value = conversion_proc.call(cell.value) end end end
def convert_headers!
def convert_headers! header_cells = cell_matrix[0] if @header_conversion_proc header_values = header_cells.map(&:value) - @header_mappings.keys @header_mappings = @header_mappings.merge(Hash[*header_values.zip(header_values.map(&@header_conversion_proc)).flatten]) end @header_mappings.each_pair do |pre, post| mapped_cells = header_cells.select { |cell| pre.is_a?(Regexp) ? cell.value.match?(pre) : cell.value == pre } raise "No headers matched #{pre.inspect}" if mapped_cells.empty? raise "#{mapped_cells.length} headers matched #{pre.inspect}: #{mapped_cells.map(&:value).inspect}" if mapped_cells.length > 1 mapped_cells[0].value = post @conversion_procs[post] = @conversion_procs.delete(pre) if @conversion_procs.key?(pre) end end
def create_cell_matrix(ast_table)
def create_cell_matrix(ast_table) ast_table.raw.map do |raw_row| line = begin raw_row.line rescue StandardError -1 end raw_row.map do |raw_cell| Cell.new(raw_cell, self, line) end end end
def describe_to(visitor, *args)
def describe_to(visitor, *args) visitor.legacy_table(self, *args) end
def diff!(other_table, options = {})
a Table argument, if you want to compare that table to some actual values.
Calling this method is particularly useful in Then steps that take
an Array of Hash (similar to the structure returned by #hashes).
The +other_table+ argument can be another Table, an Array of Array or
* misplaced_col : Raise on misplaced columns (defaults to false)
* surplus_col : Raise on surplus columns (defaults to false)
* missing_col : Raise on missing columns (defaults to true)
* surplus_row : Raise on surplus rows (defaults to true)
* missing_row : Raise on missing rows (defaults to true)
+options+ to true or false:
Whether to raise or not raise can be changed by setting values in
error is not raised for misplaced (out of sequence) columns.
surplus rows. An error is not raised for surplus columns. An
A Different error is raised if there are missing rows or columns, or
#diff!. You can use #map_column on either of the tables.
objects in their cells, you may want to use #map_column before calling
Since all tables that are passed to StepDefinitions always have String
where the difference actually is.
representation and preceded with (i) - to make it easier to identify
boolean true and the string "true") are converted to their Object#inspect
Cells that are different, but look identical (for example the
and displayed so that it's easy to read the differences.
surplus and missing cells are recognised by formatters
rows that are present in self, these are marked as missing.
surplus. Likewise, if +other_table+ lacks columns and/or
relevant positions, marking the cells in those rows/columns as
and/or rows that are not in self, new columns/rows are added at the
Compares +other_table+ to self. If +other_table+ contains columns
def diff!(other_table, options = {}) other_table = ensure_table(other_table) other_table.convert_headers! other_table.convert_columns! convert_headers! convert_columns! DiffMatrices.new(cell_matrix, other_table.cell_matrix, options).call end
def each_cells_row(&proc)
def each_cells_row(&proc) cells_rows.each(&proc) end
def ensure_table(table_or_array)
def ensure_table(table_or_array) return table_or_array if table_or_array.instance_of?(DataTable) DataTable.from(table_or_array) end
def from(data)
def from(data) case data when Array from_array(data) when String parse(data) else raise ArgumentError, 'expected data to be a String or an Array.' end end
def from_array(data)
def from_array(data) new Core::Test::DataTable.new(data) end
def hashes
Use #map_column to specify how values in a column are converted.
[{'a' => '2', 'b' => '3', 'sum' => '5'}, {'a' => '7', 'b' => '9', 'sum' => '16'}]
Gets converted into the following:
| 7 | 9 | 16 |
| 2 | 3 | 5 |
| a | b | sum |
the following plain text:
Hash are the headers in the table. For example, a Table built from
Converts this table into an Array of Hash where the keys of each
def hashes @hashes ||= build_hashes end
def header_cell(col)
def header_cell(col) cells_rows[0][col] end
def headers
def headers raw.first end
def index(cells)
def index(cells) cells_rows.index(cells) end
def initialize(data, conversion_procs = NULL_CONVERSIONS.dup, header_mappings = {}, header_conversion_proc = nil)
-
header_conversion_proc
(Proc
) -- see map_headers -
header_mappings
(Hash
) -- see map_headers -
conversion_procs
(Hash
) -- see map_column -
data
(Core::Test::DataTable
) -- the data for the table
def initialize(data, conversion_procs = NULL_CONVERSIONS.dup, header_mappings = {}, header_conversion_proc = nil) raise ArgumentError, 'data must be a Core::Test::DataTable' unless data.is_a? Core::Test::DataTable ast_table = data # Verify that it's square ast_table.transpose @cell_matrix = create_cell_matrix(ast_table) @conversion_procs = conversion_procs @header_mappings = header_mappings @header_conversion_proc = header_conversion_proc @ast_table = ast_table end
def location
def location @ast_table.location end
def map_column(column_name, strict: true, &conversion_proc)
end
end
# post['amount'] is a Fixnum, rather than a String
posts_table.hashes.each do |post|
posts_table = posts_table.map_column('amount') { |a| a.to_i }
Given /^an expense report for (.*) with the following posts:$/ do |table|
is false, no error will be raised. Example:
true, an error will be raised if the column named +column_name+ is not found. If +strict+
and +conversion_proc+ performs the conversion for each cell in that column. If +strict+ is
Change how #hashes converts column values. The +column_name+ argument identifies the column
Returns a new Table with an additional column mapping.
def map_column(column_name, strict: true, &conversion_proc) conversion_procs = @conversion_procs.dup conversion_procs[column_name.to_s] = { strict: strict, proc: conversion_proc } self.class.new(Core::Test::DataTable.new(raw), conversion_procs, @header_mappings.dup, @header_conversion_proc) end
def map_headers(mappings = {}, &block)
# => ['phone number', 'ADDRESS']
table.hashes.keys
table.map_headers('Address' => 'ADDRESS') { |header| header.downcase }
When a block is passed in along with a hash then the mappings in the hash take precendence:
# => ['phone number', 'address']
table.hashes.keys
table.map_headers { |header| header.downcase }
You may also pass in a block if you wish to convert all of the headers:
# => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
table.hashes
table.map_headers(/phone( number)?/i => :phone, 'Address' => :address)
with both Regexp and String:
A StepDefinition receiving this table can then map the columns
| 345678 | abc |
| 123456 | xyz |
| Phone Number | Address |
Example:
desired names for the columns.
column headings in the table. The values of +mappings+ are
(anything that responds to #=== will work) that may match
keys of +mappings+ are Strings or regular expressions
prettier and more flexible header names in the features. The
This makes it possible to use
Returns a new Table where the headers are redefined.
def map_headers(mappings = {}, &block) self.class.new(Core::Test::DataTable.new(raw), @conversion_procs.dup, mappings, block) end
def match(pattern)
table.match(/table:column_1_name,column_2_name/) #=> non-nil
| x | y |
| column_1_name | column_2_name |
Example:
This is used especially for argument transforms.
Matches +pattern+ against the header row of the table.
def match(pattern) header_to_match = "table:#{headers.join(',')}" pattern.match(header_to_match) end
def parse(text)
def parse(text) builder = Builder.new parser = Cucumber::Gherkin::DataTableParser.new(builder) parser.parse(text) from_array(builder.rows) end
def raw
[['a', 'b'], ['c', 'd']]
gets converted into the following:
| c | d |
| a | b |
the following plain text:
Gets the raw data of this table. For example, a Table built from
def raw cell_matrix.map do |row| row.map(&:value) end end
def rows
def rows hashes.map do |hash| hash.values_at(*headers) end end
def rows_hash
The table must be exactly two columns wide
{'a' => '2', 'b' => '3'}
Gets converted into the following:
| b | 3 |
| a | 2 |
used as keys and the second column is used as values
Converts this table into a Hash where the first column is
def rows_hash return @rows_hash if @rows_hash verify_table_width(2) @rows_hash = transpose.hashes[0] end
def symbolic_hashes
[{:foo => '2', :bar => '3', :foo_bar => '5'}, {:foo => '7', :bar => '9', :foo_bar => '16'}]
Gets converted into the following:
| 7 | 9 | 16 |
| 2 | 3 | 5 |
| foo | Bar | Foo Bar |
For example, a Table built from the following plain text:
Converts this table into an Array of Hashes where the keys are symbols.
def symbolic_hashes @symbolic_hashes ||= hashes.map do |string_hash| string_hash.transform_keys { |a| symbolize_key(a) } end end
def symbolize_key(key)
def symbolize_key(key) key.downcase.tr(' ', '_').to_sym end
def text?(text)
TODO: remove the below function if it's not actually being used.
def text?(text) Cucumber.deprecate( 'This method is no longer supported for checking text', '#text?', '11.0.0' ) raw.flatten.compact.detect { |cell_value| cell_value.index(text) } end
def to_hash
def to_hash cells_rows.map { |cells| cells.map(&:value) } end
def to_json(*args)
def to_json(*args) raw.to_json(*args) end
def to_s(options = {})
def to_s(options = {}) indentation = options.key?(:indent) ? options[:indent] : 2 prefixes = options.key?(:prefixes) ? options[:prefixes] : TO_S_PREFIXES DataTablePrinter.new(self, indentation, prefixes).to_s end
def to_step_definition_arg
def to_step_definition_arg dup end
def transpose
| 4 | 2 |
| 7 | 9 |
| a | b |
Gets converted into the following:
| b | 9 | 2 |
| a | 7 | 4 |
Returns a new, transposed table. Example:
def transpose self.class.new(Core::Test::DataTable.new(raw.transpose), @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc) end
def verify_column(column_name)
def verify_column(column_name) raise %(The column named "#{column_name}" does not exist) unless raw[0].include?(column_name) end
def verify_table_width(width)
def verify_table_width(width) raise %(The table must have exactly #{width} columns) unless raw[0].size == width end