ruport 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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