TableFu is a ruby gem for spreadsheet-style handling of arrays (e.g. filtering, formatting, and sorting by "column" or "row"). In addition, it has the ability to facet — or group — rows according to cell value. It was developed as a backend for its companion project TableSetter.
For example, TableFu can consume a csv file and sort on a column:
csv =<<-CSV Author,Best Book,Number of Pages,Style Samuel Beckett,Malone Muert,120,Modernism James Joyce,Ulysses,644,Modernism Nicholson Baker,Mezannine,150,Minimalism Vladimir Sorokin,The Queue,263,Satire CSV spreadsheet = TableFu.new(csv) do |s| s.sorted_by = {'Best Book' => {'order' => 'ascending'}} endReturns:
[["Samuel Beckett", "Malone Muert", "120", "Modernism"], ["Nicholson Baker", "Mezannine", "150", "Minimalism"], ["Vladimir Sorokin", "The Queue", "263", "Satire"], ["James Joyce", "Ulysses", "644", "Modernism"]]
TableFu is available as a rubygem:
gem install table_fuor from the actual source:
git clone git://github.com/propublica/table-fu.git cd table-fu rake install
The TableFu constructor takes two arguments; a 2 dimensional array or csv (file object or string) and a hash of column options or a block. TableFu will assume that the first row of the array contains the column headers. The simple options are:
sorted_by: the column to sort by, it defaults to no sorting at all.
csv =<<-CSV Author,Best Book,Number of Pages,Style Samuel Beckett,Malone Muert,120,Modernism James Joyce,Ulysses,644,Modernism Nicholson Baker,Mezannine,150,Minimalism Vladimir Sorokin,The Queue,263,Satire CSV spreadsheet = TableFu.new(csv) do |s| s.sorted_by = {'Best Book' => {'order' => 'ascending'}} endReturns:
[["Samuel Beckett", "Malone Muert", "120", "Modernism"], ["Nicholson Baker", "Mezannine", "150", "Minimalism"], ["Vladimir Sorokin", "The Queue", "263", "Satire"], ["James Joyce", "Ulysses", "644", "Modernism"]]
columns: the columns to include in the table, useful when reordering and filtering extraneous columns. If no arguments are provided, TableFu will include all columns by default.
spreadsheet = TableFu.new(csv) do |s| s.columns = ["Best Book", "Author"] end spreadsheet.columns.map do |column| spreadsheet.rows[0].column_for(column).to_s endReturns:
["Malone Muert", "Samuel Beckett"]
Note that the columns are still accessible directly even if they're not in the columns array.
spreadsheet = TableFu.new(csv) do |s| s.columns = ["Best Book", "Author"] end spreadsheet.rows[0]['Style'].to_sReturns:
"Modernism"
TableFu allows you to format columns of cells through the use of macros. See TableFu::Formatting for the predefined macros available.
The formatting attribute should be a hash of the form:
{"Column Name" => 'Formatting Method Name'} # or {"Meta Column Name" => {"method" => "Method Name", "arguments" => ['Column 1', 'Column 2' ... 'Column N']}}which will call the macro on the column name with the arguments if specified.
For example, you can use the last_name formatter to extract the last name of a cell containing a person's name:
spreadsheet = TableFu.new(csv) do |s| s.formatting = {"Author" => 'last_name'} end spreadsheet.rows[0].column_for('Author').to_sReturns:
"Beckett"
Of course, you can provide your own macros by patching TableFu::Formatting. TableSetter includes rails view helpers directly in TableFu::Formatting.
class TableFu::Formatting extend ActionView::Helpers::NumberHelper end
Faceting provides a way to group rows together using a cell value they share in common. Calling TableFu#faceted_by returns an array of table fu instances each with a faceted_on attribute and with only the rows where that value appears.
In this example there are 2 rows where "Modernism" appears in the style column, so calling faceted_on with the argument "Style" returns a TableFu instance with those rows grouped together:
spreadsheet = TableFu.new(csv) spreadsheet.faceted_by "Style"Returns:
table.faceted_on => Minimalism, table.rows => [["Nicholson Baker", "Mezannine", "150", "Minimalism"]] table.faceted_on => Modernism, table.rows => [["Samuel Beckett", "Malone Muert", "120", "Modernism"], ["James Joyce", "Ulysses", "644", "Modernism"]] table.faceted_on => Satire, table.rows => [["Vladimir Sorokin", "The Queue", "263", "Satire"]]
In addition to hiding columns and faceting TableFu can delete rows from the csv. Let's get rid of James Joyce (no one ever finished Ulysses anyway):
spreadsheet = TableFu.new(csv) do |s| s.delete_rows! [1] endReturns:
[["Samuel Beckett", "Malone Muert", "120", "Modernism"], ["Nicholson Baker", "Mezannine", "150", "Minimalism"], ["Vladimir Sorokin", "The Queue", "263", "Satire"]]The deleted rows are still available in @deleted_rows for later access:
spreadsheet = TableFu.new(csv) do |s| s.delete_rows! [1] endReturns:
table.deleted_rows => [["James Joyce", "Ulysses", "644", "Modernism"]]
If you want only a chunk of the data, say to paginate your table, you can call only! with the range of values you want to keep:
spreadsheet = TableFu.new(csv) do |s| s.sorted_by = {'Style' => {"order" => 'ascending'}} end spreadsheet.only!(2..4) spreadsheet.rowsReturns:
[["Nicholson Baker", "Mezannine", "150", "Minimalism"], ["Vladimir Sorokin", "The Queue", "263", "Satire"]]
TableFu can also sum a column of values:
spreadsheet = TableFu.new(csv) spreadsheet.total_for('Number of Pages').to_sReturns:
1177
Jeff Larson (Maintainer), Brian Boyer, Scott Klein, Mark Percival, and Charles Brian Quinn.
Copyright (c) 2010 ProPublica Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.