Skip to content

Commit

Permalink
DataNode flattening can deal with inconsistent column names within a …
Browse files Browse the repository at this point in the history
…layer
  • Loading branch information
DavidMikeSimon committed Sep 10, 2012
1 parent 78ba45e commit 150b233
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 26 deletions.
2 changes: 1 addition & 1 deletion TODO
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
- Replace all the TODOs and FIXMEs with whatever they're asking for (additional checks, use of ordered indifferent hashes instead of arrays of hashes, etc.)
- If there is more than one association from model A to model B and they're both focusable, pick the one with no conditions. If all the associations have conditions, complain and require that the correct association be manually specified (where "correct" might mean none of them should be valid)
- Alternately, always ignore conditional associations unless they're specifically provided to Mochigome by the model
- Named subsets of different fields and agg fields on a single model
- Named subsets of different fields on a single model
- Some kind of single-page preview on the edit screen would be cool. Maybe use FOP with fake data and the PNG output option? Slow, tho...
- Automatically set default to 0 on sum and count aggregation
- Better handling of nil in subgroup fields
Expand Down
42 changes: 26 additions & 16 deletions lib/data_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ def merge!(a)
def <<(item)
if item.is_a?(Array)
item.map {|i| self << i}
else
raise DataNodeError.new("New child #{item} is not a DataNode") unless item.is_a?(DataNode)
elsif item.is_a?(DataNode)
@children << item
@children.last
else
raise DataNodeError.new("Can't adopt #{item.inspect}, it's not a DataNode")
end
end

Expand All @@ -47,26 +48,24 @@ def clone
twin
end

# TODO: Only define xml-related methods if nokogiri loaded
def to_xml
doc = Nokogiri::XML::Document.new
append_xml_to(doc)
doc
end

# TODO: Only define ruport-related methods if ruport is loaded
def to_flat_ruport_table
col_names = flat_column_names
table = Ruport::Data::Table.new(:column_names => col_names)
append_rows_to(table, col_names.size)
append_rows_to(table, col_names)
table
end

def to_flat_arrays
table = []
col_names = flat_column_names
table << col_names
append_rows_to(table, col_names.size)
append_rows_to(table, col_names)
table
end

Expand Down Expand Up @@ -97,27 +96,38 @@ def append_xml_to(x)
x.add_child(node)
end

# TODO: Should handle trickier situations involving datanodes not having various columns
def flat_column_names
colnames = (["name"] + keys).
reject{|key| key.to_s.start_with?("_")}.
map{|key| "#{@type_name}::#{key}"}
choices = @children.map(&:flat_column_names)
colnames += choices.max_by(&:size) || []
colnames += choices.flatten(1).uniq || []
colnames
end

# TODO: Should handle trickier situations involving datanodes not having various columns
def append_rows_to(table, pad, stack = [])
row_vals = keys.reject{|k| k.to_s.start_with?("_")}.map{|k| self[k]}
stack.push([@name] + row_vals)
def append_rows_to(table, colnames, row = nil)
row = colnames.map{nil} if row.nil?

added_cell_indices = []
colnames.each_with_index do |k, i|
if k =~ /^#{@type_name}::(.+)$/
attr_name = $1
if attr_name.to_sym == :name
row[i] = name
else
row[i] = self[attr_name.to_sym]
end
added_cell_indices << i
end
end

if @children.size > 0
@children.each {|child| child.send(:append_rows_to, table, pad, stack)}
@children.each {|child| child.send(:append_rows_to, table, colnames, row)}
else
row = stack.flatten(1)
table << (row + Array.new(pad - row.size, nil))
table << row.dup
end
stack.pop

added_cell_indices.each{|i| row[i] = nil}
end
end
end
23 changes: 14 additions & 9 deletions test/unit/data_node_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,12 @@
@datanode.merge! [{:id => 400}, {:apples => 1}, {:box_cutters => 2}, {:can_openers => 3}]
emp1 = @datanode << Mochigome::DataNode.new(:employee, :alice)
emp1.merge! [{:id => 500}, {:x => 9}, {:y => 8}, {:z => 7}, {:internal_type => "Cyborg"}, {:_foo => "bar"}]
emp1 << Mochigome::DataNode.new(:phone, :android)
emp2 = @datanode << Mochigome::DataNode.new(:employee, :bob)
emp2.merge! [{:id => 600}, {:x => 5}, {:y => 4}, {:z => 8734}, {:internal_type => "Human"}]
emp2 << Mochigome::DataNode.new(:pet, :lassie)

@titles = [
@expected_titles = [
"corporation::name",
"corporation::id",
"corporation::apples",
Expand All @@ -118,12 +119,15 @@
"employee::y",
"employee::z",
"employee::internal_type",
"phone::name",
"pet::name"
]
@expected_row_1 = ['acme', 400, 1, 2, 3, 'alice', 500, 9, 8, 7, "Cyborg", "android", nil]
@expected_row_2 = ['acme', 400, 1, 2, 3, 'bob', 600, 5, 4, 8734, "Human", nil, "lassie"]
end

it "can convert to an XML document with correct attributes and elements" do
# Why stringify and reparse? So that we could use another XML generator
# Why stringify and reparse? So that implementation could use another XML generator
doc = Nokogiri::XML(@datanode.to_xml.to_s)

comment = doc.xpath('/node[@type="Corporation"]/comment()').first
Expand All @@ -140,9 +144,10 @@
assert_equal "bob", emp_nodes.last['name']
assert_equal "Cyborg", emp_nodes.first['internal_type']
assert_equal "4", emp_nodes.last.xpath('datum[@name="Y"]').first.content
assert_equal "android", emp_nodes.first.xpath('node').first['name']
assert_equal "lassie", emp_nodes.last.xpath('node').first['name']

# Keys that start with an underscore are to be turned into so-named elems
# Keys that start with an underscore are to be turned into special elems
assert_empty emp_nodes.first.xpath('datum').select{|datum|
datum['name'] =~ /foo/i
}
Expand All @@ -151,16 +156,16 @@

it "can convert to a flattened Ruport table" do
table = @datanode.to_flat_ruport_table
assert_equal @titles, table.column_names
assert_equal ['acme', 400, 1, 2, 3, 'alice', 500, 9, 8, 7, "Cyborg", nil], table.data[0].to_a
assert_equal ['acme', 400, 1, 2, 3, 'bob', 600, 5, 4, 8734, "Human", "lassie"], table.data[1].to_a
assert_equal @expected_titles, table.column_names
assert_equal @expected_row_1, table.data[0].to_a
assert_equal @expected_row_2, table.data[1].to_a
end

it "can convert to a flat array of arrays" do
a = @datanode.to_flat_arrays
assert_equal @titles, a[0]
assert_equal ['acme', 400, 1, 2, 3, 'alice', 500, 9, 8, 7, "Cyborg", nil], a[1]
assert_equal ['acme', 400, 1, 2, 3, 'bob', 600, 5, 4, 8734, "Human", "lassie"], a[2]
assert_equal @expected_titles, a[0]
assert_equal @expected_row_1, a[1]
assert_equal @expected_row_2, a[2]
end
end
end

0 comments on commit 150b233

Please sign in to comment.