crosstab 0.1.0

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.
@@ -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