ruport 0.6.0 → 0.6.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/lib/ruport/data.rb CHANGED
@@ -1 +1,6 @@
1
- %w[groupable taggable record collection table set ].each { |l| require "ruport/data/#{l}" }
1
+ require "ruport/data/groupable"
2
+ require "ruport/data/taggable"
3
+ require "ruport/data/record"
4
+ require "ruport/data/collection"
5
+ require "ruport/data/table"
6
+ require "ruport/data/set"
@@ -6,7 +6,13 @@
6
6
 
7
7
  module Ruport::Data
8
8
 
9
- # This is the base class for Ruport's Data structures.
9
+ #
10
+ # === Overview
11
+ #
12
+ # This is the base class for Ruport's Data structures. It mixes in the
13
+ # <tt>Taggable</tt> module and provides methods for converting between
14
+ # <tt>Data::Set</tt>s and <tt>Data::Table</tt>s.
15
+ #
10
16
  class Collection
11
17
  require "forwardable"
12
18
  extend Forwardable
@@ -17,28 +23,29 @@ module Ruport::Data
17
23
  @data = data.dup if data
18
24
  end
19
25
 
20
- # Simple formatting tool which allows you to quickly generate a formatted
21
- # table from a Collection object, eg
26
+ # A simple formatting tool which allows you to quickly generate a formatted
27
+ # table from a <tt>Collection</tt> object.
22
28
  #
23
- # my_collection.as(:csv) #=> "1,2,3\n4,5,6"
29
+ # Example:
30
+ # my_collection.as(:csv) #=> "1,2,3\n4,5,6"
24
31
  def as(type)
25
32
  eng = Ruport::Format.table_object :data => self, :plugin => type
26
33
  yield(eng) if block_given?
27
34
  eng.render
28
35
  end
29
36
 
30
- # Converts any Collection object to a Data::Set
37
+ # Converts a <tt>Collection</tt> object to a <tt>Data::Set</tt>.
31
38
  def to_set
32
39
  Set.new :data => data
33
40
  end
34
41
 
35
- # Converts any Collection object to a Data::Table
42
+ # Converts a <tt>Collection</tt> object to a <tt>Data::Table</tt>.
36
43
  def to_table(options={})
37
44
  Table.new({:data => data.map { |r| r.to_a }}.merge(options))
38
45
  end
39
46
 
40
- # Provides a shortcut for the as() method by converting as(:format_name)
41
- # into to_format_name
47
+ # Provides a shortcut for the <tt>as()</tt> method by converting a call to
48
+ # <tt>as(:format_name)</tt> into a call to <tt>to_format_name</tt>
42
49
  def method_missing(id,*args)
43
50
  return as($1.to_sym) if id.to_s =~ /^to_(.*)/
44
51
  super
@@ -1,20 +1,82 @@
1
1
  module Ruport::Data
2
+
3
+ #
4
+ # === Overview
5
+ #
6
+ # This module provides a simple mechanism for grouping objects based on
7
+ # tags.
8
+ #
2
9
  module Groupable
3
10
 
11
+ #
12
+ # Creates a <tt>Record</tt> made up of <tt>Table</tt>s containing all the
13
+ # records in the original table with the same tag.
14
+ #
15
+ # Example:
16
+ # table = [['inky', 1],
17
+ # ['blinky',2],
18
+ # ['pinky', 3],
19
+ # ['clyde', 4]].to_table(['name','score'])
20
+ #
21
+ # table[0].tag(:winners)
22
+ # table[1].tag(:losers)
23
+ # table[2].tag(:winners)
24
+ # table[3].tag(:losers)
25
+ #
26
+ # r = table.group_by_tag
27
+ # puts r[:winners]
28
+ # => +---------------+
29
+ # | name | score |
30
+ # +---------------+
31
+ # | inky | 1 |
32
+ # | pinky | 3 |
33
+ # +---------------+
34
+ #
35
+ # puts r[:losers]
36
+ # => +----------------+
37
+ # | name | score |
38
+ # +----------------+
39
+ # | blinky | 2 |
40
+ # | clyde | 4 |
41
+ # +----------------+
42
+ #
4
43
  def group_by_tag
5
- r_tags = data.map {|row| row.tags}.flatten.uniq
6
- d = r_tags.map do |t|
7
- select {|row| row.tags.include? t }.to_table(column_names)
8
- end
9
- r = Record.new d, :attributes => r_tags
44
+ r_tags = map { |r| r.tags }.flatten.uniq
45
+ tables_hash = Hash.new { |h,k| h[k] = [].to_table(column_names) }
46
+ each { |row|
47
+ row.tags.each { |t| tables_hash[t] << row }
48
+ }
49
+ r = Record.new tables_hash, :attributes => r_tags
10
50
  class << r
11
51
  def each_group; attributes.each { |a| yield(a) }; end
12
52
  end; r
13
53
  end
14
54
 
55
+ #
56
+ # Tags each row of the <tt>Table</tt> for which the <tt>block</tt> is not
57
+ # false with <tt>label</tt>.
58
+ #
59
+ # Example:
60
+ # table = [['inky', 1],
61
+ # ['blinky',2],
62
+ # ['pinky', 3]].to_table(['name','score'])
63
+ #
64
+ # table.create_tag_group(:cool_kids) {|r| r.score > 1}
65
+ # groups = table.group_by_tag(:cool_kids)
66
+ #
67
+ # puts group[:cool_kids]
68
+ # => +----------------+
69
+ # | name | score |
70
+ # +----------------+
71
+ # | blinky | 2 |
72
+ # | pinky | 3 |
73
+ # +----------------+
74
+ #
15
75
  def create_tag_group(label,&block)
16
- select(&block).each { |r| r.tag label }
76
+ each { |r| block[r] && r.tag(label) }
77
+ #select(&block).each { |r| r.tag label }
17
78
  end
79
+ alias_method :tag_group, :create_tag_group
18
80
 
19
81
  end
20
82
  end
@@ -5,21 +5,28 @@
5
5
  # Copyright 2006 by respective content owners, all rights reserved.
6
6
  module Ruport::Data
7
7
 
8
- # Data::Records are the work horse of Ruport's Data model. These can behave
9
- # as array like, hash like, or struct like objects. They are used as the base
10
- # record for both Tables and Sets in Ruport.
8
+ #
9
+ # === Overview
10
+ #
11
+ # Data::Records are the work horse of Ruport's data model. These can behave
12
+ # as Array-like, Hash-like, or Struct-like objects. They are used as the
13
+ # base element in both Tables and Sets.
14
+ #
11
15
  class Record
12
16
  require "forwardable"
13
17
  extend Forwardable
14
18
  include Enumerable
15
19
  include Taggable
16
20
 
17
- # Creates a new Record object. If the <tt>:attributes</tt> keyword is
18
- # specified, Hash-like and Struct-like access will be enabled. Otherwise,
19
- # Record elements may be accessed ordinally, like an Array.
20
21
  #
21
- # Records accept either Hashes or Arrays as their data.
22
+ # Creates a new Record object. If the <tt>:attributes</tt>
23
+ # keyword is specified, Hash-like and Struct-like
24
+ # access will be enabled. Otherwise, Record elements may be
25
+ # accessed ordinally, like an Array.
26
+ #
27
+ # A Record can accept either a Hash or an Array as its <tt>data</tt>.
22
28
  #
29
+ # Examples:
23
30
  # a = Record.new [1,2,3]
24
31
  # a[1] #=> 2
25
32
  #
@@ -37,6 +44,7 @@ module Ruport::Data
37
44
  # b[1] #=> ? (without attributes, you cannot rely on order)
38
45
  # b['a'] #=> 1
39
46
  # b.c #=> 3
47
+ #
40
48
  def initialize(data,options={})
41
49
  if data.kind_of?(Hash)
42
50
  if options[:attributes]
@@ -55,18 +63,19 @@ module Ruport::Data
55
63
  end
56
64
  end
57
65
 
58
- # The underlying data which is being stored in the record
66
+ # The underlying <tt>data</tt> which is being stored in the record.
59
67
  attr_reader :data
60
68
 
61
69
  def_delegators :@data,:each, :length
62
70
 
63
- # Allows either array or hash_like indexing
71
+ #
72
+ # Allows either Array or Hash-like indexing.
73
+ #
74
+ # Examples:
64
75
  #
65
76
  # my_record[1]
66
77
  # my_record["foo"]
67
78
  #
68
- # Also, this provides a feature via method_missing which allows
69
- # my_record.foo
70
79
  def [](index)
71
80
  if index.kind_of? Integer
72
81
  raise "Invalid index" unless index < @data.length
@@ -78,13 +87,14 @@ module Ruport::Data
78
87
  end
79
88
 
80
89
 
81
- # Allows setting a value at an index
82
- #
90
+ #
91
+ # Allows setting a <tt>value</tt> at an <tt>index</tt>.
92
+ #
93
+ # Examples:
94
+ #
83
95
  # my_record[1] = "foo"
84
96
  # my_record["bar"] = "baz"
85
97
  #
86
- # And via method_missing
87
- # my_record.ghost = "blinky"
88
98
  def []=(index, value)
89
99
  if index.kind_of? Integer
90
100
  raise "Invalid index" unless index < @data.length
@@ -95,9 +105,10 @@ module Ruport::Data
95
105
  end
96
106
  end
97
107
 
98
-
99
- # If attributes and data are equivalent, then == evaluates to true.
100
- # Otherwise, == returns false
108
+ #
109
+ # If <tt>attributes</tt> and <tt>data</tt> are equivalent, then
110
+ # <tt>==</tt> evaluates to true. Otherwise, <tt>==</tt> returns false.
111
+ #
101
112
  def ==(other)
102
113
  return false if attributes && !other.attributes
103
114
  return false if other.attributes && !attributes
@@ -106,43 +117,62 @@ module Ruport::Data
106
117
 
107
118
  alias_method :eql?, :==
108
119
 
109
- # Makes an array out of the data wrapped by Record
120
+ #
121
+ # Converts a Record into an Array.
122
+ #
123
+ # Example:
110
124
  #
111
125
  # a = Data::Record.new([1,2],:attributes => %w[a b])
112
126
  # a.to_a #=> [1,2]
127
+ #
113
128
  def to_a; @data.dup; end
114
-
115
- # Makes a hash out of the data wrapped by Record
116
- # Only works if attributes are specified
129
+
130
+ #
131
+ # Converts a Record into a Hash. This only works if <tt>attributes</tt>
132
+ # are specified in the Record.
133
+ #
134
+ # Example:
117
135
  #
118
136
  # a = Data::Record.new([1,2],:attributes => %w[a b])
119
137
  # a.to_h #=> {"a" => 1, "b" => 2}
138
+ #
120
139
  def to_h; Hash[*attributes.zip(data).flatten] end
121
140
 
122
- # Returns a copy of the list of attribute names associated with this Record.
141
+ #
142
+ # Returns a copy of the <tt>attributes</tt> from this Record.
143
+ #
144
+ # Example:
123
145
  #
124
146
  # a = Data::Record.new([1,2],:attributes => %w[a b])
125
147
  # a.attributes #=> ["a","b"]
148
+ #
126
149
  def attributes; @attributes.map { |a| a.to_s }; end
127
150
 
128
- # Sets the attribute list for this Record
151
+ #
152
+ # Sets the <tt>attribute</tt> list for this Record.
153
+ #
154
+ # Example:
129
155
  #
130
156
  # my_record.attributes = %w[foo bar baz]
157
+ #
131
158
  attr_writer :attributes
132
159
 
133
-
160
+ #
134
161
  # Allows you to change the order of or reduce the number of columns in a
135
- # Record. Example:
162
+ # Record.
163
+ #
164
+ # Example:
136
165
  #
137
166
  # a = Data::Record.new([1,2,3,4],:attributes => %w[a b c d])
138
167
  # b = a.reorder("a","d","b")
139
168
  # b.attributes #=> ["a","d","b"]
140
169
  # b.data #=> [1,4,2]
170
+ #
141
171
  def reorder(*indices)
142
172
  dup.reorder!(*indices)
143
173
  end
144
174
 
145
- # Same as Record#reorder but is destructive
175
+ # Same as Record#reorder but modifies its reciever in place.
146
176
  def reorder!(*indices)
147
177
  indices = reorder_data!(*indices)
148
178
  if attributes
@@ -154,7 +184,7 @@ module Ruport::Data
154
184
  end; self
155
185
  end
156
186
 
157
- def reorder_data!(*indices)
187
+ def reorder_data!(*indices) # :nodoc:
158
188
  indices = indices[0] if indices[0].kind_of?(Array)
159
189
  indices.each do |i|
160
190
  self[i] rescue raise ArgumentError,
@@ -165,7 +195,7 @@ module Ruport::Data
165
195
  end
166
196
 
167
197
 
168
- # Makes a fresh copy of the Record
198
+ # Makes a fresh copy of the Record.
169
199
  def dup
170
200
  copy = self.class.new(@data,:attributes => attributes)
171
201
  copy.tags = self.tags.dup
@@ -175,21 +205,30 @@ module Ruport::Data
175
205
  #FIXME: This does not take into account frozen / tainted state
176
206
  alias_method :clone, :dup
177
207
 
178
- # provides a unique hash value
208
+ #
209
+ # Provides a unique hash value. If a Record contains the same data and
210
+ # attributes as another Record, they will hash to the same value, even if
211
+ # they are not the same object. This is similar to the way Array works,
212
+ # but different from Hash and other objects.
213
+ #
179
214
  def hash
180
215
  (attributes.to_a + data.to_a).hash
181
216
  end
182
217
 
183
- # provides accessor style methods for attribute access, Example
218
+ #
219
+ # Provides accessor style methods for attribute access.
220
+ #
221
+ # Example:
184
222
  #
185
223
  # my_record.foo = 2
186
224
  # my_record.foo #=> 2
187
- def method_missing(id,*args)
188
- id = id.to_s.gsub(/=$/,"")
225
+ #
226
+ def method_missing(mname, *args)
227
+ id = mname.to_s.gsub(/=$/,"")
189
228
  if attributes.include?(id)
190
229
  args.empty? ? self[id] : self[id] = args.first
191
230
  else
192
- super
231
+ super(mname, *args)
193
232
  end
194
233
  end
195
234
 
@@ -0,0 +1,236 @@
1
+ # The Ruport Data Collections.
2
+ # Authors: Gregory Brown / Dudley Flanders
3
+ #
4
+ # This is Free Software. For details, see LICENSE and COPYING
5
+ # Copyright 2006 by respective content owners, all rights reserved.
6
+ module Ruport::Data
7
+
8
+ #
9
+ # === Overview
10
+ #
11
+ # Data::Records are the work horse of Ruport's data model. These can behave
12
+ # as Array-like, Hash-like, or Struct-like objects. They are used as the
13
+ # base element in both Tables and Sets.
14
+ #
15
+ class Record
16
+ require "forwardable"
17
+ extend Forwardable
18
+ include Enumerable
19
+ include Taggable
20
+
21
+ #
22
+ # Creates a new Record object. If the <tt>:attributes</tt>
23
+ # keyword is specified, Hash-like and Struct-like
24
+ # access will be enabled. Otherwise, Record elements may be
25
+ # accessed ordinally, like an Array.
26
+ #
27
+ # A Record can accept either a Hash or an Array as its <tt>data</tt>.
28
+ #
29
+ # Examples:
30
+ # a = Record.new [1,2,3]
31
+ # a[1] #=> 2
32
+ #
33
+ # b = Record.new [1,2,3], :attributes => %w[a b c]
34
+ # b[1] #=> 2
35
+ # b['a'] #=> 1
36
+ # b.c #=> 3
37
+ #
38
+ # c = Record.new {"a" => 1, "c" => 3, "b" => 2}, :attributes => %w[a b c]
39
+ # b[1] #=> 2
40
+ # b['a'] #=> 1
41
+ # b.c #=> 3
42
+ #
43
+ # c = Record.new { "a" => 1, "c" => 3, "b" => 2 }
44
+ # b[1] #=> ? (without attributes, you cannot rely on order)
45
+ # b['a'] #=> 1
46
+ # b.c #=> 3
47
+ #
48
+ def initialize(data,options={})
49
+ if data.kind_of?(Hash)
50
+ if options[:attributes]
51
+ @attributes = options[:attributes]
52
+ @data = options[:attributes].map { |k| data[k] }
53
+ else
54
+ @attributes, @data = data.to_a.transpose
55
+ end
56
+ else
57
+ @data = data.dup
58
+ @attributes = if options[:attributes]
59
+ options[:attributes]
60
+ else
61
+ []
62
+ end
63
+ end
64
+ end
65
+
66
+ # The underlying <tt>data</tt> which is being stored in the record.
67
+ attr_reader :data
68
+
69
+ def_delegators :@data,:each, :length
70
+
71
+ #
72
+ # Allows either Array or Hash-like indexing.
73
+ #
74
+ # Examples:
75
+ #
76
+ # my_record[1]
77
+ # my_record["foo"]
78
+ #
79
+ def [](index)
80
+ if index.kind_of? Integer
81
+ raise "Invalid index" unless index < @data.length
82
+ @data[index]
83
+ else
84
+ raise "Invalid index" unless attributes.index(index.to_s)
85
+ @data[attributes.index(index.to_s)]
86
+ end
87
+ end
88
+
89
+
90
+ #
91
+ # Allows setting a <tt>value</tt> at an <tt>index</tt>.
92
+ #
93
+ # Examples:
94
+ #
95
+ # my_record[1] = "foo"
96
+ # my_record["bar"] = "baz"
97
+ #
98
+ def []=(index, value)
99
+ if index.kind_of? Integer
100
+ raise "Invalid index" unless index < @data.length
101
+ @data[index] = value
102
+ else
103
+ raise "Invalid index" unless attributes.index(index.to_s)
104
+ @data[attributes.index(index.to_s)] = value
105
+ end
106
+ end
107
+
108
+ #
109
+ # If <tt>attributes</tt> and <tt>data</tt> are equivalent, then
110
+ # <tt>==</tt> evaluates to true. Otherwise, <tt>==</tt> returns false.
111
+ #
112
+ def ==(other)
113
+ return false if attributes && !other.attributes
114
+ return false if other.attributes && !attributes
115
+ attributes == other.attributes && @data == other.data
116
+ end
117
+
118
+ alias_method :eql?, :==
119
+
120
+ #
121
+ # Converts a Record into an Array.
122
+ #
123
+ # Example:
124
+ #
125
+ # a = Data::Record.new([1,2],:attributes => %w[a b])
126
+ # a.to_a #=> [1,2]
127
+ #
128
+ def to_a; @data.dup; end
129
+
130
+ #
131
+ # Converts a Record into a Hash. This only works if <tt>attributes</tt>
132
+ # are specified in the Record.
133
+ #
134
+ # Example:
135
+ #
136
+ # a = Data::Record.new([1,2],:attributes => %w[a b])
137
+ # a.to_h #=> {"a" => 1, "b" => 2}
138
+ #
139
+ def to_h; Hash[*attributes.zip(data).flatten] end
140
+
141
+ #
142
+ # Returns a copy of the <tt>attributes</tt> from this Record.
143
+ #
144
+ # Example:
145
+ #
146
+ # a = Data::Record.new([1,2],:attributes => %w[a b])
147
+ # a.attributes #=> ["a","b"]
148
+ #
149
+ def attributes; @attributes.map { |a| a.to_s }; end
150
+
151
+ #
152
+ # Sets the <tt>attribute</tt> list for this Record.
153
+ #
154
+ # Example:
155
+ #
156
+ # my_record.attributes = %w[foo bar baz]
157
+ #
158
+ attr_writer :attributes
159
+
160
+ #
161
+ # Allows you to change the order of or reduce the number of columns in a
162
+ # Record.
163
+ #
164
+ # Example:
165
+ #
166
+ # a = Data::Record.new([1,2,3,4],:attributes => %w[a b c d])
167
+ # b = a.reorder("a","d","b")
168
+ # b.attributes #=> ["a","d","b"]
169
+ # b.data #=> [1,4,2]
170
+ #
171
+ def reorder(*indices)
172
+ dup.reorder!(*indices)
173
+ end
174
+
175
+ # Same as Record#reorder but modifies its reciever in place.
176
+ def reorder!(*indices)
177
+ indices = reorder_data!(*indices)
178
+ if attributes
179
+ if indices.all? { |e| e.kind_of? Integer }
180
+ @attributes = indices.map { |i| attributes[i] }
181
+ else
182
+ @attributes = indices
183
+ end
184
+ end; self
185
+ end
186
+
187
+ def reorder_data!(*indices) # :nodoc:
188
+ indices = indices[0] if indices[0].kind_of?(Array)
189
+ indices.each do |i|
190
+ self[i] rescue raise ArgumentError,
191
+ "you may have specified an invalid column"
192
+ end
193
+ @data = indices.map { |i| self[i] }
194
+ return indices;
195
+ end
196
+
197
+
198
+ # Makes a fresh copy of the Record.
199
+ def dup
200
+ copy = self.class.new(@data,:attributes => attributes)
201
+ copy.tags = self.tags.dup
202
+ return copy
203
+ end
204
+
205
+ #FIXME: This does not take into account frozen / tainted state
206
+ alias_method :clone, :dup
207
+
208
+ #
209
+ # Provides a unique hash value. If a Record contains the same data and
210
+ # attributes as another Record, they will hash to the same value, even if
211
+ # they are not the same object. This is similar to the way Array works,
212
+ # but different from Hash and other objects.
213
+ #
214
+ def hash
215
+ (attributes.to_a + data.to_a).hash
216
+ end
217
+
218
+ #
219
+ # Provides accessor style methods for attribute access.
220
+ #
221
+ # Example:
222
+ #
223
+ # my_record.foo = 2
224
+ # my_record.foo #=> 2
225
+ #
226
+ def method_missing(id,*args)
227
+ id = id.to_s.gsub(/=$/,"")
228
+ if attributes.include?(id)
229
+ args.empty? ? self[id] : self[id] = args.first
230
+ else
231
+ super
232
+ end
233
+ end
234
+
235
+ end
236
+ end