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/AUTHORS +4 -0
- data/CHANGELOG +10 -1
- data/Rakefile +1 -1
- data/examples/simple_table_interface.rb +20 -0
- data/lib/ruport.rb +87 -69
- data/lib/ruport/attempt.rb +1 -1
- data/lib/ruport/config.rb +198 -166
- data/lib/ruport/data.rb +6 -1
- data/lib/ruport/data/collection.rb +15 -8
- data/lib/ruport/data/groupable.rb +68 -6
- data/lib/ruport/data/record.rb +73 -34
- data/lib/ruport/data/record.rb~ +236 -0
- data/lib/ruport/data/set.rb +48 -13
- data/lib/ruport/data/table.rb +164 -74
- data/lib/ruport/data/table.rb.rej +67 -0
- data/lib/ruport/data/table.rb~ +153 -68
- data/lib/ruport/data/taggable.rb +37 -9
- data/lib/ruport/format.rb +1 -1
- data/lib/ruport/mailer.rb +41 -27
- data/lib/ruport/meta_tools.rb +26 -11
- data/lib/ruport/query.rb +102 -68
- data/lib/ruport/query/sql_split.rb +1 -1
- data/lib/ruport/report.rb +84 -58
- data/lib/ruport/report/graph.rb +1 -1
- data/lib/ruport/report/invoice.rb +1 -1
- data/test/test_query.rb +305 -48
- data/test/test_query.rb.rej +161 -0
- data/test/test_query.rb~ +337 -0
- data/test/test_record.rb +6 -0
- data/test/test_table.rb +18 -0
- data/test/test_table.rb~ +336 -0
- data/test/unit.log +180 -6
- metadata +8 -2
data/lib/ruport/data.rb
CHANGED
@@ -1 +1,6 @@
|
|
1
|
-
|
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
|
-
#
|
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
|
-
#
|
21
|
-
# table from a Collection object
|
26
|
+
# A simple formatting tool which allows you to quickly generate a formatted
|
27
|
+
# table from a <tt>Collection</tt> object.
|
22
28
|
#
|
23
|
-
#
|
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
|
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
|
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
|
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 =
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
data/lib/ruport/data/record.rb
CHANGED
@@ -5,21 +5,28 @@
|
|
5
5
|
# Copyright 2006 by respective content owners, all rights reserved.
|
6
6
|
module Ruport::Data
|
7
7
|
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
100
|
-
# Otherwise,
|
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
|
-
#
|
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
|
-
#
|
116
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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.
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
188
|
-
|
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
|