elskwid-munger 0.1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/munger.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.platform = Gem::Platform::RUBY
3
+ s.name = "elskwid-munger"
4
+ s.version = "0.1.4.1"
5
+ s.authors = ['Scott Chacon', 'Brandon Mitchell', 'Don Morrison', 'Eric Lindvall']
6
+ s.email = "elskwid@gmail.com"
7
+ s.summary = "A reporting engine in Ruby - the elskwid fork!"
8
+ s.homepage = "http://github.com/elskwid/munger"
9
+ s.has_rdoc = true
10
+ s.files = ["munger.gemspec",
11
+ "Rakefile",
12
+ "README",
13
+ "examples/column_add.rb",
14
+ "examples/development.log",
15
+ "examples/example_helper.rb",
16
+ "examples/sinatra_app.rb",
17
+ "examples/test.html",
18
+ "lib/munger.rb",
19
+ "lib/munger/data.rb",
20
+ "lib/munger/item.rb",
21
+ "lib/munger/render.rb",
22
+ "lib/munger/report.rb",
23
+ "lib/munger/render/csv.rb",
24
+ "lib/munger/render/html.rb",
25
+ "lib/munger/render/sortable_html.rb",
26
+ "lib/munger/render/text.rb",
27
+ "spec/spec_helper.rb",
28
+ "spec/munger/data_spec.rb",
29
+ "spec/munger/item_spec.rb",
30
+ "spec/munger/render_spec.rb",
31
+ "spec/munger/report_spec.rb",
32
+ "spec/munger/data/new_spec.rb",
33
+ "spec/munger/render/csv_spec.rb",
34
+ "spec/munger/render/html_spec.rb",
35
+ "spec/munger/render/text_spec.rb"]
36
+ end
@@ -0,0 +1,18 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Munger::Data do
4
+
5
+ describe '.new' do
6
+
7
+ it 'initializes the data attribute to the :data value' do
8
+ data = [{:foo => '1'}, {:foo => 2}]
9
+ Munger::Data.new(:data => data).data.should == data
10
+ end
11
+
12
+ it 'yields itself to the given block' do
13
+ Munger::Data.new { |data| data.should be_kind_of(Munger::Data) }
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,183 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Munger::Data do
4
+ include MungerSpecHelper
5
+
6
+ before(:each) do
7
+ @data = Munger::Data.new(:data => test_data)
8
+ end
9
+
10
+ it "should accept an array of hashes" do
11
+ Munger::Data.new(:data => test_data).should be_valid
12
+ end
13
+
14
+ it "should be able to set data after init" do
15
+ m = Munger::Data.new
16
+ m.data = test_data
17
+ m.should be_valid
18
+ end
19
+
20
+ it "should be able to set data in init block" do
21
+ m = Munger::Data.new do |d|
22
+ d.data = test_data
23
+ end
24
+ m.should be_valid
25
+ end
26
+
27
+ it "should be able to add more data after init" do
28
+ m = Munger::Data.new
29
+ m.data = test_data
30
+ m.add_data more_test_data
31
+ m.should be_valid
32
+ additional_names = m.data.select { |r| r[:name] == 'David' || r[:name] == 'Michael' }
33
+ additional_names.should have(4).items
34
+ end
35
+
36
+ it "should be able to add data without initial data" do
37
+ m = Munger::Data.new
38
+ m.add_data more_test_data
39
+ m.should be_valid
40
+ m.should have(4).items
41
+ end
42
+
43
+ it "should be able to extract columns from data" do
44
+ @titles = @data.columns
45
+ @titles.should have(4).items
46
+ @titles.should include(:name, :score, :age)
47
+ end
48
+
49
+ it "should be able to add a new column with a default value" do
50
+ @data.add_column('new_column', :default => 1)
51
+ @data.data.first['new_column'].should eql(1)
52
+ end
53
+
54
+ it "should be able to add a new column with a block" do
55
+ @data.add_column('new_column') { |c| c.age + 1 }
56
+ @data.data.first['new_column'].should eql(24)
57
+ end
58
+
59
+ it "should be able to add multiple new columns with defaults" do
60
+ @data.add_column(['col1', 'col2'], :default => [1, 2])
61
+ @data.data.first['col1'].should eql(1)
62
+ @data.data.first['col2'].should eql(2)
63
+ end
64
+
65
+ it "should be able to add multiple new columns with a block" do
66
+ @data.add_column(['col1', 'col2']) { |c| [c.age * 2, c.score * 3]}
67
+ @data.data.first['col1'].should eql(46)
68
+ @data.data.first['col2'].should eql(36)
69
+ end
70
+
71
+ it "should work with add_columns, too" do
72
+ @data.add_columns(['col1', 'col2'], :default => [1, 2])
73
+ @data.data.first['col1'].should eql(1)
74
+ @data.data.first['col2'].should eql(2)
75
+ end
76
+
77
+ it "should be able to transform a column" do
78
+ @data.data.first[:age].should eql(23)
79
+ @data.transform_column(:age) { |c| c.age * 2 }
80
+ @data.data.first[:age].should eql(46)
81
+ end
82
+
83
+ it "should be able to transform multiple rows" do
84
+ @data.data.first[:age].should eql(23)
85
+ @data.data.first[:score].should eql(12)
86
+ @data.transform_columns([:age, :score]) { |c| [c.age * 2, c.score * 3] }
87
+ @data.data.first[:age].should eql(46)
88
+ @data.data.first[:score].should eql(36)
89
+ end
90
+
91
+ it "should be able to filter the data down" do
92
+ orig_size = @data.size
93
+ @data.filter_rows { |r| r.age < 30 }
94
+ @data.size.should < orig_size
95
+ @data.size.should eql(4)
96
+ end
97
+
98
+ it "should be able to pivot the data (1 column)" do
99
+ orig_size = @data.size
100
+ new_keys = @data.pivot(:day, :name, :score)
101
+ @data.size.should < orig_size
102
+ new_keys.should include(1, 2)
103
+ scott = @data.data.select { |r| r.name == 'Scott' }.first
104
+ scott[1].should eql(43)
105
+ end
106
+
107
+ it "should be able to pivot the data with average aggregation" do
108
+ new_keys = @data.pivot(:day, :name, :score, :average)
109
+ new_keys.should include(1, 2)
110
+ scott = @data.data.select { |r| r.name == 'Scott' }.first
111
+ scott[1].should eql(21)
112
+ end
113
+
114
+ it "should be able to pivot the data with count aggregation" do
115
+ new_keys = @data.pivot(:day, :name, :score, :count)
116
+ scott = @data.data.select { |r| r.name == 'Scott' }.first
117
+ scott[1].should eql(2)
118
+ end
119
+
120
+ it "should be able to pivot the data in three dimensions (1 col, 2 row)" do
121
+ new_keys = @data.pivot(:name, [:score, :age], :score, :count)
122
+ alice = @data.data.select { |r| r.name == 'Alice' }.first
123
+ alice.Alice.should eql(2)
124
+ end
125
+
126
+ it "should be able to add a column then pivot the data (1 column)" do
127
+ @data.add_column(:day_of_week) { |c| Date::DAYNAMES[c.day] }
128
+ orig_size = @data.size
129
+ new_keys = @data.pivot(:day_of_week, :name, :score)
130
+ @data.size.should < orig_size
131
+ new_keys.should include("Monday", "Tuesday")
132
+ alice = @data.data.select { |r| r.name == 'Alice' }.first
133
+ alice["Monday"].should eql(12)
134
+ alice["Tuesday"].should eql(24)
135
+ end
136
+
137
+ # like sql group command, give aggregation block
138
+ it "should be able to group the data like sql" do
139
+ @data.group(:name)
140
+ @data.size.should eql(6)
141
+ end
142
+
143
+ it "should be able to group on multiple columns" do
144
+ @data.group([:age, :score], :count => :day, :sum => :day, :average => :score)
145
+ alice = @data.data.select { |r| (r.score == 12) && (r.age == 33)}.first
146
+ alice.count_day.should eql(2)
147
+ alice.sum_day.should eql(3)
148
+ alice.average_day.should eql(nil)
149
+ end
150
+
151
+ it "should be able to group with a proc aggregation" do
152
+ pr = Proc.new {|arr| arr.inject(0) { |a,b| a + (b*2) }}
153
+ @data.group([:age, :score], :sum => :day, ['test', pr] => :age)
154
+ alice = @data.data.select { |r| (r.score == 12) && (r.age == 33)}.first
155
+ alice.test_age.should eql(132)
156
+ alice.sum_day.should eql(3)
157
+ end
158
+
159
+ it "should be able to output an array" do
160
+ array = @data.to_a
161
+ array.should be_a_kind_of(Array)
162
+ array.first.size.should eql(@data.columns.size)
163
+ end
164
+
165
+ it "should be able to output a filtered array" do
166
+ array = @data.to_a([:name, :age])
167
+ array.should be_a_kind_of(Array)
168
+ array.first.size.should eql(2)
169
+ scotts = array.select { |a| a.include? ('Scott') }
170
+ scotts.first.should include('Scott', 23)
171
+ end
172
+
173
+ it "should be able to pivot the data in three dimensions (2 col, 1 row)"
174
+
175
+ it "should be able to pivot the data in four dimensions (2 col, 2 row)"
176
+
177
+ it "should be able to add two Munger::Datas together if they have the same columns"
178
+
179
+ it "should be able to add data and check if it is Munger::Data"
180
+
181
+ it "(maybe) should be able to zip two Munger::Datas together given a unique key column in each"
182
+
183
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Munger::Item do
4
+ include MungerSpecHelper
5
+
6
+ before(:all) do
7
+ @data = Munger::Data.new(:data => test_data)
8
+ end
9
+
10
+ it "should accept a hash with symbols" do
11
+ hash = {'key1' => 'value1', 'key2' => 'value2'}
12
+ item = Munger::Item.ensure(hash)
13
+ item.key1.should eql('value1')
14
+ end
15
+
16
+ it "should accept a hash with strings" do
17
+ hash = {:key1 => 'value1', :key2 => 'value2'}
18
+ item = Munger::Item.ensure(hash)
19
+ item.key1.should eql('value1')
20
+ item.key2.should_not eql('value1')
21
+ item.key3.should be(nil)
22
+ end
23
+
24
+ it "should accept mixed types" do
25
+ hash = {:key1 => 'value1', 'key2' => 'value2'}
26
+ item = Munger::Item.ensure(hash)
27
+ item.key1.should eql('value1')
28
+ item.key2.should eql('value2')
29
+ end
30
+
31
+ it "should be able to access hash values indifferently" do
32
+ hash = {:key1 => 'value1', 'key2' => 'value2'}
33
+ item = Munger::Item.ensure(hash)
34
+ item['key1'].should eql('value1')
35
+ item[:key2].should eql('value2')
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + "/../../spec_helper"
2
+
3
+ describe Munger::Render::CSV do
4
+ include MungerSpecHelper
5
+
6
+ before(:each) do
7
+ @data = Munger::Data.new(:data => test_data)
8
+ @report = Munger::Report.new(:data => @data)
9
+ end
10
+
11
+ it "should accept a Munger::Report object" do
12
+ Munger::Render::Text.new(@report.process).should be_valid
13
+ end
14
+
15
+ it "should render a basic text table" do
16
+ @render = Munger::Render::CSV.new(@report.process)
17
+ count = @report.rows
18
+ text = @render.render
19
+ text.split("\n").should have_at_least(count).items
20
+ end
21
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + "/../../spec_helper"
2
+
3
+ describe Munger::Render::Html do
4
+ include MungerSpecHelper
5
+
6
+ before(:each) do
7
+ @data = Munger::Data.new(:data => test_data)
8
+ @report = Munger::Report.new(:data => @data)
9
+ end
10
+
11
+ it "should accept a Munger::Report object" do
12
+ Munger::Render::Html.new(@report.process).should be_valid
13
+ end
14
+
15
+ it "should render a basic html table" do
16
+ @render = Munger::Render::Html.new(@report.process)
17
+ count = @report.rows
18
+
19
+ html = @render.render
20
+ html.should have_tag('table')
21
+ html.should have_tag('tr', :count => count + 1) # rows plus header
22
+ end
23
+
24
+ it "should accept a custom table class" do
25
+ rep = Munger::Render::Html.new(@report.process, :classes => {:table => 'helloClass'})
26
+ html = rep.render
27
+ html.should have_tag('table.helloClass')
28
+ end
29
+
30
+ it "should use aliased column titles" do
31
+ @report = @report.columns([:age, :name, :score])
32
+ @report.column_titles = {:age => "The Age", :name => "The Name"}
33
+ html = Munger::Render::Html.new(@report.process).render
34
+ html.should match(/The Age/)
35
+ end
36
+
37
+ it "should render columns in the right order" do
38
+ @report = @report.columns([:age, :name]).process
39
+ html = Munger::Render::Html.new(@report).render
40
+ html.should have_tag('th', :count => 2) # rows plus header
41
+ html.should match(/age(.*?)name/)
42
+ end
43
+
44
+ it "should render groups" do
45
+ @report = @report.subgroup(:age).aggregate(:sum => :score).process
46
+ html = Munger::Render::Html.new(@report).render
47
+ html.should match(/151/) # only in the aggregate group
48
+ html.should have_tag('tr.group0', :count => 1)
49
+ html.should have_tag('tr.group1', :count => 9)
50
+ end
51
+
52
+ it "should render group headers" do
53
+ @report = @report.subgroup(:age, :with_headers => true).process
54
+ html = Munger::Render::Html.new(@report).render
55
+ html.should have_tag('tr.groupHeader1', :count => 9)
56
+ end
57
+
58
+ it "should render cell styles" do
59
+ @report.process.style_rows('over_thirty') { |row| row.age > 29 }
60
+
61
+ html = Munger::Render::Html.new(@report).render
62
+ html.should have_tag('tr.over_thirty', :count => 6)
63
+ end
64
+
65
+ it "should render row styles" do
66
+ @report.process.style_cells('highlight', :only => :age) { |c, r| c == 32 }
67
+ html = Munger::Render::Html.new(@report).render
68
+ html.should have_tag('td.highlight')
69
+ end
70
+
71
+ it "should render column styles"
72
+
73
+ it "should render default css if you ask"
74
+
75
+ end
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + "/../../spec_helper"
2
+
3
+ describe Munger::Render::Text do
4
+ include MungerSpecHelper
5
+
6
+ before(:each) do
7
+ @data = Munger::Data.new(:data => test_data)
8
+ @report = Munger::Report.new(:data => @data)
9
+ end
10
+
11
+ it "should accept a Munger::Report object" do
12
+ Munger::Render::Text.new(@report.process).should be_valid
13
+ end
14
+
15
+ it "should render a basic text table" do
16
+ @render = Munger::Render::Text.new(@report.process)
17
+ count = @report.rows
18
+ text = @render.render
19
+ text.split("\n").should have_at_least(count).items
20
+ end
21
+
22
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Munger::Render do
4
+ include MungerSpecHelper
5
+
6
+ before(:each) do
7
+ @data = Munger::Data.new(:data => test_data)
8
+ @report = Munger::Report.new(:data => @data).process
9
+ end
10
+
11
+ it "should render html" do
12
+ html = Munger::Render.to_html(@report)
13
+ html.should have_tag('table')
14
+ end
15
+
16
+ it "should render text" do
17
+ text = Munger::Render.to_text(@report)
18
+ text.should_not have_tag('table')
19
+ text.split("\n").should have_at_least(5).items
20
+ end
21
+
22
+ it "should render xls"
23
+
24
+ it "should render csv"
25
+
26
+ it "should render pdf"
27
+
28
+ end
@@ -0,0 +1,155 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Munger::Report do
4
+ include MungerSpecHelper
5
+
6
+ before(:each) do
7
+ @data = Munger::Data.new(:data => test_data)
8
+ @report = Munger::Report.new(:data => @data)
9
+ end
10
+
11
+ it "should accept a Munger::Data object" do
12
+ Munger::Report.new(:data => @data).should be_valid
13
+ end
14
+
15
+ it "should accept a array of hashes" do
16
+ Munger::Report.new(:data => test_data).should be_valid
17
+ Munger::Report.new(:data => invalid_test_data).should_not be_valid
18
+ end
19
+
20
+ it "should be able to sort fields by array" do
21
+ @report.sort = 'name'
22
+ data = @report.process.process_data
23
+ data.map { |a| a[:data].name }[0, 4].join(',').should eql('Alice,Alice,Alice,Chaz')
24
+
25
+ @report.sort = ['name', 'age']
26
+ data = @report.process.process_data
27
+ data.map { |a| a[:data].age }[0, 4].join(',').should eql('33,33,34,28')
28
+
29
+ @report.sort = [['name', :asc], ['age', :desc]]
30
+ data = @report.process.process_data
31
+ data.map { |a| a[:data].age }[0, 4].join(',').should eql('34,33,33,28')
32
+ end
33
+
34
+ it "should be able to custom sort fields" do
35
+ @report.sort = [['name', Proc.new {|a, b| a[2] <=> b[2]} ]]
36
+ data = @report.process.process_data
37
+ data.map { |a| a[:data].name }[0, 4].join(',').should eql('Chaz,Rich,Alice,Alice')
38
+ end
39
+
40
+ it "should be able to order columns" do
41
+ @report.columns([:name, :age, :score])
42
+ @report.columns.should eql([:name, :age, :score])
43
+ end
44
+
45
+ it "should be able to alias column titles" do
46
+ titles = {:name => 'My Name', :age => 'The Age', :score => 'Super Score'}
47
+ @report.column_titles = titles
48
+ @report.column_titles.should eql(titles)
49
+ end
50
+
51
+ it "should default to all columns" do
52
+ @report.columns.map { |c| c.to_s }.sort.join(',').should eql('age,day,name,score')
53
+ end
54
+
55
+
56
+ it "should be able to subgroup data" do
57
+ @report.sort('name').subgroup('name').process
58
+ @report.get_subgroup_rows.should have(6).items
59
+ end
60
+
61
+ it "should be able to add subgroup headers" do
62
+ @report.sort('score').subgroup('score', :with_headers => true)
63
+ @report.aggregate(:sum => :score).process
64
+ puts Munger::Render.to_text(@report)
65
+ end
66
+
67
+ it "should add the grouping name on the group line somewhere"
68
+
69
+ it "should be able to subgroup in multiple dimensions"
70
+
71
+ it "should be able to aggregate columns into subgroup rows" do
72
+ @report.sort('name').subgroup('name').aggregate(:sum => :score).process
73
+ @report.get_subgroup_rows(1).should have(6).items
74
+ @report.get_subgroup_rows(0).should have(1).items
75
+ @report.get_subgroup_rows(0).first[:data][:score].should eql(151)
76
+ end
77
+
78
+ it "should be able to aggregate multiple columns into subgroup rows" do
79
+ @report.sort('name').subgroup('name').aggregate(:sum => [:score, :age]).process
80
+ data = @report.get_subgroup_rows(0).first[:data]
81
+ data[:score].should eql(151)
82
+ data[:age].should eql(294)
83
+
84
+ @report.sort('name').subgroup('name').aggregate(:sum => :score, :average => :age).process
85
+ data = @report.get_subgroup_rows(0).first[:data]
86
+ data[:score].should eql(151)
87
+ data[:age].should eql(29)
88
+ end
89
+
90
+ it "should be able to aggregate with :average" do
91
+ @report.sort('name').subgroup('name').aggregate(:average => :score).process
92
+ @report.get_subgroup_rows(0).first[:data][:score].should eql(15)
93
+ end
94
+
95
+ it "should be able to aggregate with :custom" do
96
+ @report.sort('name').subgroup('name')
97
+ @report.aggregate(Proc.new { |d| d.inject { |t, a| 2 * (t + a) } } => :score).process
98
+ @report.get_subgroup_rows(0).first[:data][:score].should eql(19508)
99
+ end
100
+
101
+ it "should be able to style cells" do
102
+ @report.process
103
+ @report.style_cells('highlight') { |c, r| c == 32 }
104
+ styles = @report.process_data.select { |r| r[:meta][:cell_styles] }
105
+ styles.should have(2).items
106
+ end
107
+
108
+ it "should be able to style cells in certain columns" do
109
+ @report.process
110
+ @report.style_cells('highlight', :only => :age) { |c, r| c == 32 }
111
+ @report.style_cells('big', :except => [:name, :day]) { |c, r| c.size > 2 }
112
+ styles = @report.process_data.select { |r| r[:meta][:cell_styles] }
113
+ styles.should have(10).items
114
+
115
+ janet = @report.process_data.select { |r| r[:data].name == 'Janet' }.first
116
+ jstyles = janet[:meta][:cell_styles]
117
+
118
+ jstyles[:age].sort.join(',').should eql('big,highlight')
119
+ jstyles[:score].should eql(["big"])
120
+ end
121
+
122
+ it "should be able to style rows" do
123
+ @report.process
124
+ @report.style_rows('over_thirty') { |row| row.age > 29 }
125
+ @report.style_cells('highlight', :only => :age) { |c, r| c == 32 }
126
+
127
+ janet = @report.process_data.select { |r| r[:data].name == 'Janet' }.first[:meta]
128
+ janet[:row_styles].should eql(["over_thirty"])
129
+ janet[:cell_styles].should have(1).item
130
+ janet[:cell_styles][:age].should eql(["highlight"])
131
+ end
132
+
133
+ it "should know when it is processed" do
134
+ @report.should_not be_processed
135
+ @report.process
136
+ @report.should be_processed
137
+ end
138
+
139
+ it "should be able to style columns"
140
+
141
+ it "should be able to attach formatting independent of content"
142
+ # so can format numbers without hurting ability to aggregate correctly
143
+ # or add hyperlinks using data from columns not being shown
144
+
145
+ it "should be able to add and retrieve column formatters" do
146
+ @report.column_formatters = {:name => :to_s}
147
+ @report.process
148
+ @report.column_formatters.should have(1).item
149
+ @report.column_formatter(:name).should eql(:to_s)
150
+ end
151
+
152
+ it "should be able to aggregate rows into new column"
153
+
154
+
155
+ end
@@ -0,0 +1,106 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/munger")
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'fileutils'
6
+ require 'logger'
7
+ require 'pp'
8
+
9
+ require 'rspec_hpricot_matchers'
10
+ Spec::Runner.configure do |config|
11
+ config.include(RspecHpricotMatchers)
12
+ end
13
+
14
+ module MungerSpecHelper
15
+ def test_data
16
+ [{:name => 'Scott', :age => 23, :day => 1, :score => 12},
17
+ {:name => 'Chaz', :age => 28, :day => 1, :score => 12},
18
+ {:name => 'Scott', :age => 23, :day => 2, :score => 1},
19
+ {:name => 'Janet', :age => 32, :day => 2, :score => 24},
20
+ {:name => 'Rich', :age => 32, :day => 2, :score => 14},
21
+ {:name => 'Gordon', :age => 33, :day => 1, :score => 21},
22
+ {:name => 'Scott', :age => 23, :day => 1, :score => 31},
23
+ {:name => 'Alice', :age => 33, :day => 1, :score => 12},
24
+ {:name => 'Alice', :age => 34, :day => 2, :score => 12},
25
+ {:name => 'Alice', :age => 33, :day => 2, :score => 12},
26
+ ]
27
+ end
28
+
29
+ def more_test_data
30
+ [{:name => 'David', :age => 40, :day => 1, :score => 12},
31
+ {:name => 'Michael', :age => 32, :day => 2, :score => 20},
32
+ {:name => 'David', :age => 40, :day => 2, :score => 13},
33
+ {:name => 'Michael', :age => 28, :day => 1, :score => 15}]
34
+ end
35
+
36
+ def invalid_test_data
37
+ ['one', 'two', 'three']
38
+ end
39
+
40
+ def test_ar_data
41
+ test_data.map{|r| ARLike.new(r)}
42
+ end
43
+ end
44
+
45
+
46
+
47
+ ##
48
+ # rSpec Hash additions.
49
+ #
50
+ # From
51
+ # * http://wincent.com/knowledge-base/Fixtures_considered_harmful%3F
52
+ # * Neil Rahilly
53
+
54
+ class Hash
55
+
56
+ ##
57
+ # Filter keys out of a Hash.
58
+ #
59
+ # { :a => 1, :b => 2, :c => 3 }.except(:a)
60
+ # => { :b => 2, :c => 3 }
61
+
62
+ def except(*keys)
63
+ self.reject { |k,v| keys.include?(k || k.to_sym) }
64
+ end
65
+
66
+ ##
67
+ # Override some keys.
68
+ #
69
+ # { :a => 1, :b => 2, :c => 3 }.with(:a => 4)
70
+ # => { :a => 4, :b => 2, :c => 3 }
71
+
72
+ def with(overrides = {})
73
+ self.merge overrides
74
+ end
75
+
76
+ ##
77
+ # Returns a Hash with only the pairs identified by +keys+.
78
+ #
79
+ # { :a => 1, :b => 2, :c => 3 }.only(:a)
80
+ # => { :a => 1 }
81
+
82
+ def only(*keys)
83
+ self.reject { |k,v| !keys.include?(k || k.to_sym) }
84
+ end
85
+
86
+ end
87
+
88
+ ##
89
+ # Gives us a hash that acts like an ActiveRecord dataset (sort of)
90
+ #
91
+ class ARLike
92
+
93
+ attr_accessor :attributes
94
+
95
+ def initialize(attributes)
96
+ @attributes = attributes
97
+ end
98
+
99
+ def [](key)
100
+ attributes[key]
101
+ end
102
+
103
+ def []=(key, value)
104
+ attributes[key] = value
105
+ end
106
+ end