ruport 0.2.9 → 0.3.8
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/ACKNOWLEDGEMENTS +33 -0
- data/AUTHORS +13 -1
- data/CHANGELOG +76 -1
- data/README +208 -89
- data/Rakefile +12 -8
- data/TODO +14 -122
- data/lib/ruport.rb +58 -0
- data/lib/ruport/config.rb +114 -0
- data/lib/ruport/data_row.rb +144 -0
- data/lib/ruport/data_set.rb +221 -0
- data/lib/ruport/format.rb +116 -0
- data/lib/ruport/format/builder.rb +29 -5
- data/lib/ruport/format/document.rb +77 -0
- data/lib/ruport/format/open_node.rb +36 -0
- data/lib/ruport/parser.rb +202 -0
- data/lib/ruport/query.rb +208 -0
- data/lib/ruport/query/sql_split.rb +33 -0
- data/lib/ruport/report.rb +116 -0
- data/lib/ruport/report/mailer.rb +17 -15
- data/test/{addressbook.csv → samples/addressbook.csv} +0 -0
- data/test/samples/car_ads.txt +505 -0
- data/test/{data.csv → samples/data.csv} +0 -0
- data/test/samples/document.xml +22 -0
- data/test/samples/five_lines.txt +5 -0
- data/test/samples/five_paragraphs.txt +9 -0
- data/test/samples/ross_report.txt +58530 -0
- data/test/samples/ruport_test.sql +8 -0
- data/test/samples/stonecodeblog.sql +279 -0
- data/test/{test.sql → samples/test.sql} +2 -1
- data/test/{test.yaml → samples/test.yaml} +0 -0
- data/test/tc_builder.rb +7 -4
- data/test/tc_config.rb +41 -0
- data/test/tc_data_row.rb +16 -26
- data/test/tc_data_set.rb +60 -41
- data/test/tc_database.rb +25 -0
- data/test/tc_document.rb +42 -0
- data/test/tc_element.rb +18 -0
- data/test/tc_page.rb +42 -0
- data/test/tc_query.rb +55 -0
- data/test/tc_reading.rb +60 -0
- data/test/tc_report.rb +31 -0
- data/test/tc_section.rb +45 -0
- data/test/tc_sql_split.rb +18 -0
- data/test/tc_state.rb +142 -0
- data/test/ts_all.rb +6 -3
- data/test/ts_format.rb +5 -0
- data/test/ts_parser.rb +10 -0
- metadata +102 -60
- data/bin/ruport +0 -104
- data/lib/ruport/format/chart.rb +0 -1
- data/lib/ruport/report/data_row.rb +0 -79
- data/lib/ruport/report/data_set.rb +0 -153
- data/lib/ruport/report/engine.rb +0 -201
- data/lib/ruport/report/fake_db.rb +0 -54
- data/lib/ruport/report/fake_engine.rb +0 -26
- data/lib/ruport/report/fake_mailer.rb +0 -23
- data/lib/ruport/report/sql.rb +0 -95
- data/lib/ruportlib.rb +0 -11
- data/test/tc_engine.rb +0 -102
- data/test/tc_mailer.rb +0 -21
data/TODO
CHANGED
@@ -1,135 +1,27 @@
|
|
1
|
-
TODO:
|
1
|
+
TODO: (Wiped clean for a fresh start as of 2006.02.20)
|
2
2
|
|
3
|
-
|
4
|
-
- Fix the manual that is in the example package so it it reads FakeDB instead
|
5
|
-
of MockDB. Update the manual to cover new features in 0.3.0
|
3
|
+
For Ruport 0.4.0
|
6
4
|
|
7
|
-
|
8
|
-
creations. Gotta work out the kinks here.
|
9
|
-
|
10
|
-
Improvement:
|
5
|
+
- Integrate Ruport::Parser into Report#parse and Format#parser
|
11
6
|
|
12
|
-
-
|
7
|
+
- Document the inner classes of Format
|
13
8
|
|
14
|
-
-
|
9
|
+
- Get unit tests up to 100% coverage
|
15
10
|
|
16
|
-
-
|
17
|
-
Make attachments doable
|
11
|
+
- make the Fetchable module to abstract data acquisition
|
18
12
|
|
19
|
-
|
13
|
+
High Priority Goals:
|
20
14
|
|
21
|
-
|
22
|
-
Make line editing work right
|
23
|
-
|
24
|
-
- Queries Table System
|
15
|
+
- Implement some aspects of XST into DataSet
|
25
16
|
|
26
|
-
|
27
|
-
Hook up unit tests.
|
17
|
+
- Offer charting support in Format
|
28
18
|
|
29
|
-
|
19
|
+
Community Requests:
|
30
20
|
|
31
|
-
|
32
|
-
Form better more complete unit tests.
|
21
|
+
- Integration with Rails (ActiveRecord)
|
33
22
|
|
34
|
-
|
23
|
+
Other (Unordered) Goals:
|
35
24
|
|
36
|
-
|
25
|
+
- Make mailer more robust via MailFactory
|
37
26
|
|
38
|
-
-
|
39
|
-
|
40
|
-
* Implement RSS generation for Format::Builder
|
41
|
-
|
42
|
-
- Charts:
|
43
|
-
|
44
|
-
Build something that'll take a data set and build a chart. (SVG?),
|
45
|
-
then dump it into PDF::Writer.
|
46
|
-
|
47
|
-
- Multiple query reports:
|
48
|
-
|
49
|
-
Allow the user to make arbitrary SQL queries,
|
50
|
-
combining the rows, and then iterating through them row by row in the
|
51
|
-
order specified.
|
52
|
-
|
53
|
-
This could be done with select taking an array of queries and possibly
|
54
|
-
some ordering instructions
|
55
|
-
|
56
|
-
- Cross database import from file:
|
57
|
-
|
58
|
-
Make an import(file,table,delimiter=",") command that will work
|
59
|
-
regardless of database choice and import the data stored in a file into
|
60
|
-
a specified table.
|
61
|
-
|
62
|
-
- Parse/Input tight integration:
|
63
|
-
|
64
|
-
Write wrapper functions over the Parse/Input library to give Ruport the
|
65
|
-
power to feed in any data source such as a CSV, A website, or a log file
|
66
|
-
and do formatting and use the other features of Ruport.
|
67
|
-
|
68
|
-
- PDF::Writer and CSV convenience methods:
|
69
|
-
|
70
|
-
Create functions that will allow basic and common reports to be built
|
71
|
-
using PDF and CSV format without having to write the functionality over
|
72
|
-
and over again. This would be especially useful for just dumping a
|
73
|
-
table's values in a certain order with some fields removed.
|
74
|
-
|
75
|
-
[UPDATE: to_csv is in place but can use some additional frosting]
|
76
|
-
|
77
|
-
- Logger / Exception handler:
|
78
|
-
|
79
|
-
Create a system to handle and log errors as well as provide log messages
|
80
|
-
regarding what Ruport does when a template is run.
|
81
|
-
|
82
|
-
[UPDATE: Logger is in place but is not covering many functions]
|
83
|
-
|
84
|
-
- DataSets should implement tagging, allowing tag conditions to be passed and
|
85
|
-
then applied to rows. Formulas should be implemented, allowing column
|
86
|
-
generation by Proc. More about this later. (These are COOL and POWERFUL
|
87
|
-
features... thanks to Greg Gibson for the ideas.
|
88
|
-
|
89
|
-
|
90
|
-
Design considerations:
|
91
|
-
|
92
|
-
Ruport will eventually need to be cleanly seperating between formatting
|
93
|
-
tools and data feeders. This will likely happen in an early release, if not
|
94
|
-
the next than the one after.
|
95
|
-
|
96
|
-
[ UPDATE: The folders have been seperated but need reworking. db/mailer ??
|
97
|
-
Ruport needs to be made into a module ]
|
98
|
-
|
99
|
-
Ruport trys to use a lot of 'intelligent' defaults but it might try to be
|
100
|
-
too clever in some places making it not clever at all. The community
|
101
|
-
reaction will determine what places may need opening up more or need more
|
102
|
-
consideration for the defaults.
|
103
|
-
|
104
|
-
[ UPDATE: See changelog for a list of dropped features. Complain about any
|
105
|
-
that you still think need to be dropped ]
|
106
|
-
|
107
|
-
Other:
|
108
|
-
|
109
|
-
A quick reference page for templates and/or tutorial would be A Good Thing
|
110
|
-
|
111
|
-
The RDOC sucks!
|
112
|
-
|
113
|
-
The unit tests need to be wired so that they can actually run without a ton
|
114
|
-
of additional steps, they need to be expanded to cover the whole system, and
|
115
|
-
they need to work in the Gem package as well as the source package.
|
116
|
-
|
117
|
-
[UPDATE: Mailer is the only class that needs to be unit tested still. The
|
118
|
-
units work out of the box via MockDB now. Functional tests with
|
119
|
-
scaffolding are needed, though ]
|
120
|
-
|
121
|
-
Ruport needs to be modified to support as many databases as possibly
|
122
|
-
internally. The system was written using MySQL on Gentoo Linux
|
123
|
-
and Mac OS X.3 / OS X.4, Windows 2000 / XP and Microsoft SQL Server
|
124
|
-
on Windows 2000 (via ODBC),
|
125
|
-
|
126
|
-
so the support for these platforms are best. An effort will be given to
|
127
|
-
make Ruport less hostile in other environments.
|
128
|
-
|
129
|
-
|
130
|
-
This is only the tip of the iceburg. Please feel free to continue to fill my
|
131
|
-
plate by sending any suggestions to gregory.t.brown@gmail.com
|
132
|
-
|
133
|
-
JEG2 Code Review 2005.11.14: (email me if you find any of this interesting)
|
134
|
-
- use the many levels of logger
|
135
|
-
- RQL (Ruport Query Language)
|
27
|
+
- Support KirbyBase
|
data/lib/ruport.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
# ruport.rb : Ruby Reports toplevel module
|
3
|
+
#
|
4
|
+
# Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
|
5
|
+
#
|
6
|
+
# Copyright (c) 2006, All Rights Reserved.
|
7
|
+
#
|
8
|
+
# This is free software. You may modify and redistribute this freely under
|
9
|
+
# your choice of the GNU General Public License or the Ruby License.
|
10
|
+
#
|
11
|
+
# See LICENSE and COPYING for details
|
12
|
+
#
|
13
|
+
|
14
|
+
module Ruport
|
15
|
+
VERSION = "Ruport Version 0.3.8 (Developmental)"
|
16
|
+
|
17
|
+
# Ruports logging and error interface.
|
18
|
+
# Can generate warnings or raise fatal errors
|
19
|
+
#
|
20
|
+
# Takes a message to display and a set of options.
|
21
|
+
# Will log to the file defined by Config::log_file
|
22
|
+
#
|
23
|
+
# Options:
|
24
|
+
# <tt>:status</tt>:: sets the severity level. defaults to <tt>:warn</tt>
|
25
|
+
# <tt>:output</tt>:: optional secondary output, defaults to <tt>$stderr</tt>
|
26
|
+
# <tt>:level</tt>:: set to <tt>:log_only</tt> to disable secondary output
|
27
|
+
# <tt>:exception</tt>:: exception to throw on fail. Defaults to RunTimeError
|
28
|
+
#
|
29
|
+
# The status <tt>:warn</tt> will invoke Logger#warn. A status of
|
30
|
+
# <tt>:fatal</tt> will invoke Logger#fatal and raise an exception
|
31
|
+
#
|
32
|
+
# By default, complain will also print warnings to $stderr
|
33
|
+
# You can redirect this to any I/O object via <tt>:output</tt>
|
34
|
+
#
|
35
|
+
# You can prevent messages from appearing on the secondary output by setting
|
36
|
+
# <tt>:level</tt> to <tt>:log_only</tt>
|
37
|
+
#
|
38
|
+
# If you want to recover these messages to secondary output for debugging, you
|
39
|
+
# can use Config::enable_paranoia
|
40
|
+
def Ruport.complain(message,options={})
|
41
|
+
options[:status] ||= :warn
|
42
|
+
options[:output] ||= $stderr
|
43
|
+
case(options[:status])
|
44
|
+
when :warn
|
45
|
+
Ruport::Config::logger.warn(message) if Ruport::Config::logger
|
46
|
+
when :fatal
|
47
|
+
Ruport::Config::logger.fatal(message) if Ruport::Config::logger
|
48
|
+
raise options[:exception] || RuntimeError, message
|
49
|
+
end
|
50
|
+
options[:output].puts "[!!] #{message}" unless
|
51
|
+
options[:level].eql?(:log_only) and not Ruport::Config.paranoid?
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
%w[ config report format query data_row data_set].each { |lib|
|
57
|
+
require "ruport/#{lib}"
|
58
|
+
}
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# ruport/config.rb : Ruby Reports configuration system
|
2
|
+
#
|
3
|
+
# Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2006, All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. You may modify and redistribute this freely under
|
8
|
+
# your choice of the GNU General Public License or the Ruby License.
|
9
|
+
#
|
10
|
+
# See LICENSE and COPYING for details
|
11
|
+
#
|
12
|
+
require "singleton"
|
13
|
+
require "ostruct"
|
14
|
+
module Ruport
|
15
|
+
# This class serves as the configuration system for Ruport.
|
16
|
+
# It's functionality is implemented through Config::method_missing
|
17
|
+
#
|
18
|
+
# source :default and mailer :default will become the fallback values if one
|
19
|
+
# is not specified in Report::Mailer or Query, but you may define as many
|
20
|
+
# sources as you like and switch between them later.
|
21
|
+
#
|
22
|
+
# An example config file is shown below:
|
23
|
+
#
|
24
|
+
# # password is optional, dsn may omit hostname for localhost
|
25
|
+
# Ruport::Config.source :default,
|
26
|
+
# :dsn => "dbi:mysql:somedb:db.blixy.org", :user => "root", :password => "chunky_bacon"
|
27
|
+
#
|
28
|
+
# # :password, :port, and :auth_type are optional. :port defaults to 25 and
|
29
|
+
# # :auth_type defaults to :plain. For more information, see the source
|
30
|
+
# # of Report::Mailer#select_mailer
|
31
|
+
# Ruport::Config.mailer :default,
|
32
|
+
# :host => "mail.chunkybacon.org", :address => "chunky@bacon.net",
|
33
|
+
# :user => "cartoon", :password => "fox", :port => 25, :auth_type => :login
|
34
|
+
#
|
35
|
+
# # optional, if specifed, Ruport#complain will report to it
|
36
|
+
# Ruport::Config.log_file 'foo.log'
|
37
|
+
#
|
38
|
+
# # optional, if enabled, will force :log_only complaint calls to
|
39
|
+
# # print to secondary output ($sterr by default).
|
40
|
+
# # call Ruport::Config.disable_paranoia to disable
|
41
|
+
# Ruport::Config.enable_paranoia
|
42
|
+
#
|
43
|
+
# Alternatively, this configuration could be done by opening the class:
|
44
|
+
# class Ruport::Config
|
45
|
+
#
|
46
|
+
# source :default, :dsn => "dbi:mysql:some_db", :user => "root"
|
47
|
+
#
|
48
|
+
# mailer :default, :host => "mail.iheartwhy.com",
|
49
|
+
# :address => "sandal@ruby-harmonix.net", :user => "sandal",
|
50
|
+
# :password => "abc123"
|
51
|
+
#
|
52
|
+
# logfile 'foo.log'
|
53
|
+
#
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# Saving this config information into a file and then requiring it can allow
|
57
|
+
# you share configurations between Ruport applications.
|
58
|
+
#
|
59
|
+
class Config
|
60
|
+
include Singleton
|
61
|
+
|
62
|
+
def Config.method_missing(method_id,*args)
|
63
|
+
case(method_id)
|
64
|
+
when :source
|
65
|
+
return @@sources[args.first] if args.length == 1
|
66
|
+
@@sources[args.first] = OpenStruct.new(*args[1..-1])
|
67
|
+
unless @@sources[args.first].send(:dsn)
|
68
|
+
Ruport.complain("Bad or missing DSN for source #{args.first}!")
|
69
|
+
end
|
70
|
+
when :mailer
|
71
|
+
@@mailers[args.first] = OpenStruct.new(*args[1..-1])
|
72
|
+
when :log_file
|
73
|
+
@@logger = Logger.new(args.first)
|
74
|
+
when :default_source
|
75
|
+
@@sources[:default]
|
76
|
+
when :default_mailer
|
77
|
+
@@mailers[:default]
|
78
|
+
when :sources
|
79
|
+
@@sources
|
80
|
+
when :mailers
|
81
|
+
@@mailers
|
82
|
+
when :logger
|
83
|
+
@@logger
|
84
|
+
when :enable_paranoia
|
85
|
+
@@paranoid = true
|
86
|
+
when :disable_paranoia
|
87
|
+
@@paranoid = false
|
88
|
+
when :paranoid?
|
89
|
+
@@paranoid
|
90
|
+
else
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def Config.init!
|
98
|
+
@@sources = { :default =>
|
99
|
+
OpenStruct.new( :dsn => "ruport",
|
100
|
+
:user => "",
|
101
|
+
:password => ""
|
102
|
+
)
|
103
|
+
}
|
104
|
+
@@mailers = { :default => nil }
|
105
|
+
@@logger ||= nil
|
106
|
+
@@paranoid ||= false
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
init!
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# --
|
2
|
+
# data_row.rb : Ruby Reports row abstraction
|
3
|
+
#
|
4
|
+
# Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
|
5
|
+
#
|
6
|
+
# Copyright (c) 2006, All Rights Reserved.
|
7
|
+
#
|
8
|
+
# This is free software. You may modify and redistribute this freely under
|
9
|
+
# your choice of the GNU General Public License or the Ruby License.
|
10
|
+
#
|
11
|
+
# See LICENSE and COPYING for details
|
12
|
+
# ++
|
13
|
+
module Ruport
|
14
|
+
|
15
|
+
# DataRows are Enumerable lists which can be accessed by field name or ordinal
|
16
|
+
# position.
|
17
|
+
#
|
18
|
+
# They feature a tagging system, allowing them to be easily
|
19
|
+
# compared or recalled.
|
20
|
+
#
|
21
|
+
# DataRows form the elements of DataSets
|
22
|
+
#
|
23
|
+
class DataRow
|
24
|
+
|
25
|
+
include Enumerable
|
26
|
+
|
27
|
+
# Takes data and field names as well as some optional parameters and
|
28
|
+
# constructs a DataRow.
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# <tt>data</tt> can be specified in Hash, Array, or DataRow form
|
32
|
+
#
|
33
|
+
# Options:
|
34
|
+
# <tt>:filler</tt>:: this will be used as a default value for empty
|
35
|
+
# <tt>:tags</tt>:: an initial set of tags for the row
|
36
|
+
#
|
37
|
+
#
|
38
|
+
# Examples:
|
39
|
+
# >> Ruport::DataRow.new [1,2,3,4,5], [:a,:b,:c,:d,:e],
|
40
|
+
# :tags => %w[cat dog]
|
41
|
+
# => #<Ruport::DataRow:0xb77e4b04 @fields=[:a, :b, :c, :d, :e],
|
42
|
+
# @data=[1, 2, 3, 4, 5], @tags=["cat", "dog"]>
|
43
|
+
#
|
44
|
+
# >> Ruport::DataRow.new({ :a => 'moo', :c => 'caw'} , [:a,:b,:c,:d,:e],
|
45
|
+
# :tags => %w[cat dog])
|
46
|
+
# => #<Ruport::DataRow:0xb77c298c @fields=[:a, :b, :c, :d, :e],
|
47
|
+
# @data=["moo", nil, "caw", nil, nil], @tags=["cat", "dog"]>
|
48
|
+
#
|
49
|
+
# >> Ruport::DataRow.new [1,2,3], [:a,:b,:c,:d,:e], :tags => %w[cat dog],
|
50
|
+
# :filler => 0
|
51
|
+
# => #<Ruport::DataRow:0xb77bb4d4 @fields=[:a, :b, :c, :d, :e],
|
52
|
+
# @data=[1, 2, 3, 0, 0], @tags=["cat", "dog"]>
|
53
|
+
#
|
54
|
+
def initialize( data, fields, options={} )
|
55
|
+
@fields = fields
|
56
|
+
@tags = options[:tags] || {}
|
57
|
+
@data = []
|
58
|
+
nr_action =
|
59
|
+
if data.kind_of?(Array)
|
60
|
+
lambda { |key, index| @data[index] = data.shift || options[:filler] }
|
61
|
+
elsif data.kind_of?(DataRow)
|
62
|
+
lambda { |key, index| @data = data.to_a }
|
63
|
+
else
|
64
|
+
lambda { |key, index| @data[index] = data[key] || options[:filler] }
|
65
|
+
end
|
66
|
+
@fields.each_with_index { |key, index| nr_action.call(key,index) }
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_accessor :fields, :tags
|
70
|
+
|
71
|
+
# Returns an array of values. Should probably return a DataRow.
|
72
|
+
# Loses field information.
|
73
|
+
def +(other)
|
74
|
+
self.to_a + other.to_a
|
75
|
+
end
|
76
|
+
|
77
|
+
# Lets you access individual fields
|
78
|
+
#
|
79
|
+
# i.e. row["phone"] or row[4]
|
80
|
+
def [](key)
|
81
|
+
key.kind_of?(Fixnum) ? @data[key] : @data[@fields.index(key)]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Lets you set field values
|
85
|
+
#
|
86
|
+
# i.e. row["phone"] = '2038291203', row[7] = "allen"
|
87
|
+
def []=(key,value)
|
88
|
+
if key.kind_of?(Fixnum)
|
89
|
+
@data[key] = value
|
90
|
+
else
|
91
|
+
@data[@fields.index(key)] = value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Converts the DataRow to a plain old Array
|
96
|
+
def to_a
|
97
|
+
@data
|
98
|
+
end
|
99
|
+
|
100
|
+
# Converts the DataRow to a string representation
|
101
|
+
# for outputting to screen.
|
102
|
+
def to_s
|
103
|
+
"[" + @data.join(",") + "]"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Checks to see row includes the tag given.
|
107
|
+
#
|
108
|
+
# Example:
|
109
|
+
#
|
110
|
+
# >> row.has_tag? :running_balance
|
111
|
+
# => true
|
112
|
+
#
|
113
|
+
def has_tag?(tag)
|
114
|
+
@tags.include?(tag)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Iterates through DataRow elements. Accepts a block.
|
118
|
+
def each(&action)
|
119
|
+
@data.each(&action)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Allows you to add a tag to a row.
|
123
|
+
#
|
124
|
+
# Examples:
|
125
|
+
#
|
126
|
+
# row.tag_as(:jay_cross) if row["product"].eql?("im_courier")
|
127
|
+
# row.tag_as(:running_balance) if row.fields.include?("RB")
|
128
|
+
#
|
129
|
+
def tag_as(something)
|
130
|
+
@tags[something] = true
|
131
|
+
end
|
132
|
+
|
133
|
+
# Compares two DataRow objects. If values and fields are the same
|
134
|
+
# (and in the correct order) returns true. Otherwise returns false.
|
135
|
+
def ==(other)
|
136
|
+
self.to_a.eql?(other.to_a) && @fields.eql?(other.fields)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Synonym for DataRow#==
|
140
|
+
def eql?
|
141
|
+
self == other
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|