crosstab 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ class Crosstab::Table < Crosstab::Generic
2
+ # Pass in a block and we'll execute it within the context of this class.
3
+ #
4
+ # Example:
5
+ #
6
+ # my_crosstab = Crosstab.new do
7
+ # table do
8
+ # row "Male", :a => 1
9
+ # row "Female", :a => 2
10
+ # end
11
+ # end
12
+ #
13
+ def initialize(&block)
14
+ instance_eval(&block) if block
15
+ end
16
+
17
+ # attr_reader for the rows attribute which should contain an empty array, or a list of rows
18
+ #
19
+ # Example:
20
+ #
21
+ # rows
22
+ # #=> []
23
+ #
24
+ # row "Male", :a => 1
25
+ #
26
+ # rows
27
+ # #=> [Crosstab::Row...]
28
+ #
29
+ def rows
30
+ @rows ||= []
31
+ end
32
+
33
+ # Creates a new Crosstab::Row and appends it to the rows array.
34
+ #
35
+ # Example:
36
+ #
37
+ # rows
38
+ # #=> []
39
+ #
40
+ # row "Male", :a => 1
41
+ #
42
+ # rows
43
+ # #=> [Crosstab::Row...]
44
+ #
45
+ def row(name,qualification)
46
+ rows << Crosstab::Row.new(name, qualification)
47
+ end
48
+
49
+ # DSL child setter, creates a new Group, and rows created within its block will be assigned to that group.
50
+ #
51
+ # Example:
52
+ #
53
+ # rows
54
+ # #=> []
55
+ #
56
+ # group "Gender" do
57
+ # row "Male", :a => 1
58
+ # row "Female", :a => 2
59
+ # end
60
+ #
61
+ # rows
62
+ # #=> [Crosstab::Row..., Crosstab::Row...]
63
+ #
64
+ # rows[0].group.title
65
+ # # => "Gender"
66
+ #
67
+ # rows[1].group.title
68
+ # # => "Gender"
69
+
70
+ def group(name=nil, &block)
71
+ if block
72
+ old_rows = rows.dup # Save current state
73
+
74
+ instance_eval(&block) # Execute block within current scope
75
+
76
+ g = Crosstab::Group.new(name) # Create new group
77
+
78
+ # Set group for all of the new rows
79
+ (rows - old_rows).each do |row|
80
+ row.group g
81
+ end
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,81 @@
1
+ $LOAD_PATH.unshift "../lib" if __FILE__ == $0
2
+
3
+ require 'test/unit'
4
+ require 'crosstab'
5
+
6
+ class BannerTests < Test::Unit::TestCase
7
+ def setup
8
+ @banner = Crosstab::Banner.new
9
+ end
10
+
11
+ # Responds to these public methods
12
+ def test_respond_methods
13
+ [:title, :qualification, :qualifies?, :columns, :column, :group].each do |method|
14
+ assert_respond_to @banner, method, "Hey, an instantiated banner should have a `#{method}` method."
15
+ end
16
+ end
17
+
18
+ # Test defaults for reader methods
19
+ def test_default_columns
20
+ assert_kind_of Array, @banner.columns
21
+
22
+ # Should have a total column by default
23
+ assert_equal 1, @banner.columns.length
24
+ assert_kind_of Crosstab::Column, @banner.columns.first
25
+ assert_equal "Total", @banner.columns.first.title
26
+ assert_equal true, @banner.columns.first.qualifies?(Hash.new)
27
+ end
28
+
29
+ def test_default_title
30
+ assert_nil @banner.title
31
+ end
32
+
33
+ def test_default_qualifies?
34
+ assert_equal true, @banner.qualifies?(Hash.new)
35
+ end
36
+
37
+ # Test behavior of setter methods
38
+ def test_set_title
39
+ @banner.title "Test Title"
40
+ assert_equal "Test Title", @banner.title
41
+ end
42
+
43
+ def test_set_qualification
44
+ @banner.qualification :a => 1
45
+
46
+ assert_equal true, @banner.qualifies?(:a => 1, :b => 1)
47
+ assert_equal false, @banner.qualifies?(:a => 2, :b => 1)
48
+ end
49
+
50
+ def test_set_column
51
+ # erase the default column
52
+ @banner.columns.pop
53
+
54
+ @banner.column "New Column", :a => 2
55
+
56
+ assert_equal 1, @banner.columns.length
57
+ assert_kind_of Crosstab::Column, @banner.columns.first
58
+ assert_equal "New Column", @banner.columns.first.title
59
+ end
60
+
61
+ def test_set_group
62
+ # erase the default column
63
+ @banner.columns.pop
64
+
65
+ @banner.group "Gender" do
66
+ column "Male", :a => 1
67
+ column "Female", :a => 2
68
+ end
69
+
70
+ assert_equal 2, @banner.columns.length
71
+ assert_kind_of Crosstab::Column, @banner.columns.first
72
+ assert_kind_of Crosstab::Column, @banner.columns.last
73
+
74
+ assert_equal "Male", @banner.columns.first.title
75
+ assert_equal "Female", @banner.columns.last.title
76
+
77
+ assert_equal "Gender", @banner.columns.first.group.title
78
+ assert_equal "Gender", @banner.columns.last.group.title
79
+ end
80
+ end
81
+
@@ -0,0 +1,95 @@
1
+ $LOAD_PATH.unshift "../lib" if __FILE__ == $0
2
+
3
+ require 'test/unit'
4
+
5
+ class TestCrosstabCell < Test::Unit::TestCase
6
+ def setup
7
+ @cell = Crosstab::Cell.new
8
+ end
9
+
10
+ # Responds to these public methods
11
+ def test_respond_methods
12
+ [:base, :frequency, :percentage, :result, :significant_against?, :column].each do |method|
13
+ assert_respond_to @cell, method, "Hey, an instantiated cell should have a `#{method}` method."
14
+ end
15
+ end
16
+
17
+ # Test defaults for reader methods
18
+ def test_default_base
19
+ assert_equal 0, @cell.base
20
+ end
21
+
22
+ def test_default_frequency
23
+ assert_equal 0, @cell.frequency
24
+ end
25
+
26
+ def test_default_percentage
27
+ assert_equal 0, @cell.percentage
28
+ end
29
+
30
+ def test_default_result
31
+ assert_equal ["--"], @cell.result
32
+ end
33
+
34
+ def test_normal_result
35
+ @cell.base 100
36
+ @cell.frequency 75
37
+
38
+ assert_equal [75, "75%"], @cell.result
39
+ end
40
+
41
+ def test_zero_frequency_result
42
+ @cell.base 100
43
+ @cell.frequency 0
44
+
45
+ assert_equal ["--"], @cell.result
46
+ end
47
+
48
+ # Test behavior of setter methods
49
+ def test_set_base
50
+ @cell.base 999
51
+ assert_equal 999, @cell.base
52
+ end
53
+
54
+ def test_set_frequency
55
+ @cell.frequency 999
56
+ assert_equal 999, @cell.frequency
57
+ end
58
+
59
+ def test_default_column
60
+ assert_equal nil, @cell.column
61
+ end
62
+
63
+ def test_set_column
64
+ column = Crosstab::Column.new
65
+ @cell.column column
66
+ assert_equal column, @cell.column
67
+ end
68
+
69
+ def test_alternate_initialization
70
+ cell = Crosstab::Cell.new :frequency => 2, :base => 4
71
+
72
+ assert_equal 2, cell.frequency
73
+ assert_equal 4, cell.base
74
+ end
75
+
76
+ # Test behavior of complex methods
77
+ def test_percentage
78
+ @cell.base 100
79
+ @cell.frequency 75
80
+
81
+ assert_equal 0.75, @cell.percentage
82
+ end
83
+
84
+ def test_significant_against
85
+ @cell.base 100
86
+ @cell.frequency 50
87
+
88
+ @test_cell_a = Crosstab::Cell.new :base => 100, :frequency => 63
89
+ @test_cell_b = Crosstab::Cell.new :base => 100, :frequency => 64
90
+
91
+ assert_equal false, @test_cell_a.significant_against?(@cell) #63/100 against 50/100 is not significant at a 95% confidence level
92
+ assert_equal true, @test_cell_b.significant_against?(@cell) #64/100 against 50/100, however, is.
93
+ end
94
+
95
+ end
@@ -0,0 +1,60 @@
1
+ $LOAD_PATH.unshift "../lib" if __FILE__ == $0
2
+
3
+ require 'test/unit'
4
+
5
+
6
+ class ColumnTests < Test::Unit::TestCase
7
+ def setup
8
+ @column = Crosstab::Column.new
9
+ end
10
+
11
+ # Responds to these public methods
12
+ def test_respond_method
13
+ [:title, :qualification, :qualifies?, :records, :group].each do |method|
14
+ assert_respond_to @column, method, "Hey, an instantiated column should have a `#{method}` method."
15
+ end
16
+ end
17
+
18
+ # Test defaults for reader methods
19
+ def test_default_title
20
+ assert_nil @column.title
21
+ end
22
+
23
+ def test_default_qualifies?
24
+ assert_equal true, @column.qualifies?(Hash.new)
25
+ end
26
+
27
+ def test_default_records
28
+ assert_equal [], @column.records
29
+ end
30
+
31
+ def test_default_group
32
+ assert_equal nil, @column.group
33
+ end
34
+
35
+ # Test behavior of setter methods
36
+ def test_set_title
37
+ @column.title "Test Title"
38
+ assert_equal "Test Title", @column.title
39
+ end
40
+
41
+ def test_set_qualification
42
+ @column.qualification :a => 1
43
+
44
+ assert_equal true, @column.qualifies?(:a => 1, :b => 1)
45
+ assert_equal false, @column.qualifies?(:a => 2, :b => 1)
46
+ end
47
+
48
+ def test_set_records
49
+ record = {:a => 1}
50
+
51
+ @column.records [record]
52
+ assert_equal [record], @column.records
53
+ end
54
+
55
+ def test_set_group
56
+ @column.group Crosstab::Group.new("Group Title")
57
+ assert_equal "Group Title", @column.group.title
58
+ assert_equal [ @column ], @column.group.children
59
+ end
60
+ end
@@ -0,0 +1,214 @@
1
+ $LOAD_PATH.unshift "../lib" if __FILE__ == $0
2
+
3
+ require 'test/unit'
4
+ require 'crosstab'
5
+
6
+ class CrosstabTests < Test::Unit::TestCase
7
+ def setup
8
+ # Minimal crosstab for most tests
9
+ @crosstab = Crosstab::Crosstab.new
10
+
11
+ # Complex crosstab for calculation tests
12
+ @calculated_crosstab = Crosstab::Crosstab.new do
13
+ data_source [{:a => 1, :b => 1, :c => 1},
14
+ {:a => 1, :b => 2, :c => 2},
15
+ {:a => 1, :b => 2, :c => 4}]
16
+
17
+ banner do
18
+ column "Total"
19
+ group "Gender" do
20
+ column "Male", :a => 1
21
+ column "Female", :a => 2
22
+ end
23
+ end
24
+
25
+ table do
26
+ title "Q.A Gender"
27
+ row "Male", :a => 1
28
+ row "Female", :a => 2
29
+ end
30
+
31
+ table do
32
+ title "Q.B Age"
33
+ row "18-34", :b => 1
34
+ row "35-54", :b => 2
35
+ end
36
+
37
+ table do
38
+ title "Q.C Likelihood of purchasing"
39
+
40
+ group "Very/somewhat likely (Net)" do
41
+ row "Very likely", :c => 1
42
+ row "Somewhat likely", :c => 2
43
+ end
44
+
45
+ group "Very/somewhat unlikely (Net)" do
46
+ row "Somewhat unlikely", :c => 3
47
+ row "Very unlikely", :c => 4
48
+ end
49
+ end
50
+ end
51
+ @calculated_crosstab.calculate
52
+ end
53
+
54
+ # Responds to these public methods
55
+ def test_respond_methods
56
+ [:data_source, :calculate, :title, :qualification,
57
+ :qualifies?, :banner, :table, :tables, :printed?].each do |method|
58
+ assert_respond_to @crosstab, method, "Hey an instantiated crosstab should have a `#{method}` method."
59
+ end
60
+ end
61
+
62
+ # Test defaults for reader methods
63
+ def test_default_data_source
64
+ assert_equal [], @crosstab.data_source, "By default, `data_source` is supposed to respond with an empty array."
65
+ end
66
+
67
+ def test_default_title
68
+ assert_nil @crosstab.title, "By default, `title` is supposed to be nil"
69
+ end
70
+
71
+ def test_default_qualifies?
72
+ record = Hash.new
73
+
74
+ assert(@crosstab.qualifies?(record), "By default, `qualifies?` is supposed to respond with true.")
75
+ end
76
+
77
+ def test_default_banner
78
+ assert_kind_of Crosstab::Banner, @crosstab.banner, "By default, `banner` is supposed to respond with a Crosstab::Banner kind of object. The user shouldn't have to define their own unless they want more than a total column."
79
+ end
80
+
81
+ def test_default_tables
82
+ assert_equal [], @crosstab.tables, "By default, `tables` is supposed to respond with an empty array."
83
+ end
84
+
85
+ def test_default_printed?
86
+ assert_equal false, @crosstab.printed?
87
+ end
88
+
89
+ # Test behavior of setter methods
90
+ def test_set_data_source
91
+ test_data = [{:a => 1}, {:a => 2}]
92
+
93
+ @crosstab.data_source test_data
94
+ assert_equal test_data, @crosstab.data_source, "`data_source [{:a => 1}, {:a => 2}]` is supposed to respond with the same thing it was set with."
95
+ end
96
+
97
+ def test_set_title
98
+ @crosstab.title "Test Title"
99
+ assert_equal "Test Title", @crosstab.title
100
+ end
101
+
102
+ def test_set_qualification
103
+ @crosstab.qualification :a => true
104
+
105
+ assert_equal true, @crosstab.qualifies?({:a => true}), "`qualification :a => true` should redefine `qualifies?` so that it returns true on `qualifies? {:a => true}`"
106
+ assert_equal false, @crosstab.qualifies?({:a => false}), "`qualification :a => true` should redefine `qualifies?` so that it returns false on `qualifies? {:a => false}`"
107
+ end
108
+
109
+ def test_set_banner
110
+ old_banner = @crosstab.banner
111
+ @crosstab.banner do
112
+ # empty on purpose
113
+ end
114
+
115
+ assert_not_same old_banner, @crosstab.banner, "`banner` should create a new banner. It didn't. It still has the same ID."
116
+ end
117
+
118
+ def test_set_table
119
+ @crosstab.table do
120
+ # empty on purpose
121
+ end
122
+
123
+ assert_equal 1, @crosstab.tables.length, "`table {}` should push a new table onto `tables`. `tables` should now have a length of 1."
124
+ assert_kind_of Crosstab::Table, @crosstab.tables.first, "`table {}` should push a new Crosstab::Table object onto `tables`. It didn't."
125
+ end
126
+
127
+ def test_set_printed?
128
+ @crosstab.printed? true
129
+ assert_equal true, @crosstab.printed?
130
+ end
131
+
132
+ # Test the post-calculation methods
133
+ def test_calculate_adds_cells
134
+ columns = @calculated_crosstab.banner.columns
135
+ rows = @calculated_crosstab.tables[0].rows
136
+
137
+ assert_equal true, rows.all? { |row| row.cells.length == columns.length }, "Each row should have a number of cells equal to the number of columns in the banner."
138
+ end
139
+
140
+ def test_calculate_cells_make_sense
141
+
142
+ tables = []
143
+
144
+ tables << []
145
+ tables.last << [{:frequency => 3, :base => 3},
146
+ {:frequency => 3, :base => 3},
147
+ {:frequency => 0, :base => 0}] # Male
148
+
149
+ tables.last << [{:frequency => 0, :base => 3},
150
+ {:frequency => 0, :base => 3},
151
+ {:frequency => 0, :base => 0}] # Female
152
+
153
+ tables << []
154
+ tables.last << [{:frequency => 1, :base => 3},
155
+ {:frequency => 1, :base => 3},
156
+ {:frequency => 0, :base => 0}] # 18-34
157
+ tables.last << [{:frequency => 2, :base => 3},
158
+ {:frequency => 2, :base => 3},
159
+ {:frequency => 0, :base => 0}] # 35+
160
+
161
+ tables << []
162
+ tables.last << [{:frequency => 1, :base => 3},
163
+ {:frequency => 1, :base => 3},
164
+ {:frequency => 0, :base => 0}] # Very likely
165
+ tables.last << [{:frequency => 1, :base => 3},
166
+ {:frequency => 1, :base => 3},
167
+ {:frequency => 0, :base => 0}] # Somewhat likely
168
+ tables.last << [{:frequency => 0, :base => 3},
169
+ {:frequency => 0, :base => 3},
170
+ {:frequency => 0, :base => 0}] # Somewhat unlikely
171
+ tables.last << [{:frequency => 1, :base => 3},
172
+ {:frequency => 1, :base => 3},
173
+ {:frequency => 0, :base => 0}] # Very unlikely
174
+
175
+ tables.each_with_index do |table, table_index|
176
+ table.each_with_index do |row, row_index|
177
+ row.each_with_index do |cell, cell_index|
178
+ cell.each do |method,value|
179
+ assert_equal value, @calculated_crosstab.
180
+ tables[table_index].
181
+ rows[row_index].
182
+ cells[cell_index].
183
+ send(method), "for @calculated_crosstab.tables[#{table_index}].rows[#{row_index}].cells[#{cell_index}].send(#{method.inspect})"
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ def test_calculate_groups
191
+ groups = []
192
+ groups << [{:frequency => 2, :base => 3},
193
+ {:frequency => 2, :base => 3},
194
+ {:frequency => 0, :base => 0}] # Very//somewhat likely
195
+ groups << [{:frequency => 1, :base => 3},
196
+ {:frequency => 1, :base => 3},
197
+ {:frequency => 0, :base => 0}] # Somewhat unlikely
198
+
199
+ groups.each_with_index do |row, index|
200
+ row.each_with_index do |cell, cell_index|
201
+ cell.each do |method, value|
202
+ # index + 1 is a cheat for referencing rows 1 & 2 which both have two different groups.
203
+ assert_equal value,
204
+ @calculated_crosstab.tables[2].rows[index + 1].group.cells[cell_index].send(method)
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ def test_add_tests_for_to_s
211
+ assert false
212
+ end
213
+
214
+ end