yob-activemdb 0.2.2

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/README.txt ADDED
@@ -0,0 +1,69 @@
1
+ ActiveMDB
2
+ by Matthew King
3
+ http://rubyforge.org/projects/activemdb/
4
+
5
+ == DESCRIPTION:
6
+
7
+ ActiveMDB is a developer's tool for exploration and migration of MS Access (.mdb) files. It uses ActiveRecord-ish reflection to parse table and column names and provide attribute readers. Yes, it is *READ ONLY*. ActiveMDB is little more than a wrapper of varying thickness around the utilities from Brian Bruns's MDB Tools project (http://mdbtools.sourceforge.net/). Kudos to Mr. Bruns.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * MDBTools - Straightforward wrapper around the CLI tools that you get with libmdb
12
+ * ActiveMDB::Base - Subclass to make your models, just like the big shots do.
13
+
14
+
15
+ == SYNOPSIS:
16
+
17
+ # When your Access database schema conforms to the 37s stylebook:
18
+ class Bacon < ActiveMDB::Base
19
+ set_mdb_file 'db/wherefore.mdb'
20
+ end
21
+
22
+ # in the find_* methods, the entries in the hash
23
+ # get turned into "WHERE #{key} like %#{value}%" conditions,
24
+ # which fails when the column is a boolean, which is a regression from 0.1.0.
25
+ # I could fix this tonight, but my son is yelling at me to come out for dinner.
26
+ best_bacon = Bacon.find_all(:rind => 'creamy', :sodium_content => 'Awesome!' )
27
+
28
+ # When it doesn't:
29
+ class Employee < ActiveMDB::Base
30
+ set_mdb_file 'db/sample.mdb'
31
+ set_table_name 'Employee'
32
+ set_primary_key 'Emp_Id'
33
+ end
34
+
35
+ paula = Employee.find_first(:Department => 'Engineering', :Specialty => 'paulaBeans')
36
+
37
+
38
+ == REQUIREMENTS:
39
+
40
+ * http://mdbtools.sourceforge.net/
41
+
42
+ == INSTALL:
43
+
44
+ * Sadly, no easy install of MDB Tools at this time. It compiles on Mac OS X 10.4.x, both PPC and Intel. Haven't tested on Linuxes yet, but that's what Parallels is for, right?
45
+
46
+ == LICENSE:
47
+
48
+ (The MIT License)
49
+
50
+ Copyright (c) 2007
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining
53
+ a copy of this software and associated documentation files (the
54
+ 'Software'), to deal in the Software without restriction, including
55
+ without limitation the rights to use, copy, modify, merge, publish,
56
+ distribute, sublicense, and/or sell copies of the Software, and to
57
+ permit persons to whom the Software is furnished to do so, subject to
58
+ the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be
61
+ included in all copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
64
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
65
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
66
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
67
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
68
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
69
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/active_mdb.rb'
6
+
7
+ Hoe.new('activemdb', ActiveMDB::VERSION) do |p|
8
+ p.rubyforge_name = 'activemdb'
9
+ p.author = 'Matthew King'
10
+ p.email = 'automatthew@gmail.com'
11
+ p.summary = 'ActiveRecordy wrapper around MDB Tools, allowing POSIX platforms to read MS Access (.mdb) files'
12
+ p.description = p.paragraphs_of('README.txt', 2).join("\n\n")
13
+ p.url = 'http://activemdb.rubyforge.org/'
14
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
15
+ end
16
+
17
+ # vim: syntax=Ruby
data/lib/active_mdb.rb ADDED
@@ -0,0 +1,19 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ module ActiveMDB
3
+ VERSION = '0.2.2'
4
+ end
5
+
6
+
7
+ require 'rubygems'
8
+ require 'active_support'
9
+ require 'active_mdb/mdb_tools'
10
+ load 'active_mdb/mdb.rb'
11
+ load 'active_mdb/table.rb'
12
+ load 'active_mdb/record.rb'
13
+ load 'active_mdb/column.rb'
14
+ load 'active_mdb/base.rb'
15
+ require 'faster_csv'
16
+
17
+
18
+
19
+
@@ -0,0 +1,184 @@
1
+ module ActiveMDB
2
+ # Hi, I am an imitation of ActiveRecord.
3
+ class Base
4
+
5
+ def initialize(attributes=nil)
6
+ @attributes = attributes unless attributes.nil?
7
+ end
8
+
9
+ cattr_accessor :pluralize_table_names, :instance_writer => false
10
+ @@pluralize_table_names = true
11
+
12
+ class << self # Class methods
13
+ attr_accessor :mdb_file
14
+ attr_reader :mdb
15
+
16
+ # set the path to the MDB file
17
+ def set_mdb_file(file)
18
+ @mdb_file = file
19
+ @mdb = MDB.new(file)
20
+ end
21
+
22
+ # set the name of the table in the MDB file
23
+ def table_name
24
+ table_name = Inflector.underscore(Inflector.demodulize(self.to_s))
25
+ table_name = Inflector.pluralize(table_name) if pluralize_table_names
26
+ table_name
27
+ end
28
+
29
+ # borrowed from ActiveRecord
30
+ def set_table_name(value = nil, &block)
31
+ define_attr_method :table_name, value, &block
32
+ end
33
+ alias :table_name= :set_table_name
34
+
35
+ # Returns an array of column objects for the table associated with this class.
36
+ def columns
37
+ unless @columns
38
+ @columns = @mdb.columns(table_name)
39
+ # @columns.each {|column| column.primary = column.name == primary_key}
40
+ end
41
+ @columns
42
+ end
43
+
44
+ def column_names
45
+ @mdb.column_names(table_name)
46
+ end
47
+
48
+ # returns 'id' unless you overwrite this method using set_primary_key
49
+ def primary_key
50
+ 'id'
51
+ end
52
+
53
+ # specify the field to be used as a primary key for those
54
+ # operations that require one. At this time, that isn't really
55
+ # anything except count.
56
+ def set_primary_key(value = nil, &block)
57
+ define_attr_method :primary_key, value, &block
58
+ end
59
+ alias :primary_key= :set_primary_key
60
+
61
+ def table_exists?
62
+ @mdb.tables.include? table_name
63
+ end
64
+
65
+ # How many? Requires that the primary_key return a valid field.
66
+ def count
67
+ @mdb.count(table_name, primary_key)
68
+ end
69
+
70
+ # the conditions hash keys are column names, the values are search values
71
+ # find_first :superhero_name => 'The Ironist', :powers => 'Wit'
72
+ #
73
+ # mdb-sql doesn't implement LIMIT yet, so this method pulls all results and
74
+ # calls Array#first on them. Ooky.
75
+ def find_first(conditions_hash)
76
+ # rekey_hash(conditions_hash)
77
+ find_from_hash(conditions_hash).first
78
+ end
79
+
80
+ # the conditions hash keys are column names, the values are search values
81
+ # find_all :superhero_name => 'The Ironist', :powers => 'Wit'
82
+ def find_all(conditions_hash = {})
83
+ find_from_hash(conditions_hash)
84
+ end
85
+
86
+ # borrowed from ActiveRecord.
87
+ def define_attr_method(name, value=nil, &block)
88
+ sing = class << self; self; end
89
+ sing.send :alias_method, "original_#{name}", name
90
+ if block_given?
91
+ sing.send :define_method, name, &block
92
+ else
93
+ # use eval instead of a block to work around a memory leak in dev
94
+ # mode in fcgi
95
+ sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
96
+ end
97
+ end
98
+
99
+ # relies on stuff that doesn't work right now
100
+ def rekey_hash(conditions_hash)
101
+ conditions_hash.each do |key,value|
102
+ column = self[key.to_s]
103
+ if column.boolean?
104
+ key = column.name + '!'
105
+ else
106
+ key = column.name
107
+ end
108
+ end
109
+ end
110
+
111
+ # supply a conditions string that would nestle gently in a
112
+ # WHERE clause, after the WHERE but before the.
113
+ def find_where(conditions)
114
+ MDBTools.sql_select_where(mdb_file, table_name, nil, conditions).collect! { |record| instantiate(record) }
115
+ end
116
+
117
+ # takes a hash where the keys are column names (string or symbol)
118
+ # and the values are the associated values.
119
+ def find_from_hash(hash)
120
+ conditions = conditions_from_hash(hash)
121
+ find_where(conditions)
122
+ end
123
+
124
+ # the conditions hash keys are column names, the values are search values
125
+ # e.g. search_with_hash(:first_name => 'Matthew', :last_name => 'King')
126
+ def conditions_from_hash(hash)
127
+ return nil if hash.nil? || hash.empty?
128
+ MDBTools.compile_conditions(hash) do |column_name, value|
129
+ column = column_for_method(column_name) || column_for_field(column_name)
130
+ raise ArgumentError, "No column corresponding to #{column_name}" unless column
131
+ case column.klass.to_s
132
+ when 'Fixnum', 'Float'
133
+ "#{column.name} = #{value}"
134
+ when 'String'
135
+ "#{column.name} LIKE '%#{value}%'"
136
+ when 'Object'
137
+ value = value ? 1 : 0
138
+ "#{column.name} IS #{value}"
139
+ end
140
+ end
141
+ end
142
+
143
+ # given the name of a column, return the Column object
144
+ def column_for_field(column_name)
145
+ columns.detect {|c| c.name == column_name.to_s}
146
+ end
147
+
148
+ def column_for_method(method_name)
149
+ columns.detect {|c| c.method_name == method_name.to_s}
150
+ end
151
+
152
+ private
153
+
154
+ def instantiate(record)
155
+ new_hash = {}
156
+ record.each do |name,value|
157
+ # begin
158
+ new_hash[MDBTools.methodize(name)] = column_for_field(name).type_cast(value)
159
+ # rescue
160
+ # raise "No column for #{name}"
161
+ # end
162
+ end
163
+ self.new new_hash
164
+ end
165
+
166
+
167
+ end
168
+
169
+ private
170
+
171
+ def method_missing(method_id, *args, &block)
172
+ method_name = MDBTools.methodize(method_id.to_s)
173
+ spaced_method_name = method_name.gsub("_"," ")
174
+ if @attributes.include?(method_name)
175
+ value = @attributes[method_name]
176
+ elsif @attributes.include?(spaced_method_name)
177
+ value = @attributes[spaced_method_name]
178
+ else
179
+ super
180
+ end
181
+ end
182
+
183
+ end
184
+ end
@@ -0,0 +1,69 @@
1
+ class Column
2
+ include MDBTools
3
+
4
+ attr_reader :method_name, :name, :type, :size
5
+
6
+ def initialize(name, type, size)
7
+ @name, @type = name, type
8
+ @method_name, @size = methodize(name), size.to_i
9
+ end
10
+
11
+ # returns a new Column from a hash with the keys:
12
+ # 'Column Name', 'Type', and 'Size'
13
+ def self.new_from_describe(describe_hash)
14
+ self.new(describe_hash["Column Name"], describe_hash["Type"], describe_hash["Size"])
15
+ end
16
+
17
+ # return Ruby class corresponding to data type.
18
+ # Borrowed from ActiveRecord
19
+ def klass
20
+ case type
21
+ when 'Text', 'Character' then String
22
+ when 'Long Integer' then Fixnum
23
+ when 'Double' then Float
24
+ when 'Currency', 'Float' then Float
25
+ when 'DateTime (Short)' then Time
26
+ when 'Boolean' then Object
27
+ when 'Decimal' then BigDecimal
28
+ when 'Binary' then String
29
+ end
30
+ end
31
+
32
+ # Casts value (which is a String) to an appropriate instance.
33
+ # Totally borrowed from ActiveRecord
34
+ def type_cast(value)
35
+ return nil if value.nil?
36
+ case type
37
+ when 'Text', 'Character' then value
38
+ when 'Long Integer' then value.to_i rescue value ? 1 : 0
39
+ when 'Currency', 'Float' then value.to_f
40
+ when 'Double' then value.to_f
41
+ when 'DateTime (Short)' then self.class.string_to_time(value)
42
+ when 'Boolean' then self.class.value_to_boolean(value)
43
+ when 'Decimal' then self.class.value_to_decimal(value)
44
+ when 'Binary' then self.class.binary_to_string(value)
45
+ else value
46
+ end
47
+ end
48
+
49
+ def self.string_to_time(string)
50
+ string
51
+ end
52
+
53
+ # provided any argument, returns the mdb-tools version
54
+ # of truth (which is to say, 1 or 0)
55
+ def self.value_to_boolean(value)
56
+ if value == true || value == false
57
+ value
58
+ else
59
+ %w(true t 1).include?(value.to_s.downcase)
60
+ end
61
+ end
62
+
63
+ # Are you a Boolean?
64
+ def boolean?
65
+ self.type == 'Boolean'
66
+ end
67
+
68
+
69
+ end
@@ -0,0 +1,50 @@
1
+ # require 'mdb_tools'
2
+
3
+ class MDB
4
+ include MDBTools
5
+
6
+ attr_reader :mdb_file, :prefix, :exclude, :tables
7
+
8
+ def initialize(mdb_file, options = {})
9
+ @mdb_file = check_file(mdb_file)
10
+ @prefix = options[:prefix] || ''
11
+ @exclude, @include = options[:exclude], options[:include]
12
+ @export_syntax = options[:sql_syntax] || 'mysql'
13
+ @tables = mdb_tables(@mdb_file, :exclude => @exclude, :include => @include)
14
+ # @tables = create_table_objects
15
+ end
16
+
17
+ # consumer of poor, weak MDBTools.faked_count
18
+ def count(table_name, attribute)
19
+ MDBTools.faked_count(@mdb_file, table_name, attribute)
20
+ end
21
+
22
+ def columns(table_name)
23
+ MDBTools.describe_table(@mdb_file, table_name).map do |column|
24
+ Column.new_from_describe(column)
25
+ end
26
+ end
27
+
28
+ def column_names(table_name)
29
+ MDBTools.field_names_for(@mdb_file, table_name)
30
+ end
31
+
32
+
33
+ private
34
+
35
+ # Deprecated.
36
+ def create_table_objects
37
+ tables = {}
38
+ @table_names.each do |table|
39
+ tables[table] = Table.new(self, table, @prefix)
40
+ metaclass = class << self; self; end
41
+ metaclass.send :define_method, methodize(table) do
42
+ tables[table]
43
+ end
44
+ end
45
+ tables
46
+ end
47
+
48
+
49
+ end
50
+
@@ -0,0 +1,229 @@
1
+ module MDBTools
2
+
3
+ extend self
4
+
5
+ DELIMITER = '::'
6
+ LINEBREAK = "\n"
7
+ SANITIZER = /^\w\.\_/ # dumb filter for SQL arguments
8
+ BACKENDS = %w{ access mysql oracle postgres sybase }
9
+
10
+ # test for existence and usability of file
11
+ def check_file(mdb_file)
12
+ raise ArgumentError, "File not found: #{mdb_file}" unless File.exist?(mdb_file)
13
+ @mdb_version = `mdb-ver #{mdb_file} 2>&1`.chomp
14
+ if $? != 0
15
+ raise ArgumentError, "mdbtools cannot access #{mdb_file}"
16
+ end
17
+ mdb_file
18
+ end
19
+
20
+ # runs mdb_version. A blank version indicates an unusable file
21
+ def valid_file?(file)
22
+ !mdb_version(file).blank?
23
+ end
24
+
25
+ def mdb_version(file)
26
+ `mdb-ver #{file} 2> /dev/null`.chomp
27
+ end
28
+
29
+ # raises an ArgumentError unless the mdb file contains a table with the specified name.
30
+ # returns the table name, otherwise.
31
+ def check_table(mdb_file, table_name)
32
+ unless mdb_tables(mdb_file).include?(table_name)
33
+ raise ArgumentError, "mdbtools does not think a table named \"#{table_name}\" exists"
34
+ end
35
+ table_name
36
+ end
37
+
38
+ # uses mdb-tables tool to return an array of table names.
39
+ # You can filter the tables by passing an array of strings as
40
+ # either the :exclude or :include key to the options hash.
41
+ # The strings will be ORed into a regex. Only one or the other of
42
+ # :exclude or :include, please.
43
+ #
44
+ # ex. mdb_tables('thing.mdb', :exclude => ['_Lookup'])
45
+ #
46
+ # ex. mdb_tables('thing.mdb', :include => ['tbl'])
47
+ def mdb_tables(mdb_file, options = {})
48
+ included, excluded = options[:include], options[:exclude]
49
+ return `mdb-tables -1 #{mdb_file}`.split(LINEBREAK) if not (included || excluded)
50
+ raise ArgumentError if (options[:include] && options [:exclude])
51
+ if options[:exclude]
52
+ regex = Regexp.new options[:exclude].to_a.join('|')
53
+ tables = `mdb-tables -1 #{mdb_file}`.split(LINEBREAK).delete_if { |name| name =~ regex }
54
+ end
55
+ if options[:include]
56
+ regex = Regexp.new options[:include].to_a.join('|')
57
+ tables = `mdb-tables -1 #{mdb_file}`.split(LINEBREAK).select { |name| name =~ regex }
58
+ end
59
+ tables
60
+ end
61
+
62
+ # takes an array of field names
63
+ # and some conditions to append in a WHERE clause
64
+ def sql_select_where(mdb_file, table_name, attributes = nil, conditions=nil)
65
+ if attributes.respond_to?(:join)
66
+ attributes = attributes.collect {|a| "\"#{a}\"" }.join(' ')
67
+ elsif attributes.kind_of?(String)
68
+ attributes = "\"#{attributes}\""
69
+ else
70
+ attributes ||= '*'
71
+ end
72
+ where = conditions ? "where #{conditions}" : ""
73
+ sql = "select #{attributes} from #{table_name} #{where}"
74
+ mdb_sql(mdb_file, sql)
75
+ end
76
+
77
+ # forks an IO.popen running mdb-sql and discarding STDERR to /dev/null.
78
+ # The sql argument should be a single statement, 'cause I don't know
79
+ # what will happen otherwise. mdb-sql uses "\ngo" as the command terminator.
80
+ def mdb_sql(mdb_file,sql)
81
+ # libMDB barks on stderr quite frequently, so discard stderr entirely
82
+ command = "mdb-sql -Fp -d '#{DELIMITER}' #{mdb_file} 2> /dev/null \n"
83
+ array = []
84
+ IO.popen(command, 'r+') do |pipe|
85
+ pipe << "#{sql}\ngo\n"
86
+ pipe.close_write
87
+ pipe.readline
88
+ fields = pipe.readline.chomp.split(DELIMITER)
89
+ pipe.each do |row|
90
+ hash = {}
91
+ row = row.chomp.split(DELIMITER)
92
+ fields.each_index do |i|
93
+ hash[fields[i]] = row[i]
94
+ end
95
+ array << hash
96
+ end
97
+ end
98
+ array
99
+ end
100
+
101
+ # uses mdb-sql to retrieve an array of the table's field names
102
+ def field_names_for(mdb_file, table)
103
+ fields = `echo -n 'select * from #{table} where 1 = 2' | mdb-sql -Fp -d '#{DELIMITER}' #{mdb_file}`.chomp.sub(/^\n+/, '')
104
+ fields.split(DELIMITER)
105
+ end
106
+
107
+
108
+ # takes a hash where keys are column names, values are search values
109
+ # and returns a string that you can use in a WHERE clause
110
+ #
111
+ # ex. compile_conditions(:first_name => 'Summer', :last_name => 'Roberts')
112
+ # gives "first_name like '%Summer%' AND last_name like '%Roberts%'
113
+ #
114
+ # if you want to use an operator other than LIKE, give compile_conditions
115
+ # a block that accepts column_name and value and does something interesting
116
+ #
117
+ # compile_conditions(:age => 18) {|name, value| "#{name} = #{value}"}
118
+ #
119
+ # the condition phrases are all ANDed together before insertion into a WHERE clause
120
+ def compile_conditions(conditions_hash)
121
+ conditions = conditions_hash.sort_by{|k,v| k.to_s}.map do |column_name, value|
122
+ if block_given?
123
+ yield column_name, value
124
+ else
125
+ "#{column_name} like '%#{value}%'"
126
+ end
127
+ end.join(' AND ')
128
+ end
129
+
130
+ # really dumb way to get a count. Does a SELECT and call size on the results
131
+ def faked_count(*args)
132
+ sql_select_where(*args).size
133
+ end
134
+
135
+ # convenience method, not really used with ActiveMDB.
136
+ # Valid options are :format, :headers, and :sanitize,
137
+ # which correspond rather directly to the underlying mdb-export arguments.
138
+ # Defaults to :format => 'sql', :headers => false, :sanitize => true
139
+ def mdb_export(mdb_file, table_name, options = {})
140
+ defaults = { :format => 'sql',
141
+ :headers => false,
142
+ :sanitize => true }
143
+ options = defaults.merge options
144
+
145
+ args = []
146
+ if options[:delimiter]
147
+ args << "-d #{options[:delimiter].dump}"
148
+ elsif options[:format] == 'sql'
149
+ args << "-I "
150
+ elsif options[:format] == 'csv'
151
+ args << "-d ',' "
152
+ else
153
+ raise ArgumentError, "Unknown format: #{options[:format]}"
154
+ end
155
+
156
+ args << "-H " unless options[:headers] == true
157
+ args << "-S" unless options[:sanitize] == false
158
+ `mdb-export #{args} #{mdb_file} #{table_name.to_s.dump}`
159
+ end
160
+
161
+ # wrapper for DESCRIBE TABLE using mdb-sql
162
+ def describe_table(mdb_file, table_name)
163
+ command = "describe table \"#{table_name}\""
164
+ mdb_sql(mdb_file,command)
165
+ end
166
+
167
+ # wrapper for mdb-schema, returns SQL statements
168
+ def mdb_schema(mdb_file, table_name)
169
+ schema = `mdb-schema -T #{table_name.dump} #{mdb_file}`
170
+ end
171
+
172
+ # convenience method for mdb_export to output CSV with headers.
173
+ def table_to_csv(mdb_file, table_name)
174
+ mdb_export(mdb_file, table_name, :format => 'csv', :headers => true)
175
+ end
176
+
177
+ def delimited_to_arrays(text)
178
+ text.gsub!(/\r\n/,' ')
179
+ text.split(LINEBREAK).collect { |row| row.split(DELIMITER)}
180
+ end
181
+
182
+ def arrays_to_hashes(headers, arrays)
183
+ arrays.collect do |record|
184
+ record_hash = Hash.new
185
+ until record.empty? do
186
+ headers.each do |header|
187
+ record_hash[header] = record.shift
188
+ end
189
+ end
190
+ record_hash
191
+ end
192
+ end
193
+
194
+
195
+ # helper to turn table names into standard format method names.
196
+ # Inside, it's just ActionView::Inflector.underscore
197
+ def methodize(table_name)
198
+ Inflector.underscore table_name
199
+ end
200
+
201
+ def backends
202
+ BACKENDS
203
+ end
204
+
205
+ # poor, weakly sanitizing gsub!.
206
+ def sanitize!(string)
207
+ string.gsub!(SANITIZER, '')
208
+ end
209
+
210
+ # mdb-tools recognizes 1 and 0 as the boolean values.
211
+ # Make it so.
212
+ def mdb_truth(value)
213
+ case value
214
+ when false
215
+ 0
216
+ when true
217
+ 1
218
+ when 0
219
+ 0
220
+ when 1
221
+ 1
222
+ when "0"
223
+ 0
224
+ when "1"
225
+ 1
226
+ end
227
+ end
228
+
229
+ end
@@ -0,0 +1,34 @@
1
+ # require 'mdb_tools'
2
+
3
+ # Deprecated way more than Table
4
+ class Record
5
+ include MDBTools
6
+ include Enumerable
7
+
8
+ def initialize(mdb_table, line)
9
+ raise 'no results' unless line
10
+ @struct = mdb_table.record_struct
11
+ @data = @struct.new(*line)
12
+ create_accessors
13
+ end
14
+
15
+ def each(&block)
16
+ @data.members.each {|k| yield k, @data[k] }
17
+ end
18
+
19
+ def compact
20
+ self.select {|k,v| v && !v.empty? && v != "0" }
21
+ end
22
+
23
+ private
24
+
25
+ def create_accessors
26
+ meta = class << self; self; end
27
+ @struct.members.each do |att|
28
+ meta.send :define_method, att do
29
+ @data[att]
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,95 @@
1
+ # require 'mdb_tools'
2
+
3
+ # Deprecated. So very deprecated.
4
+ class Table
5
+ include MDBTools
6
+
7
+ attr_reader :mdb_file, :table_name, :columns, :record_struct, :schema
8
+ attr_accessor :primary_key
9
+
10
+
11
+ def initialize(mdb, table_name, prefix)
12
+ @mdb_file = check_file(mdb.mdb_file)
13
+ @table_name = check_table(@mdb_file, table_name)
14
+ # @schema = mdb_schema(@mdb_file, @table_name)
15
+ @columns = describe_table(mdb_file, table_name).map do |column|
16
+ Column.new_from_describe(column)
17
+ end
18
+ @record_struct = create_record_struct
19
+ end
20
+
21
+
22
+ def [](method_name)
23
+ self.columns.detect {|c| c.method_name == method_name }
24
+ end
25
+
26
+ # returns an array of column names
27
+ def column_names
28
+ columns.collect {|x| methodize(x.method_name).to_sym}
29
+ end
30
+
31
+ def create_record_struct
32
+ attributes = columns.collect {|column| column.method_name.to_sym}
33
+ Struct.new( *attributes)
34
+ end
35
+
36
+ def to_csv
37
+ table_to_csv(mdb_file, table_name)
38
+ end
39
+
40
+ # returns the first record that meets the equality (sometimes LIKE) conditions
41
+ # of the supplied hash. No comparison operators available at the moment.
42
+ #
43
+ # find_first :superhero_name => 'The Ironist', :powers => 'Wit'
44
+ #
45
+ # mdb-sql doesn't implement LIMIT yet, so this method pulls all results and
46
+ # calls Array#first on them. Ooky.
47
+ def find_first(conditions_hash)
48
+ rekey_hash(conditions_hash)
49
+ result = sql_search(conditions_hash).first
50
+ create_record(result)
51
+ end
52
+
53
+
54
+ def find_all(conditions_hash={})
55
+ if conditions_hash.empty?
56
+ return sql_select_where(mdb_file, table_name, nil, '1 = 1').collect {|r| create_record(r)}
57
+ end
58
+ rekey_hash(conditions_hash)
59
+ sql_search(conditions_hash).collect {|r| create_record(r) }
60
+ end
61
+
62
+ private
63
+
64
+ def create_record(line)
65
+ Record.new(self, line)
66
+ end
67
+
68
+ def rekey_hash(conditions_hash)
69
+ conditions_hash.each do |key,value|
70
+ column = self[key.to_s]
71
+ if column.boolean?
72
+ key = column.name + '!'
73
+ else
74
+ key = column.name
75
+ end
76
+ end
77
+ end
78
+
79
+ # the conditions hash keys are column names, the values are search values
80
+ # e.g. sql_search(:first_name => 'Matthew', :last_name => 'King')
81
+ def sql_search(conditions_hash)
82
+ conditions = compile_conditions(conditions_hash) do |method_name,value|
83
+ column = self[method_name.to_s]
84
+ if column.boolean?
85
+ "#{column.name} = #{mdb_truth(value)}"
86
+ else
87
+ "#{column.name} like '%#{value}%'"
88
+ end
89
+ end
90
+ sql_select_where(mdb_file, table_name, nil, conditions)
91
+ end
92
+
93
+
94
+ end
95
+
@@ -0,0 +1,55 @@
1
+ require File.join(File.dirname(__FILE__), "..", 'lib', 'active_mdb')
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), 'test_helper')
4
+
5
+ class RealWorldTest < Test::Unit::TestCase
6
+
7
+ RETREAT = File.join(File.dirname(__FILE__), '..', 'db', 'retreat.mdb')
8
+
9
+ class Family < ActiveMDB::Base
10
+ set_mdb_file RETREAT
11
+ set_table_name 'tblFamilyData'
12
+ end
13
+
14
+ class Cabin < ActiveMDB::Base
15
+ set_mdb_file RETREAT
16
+ set_table_name 'tCabins'
17
+ end
18
+
19
+ def setup
20
+ @retreat = MDB.new(RETREAT)
21
+ end
22
+
23
+ def test_tables
24
+ tables = ["JobAssignmentsByCabin","Switchboard Items","tblAdultChildCode","tblCities","tblFamilyData","tblGender","tblIndividData","tblStatusChurch","tblStatusIndiv","tCabinAssignments","tCabins","tCampRates","tCampScholarships","tJobs","tJobsR","tParkAreas","tPayments","tRetreatEnrollment","tSponsors","tblStatusFamily"]
25
+ assert_equal tables, @retreat.tables
26
+ end
27
+
28
+ def test_column_names
29
+ column_names = ["FamKey","LName", "HisName", "HerName", "HmAddress", "AddressNumber", "StreetDirection", "StreetName", "HmAddress2", "City", "State", "Zip", "HmPhone", "UnLstHmPho?"]
30
+ assert_equal column_names, Family.column_names
31
+ end
32
+
33
+ def test_columns
34
+ column = Family.columns[2]
35
+ assert_kind_of Column, column
36
+ assert_respond_to column, 'type'
37
+ assert_respond_to column, 'name'
38
+ assert_respond_to column, 'size'
39
+ Family.columns.each do |c|
40
+ assert_not_nil c.name
41
+ assert_not_nil c.type
42
+ end
43
+ end
44
+
45
+ def test_column_klass
46
+ column = Family.columns[1]
47
+ assert_equal String, column.klass
48
+ Family.columns.each do |c|
49
+ assert_not_nil c.klass
50
+ assert_kind_of Class, c.klass
51
+ end
52
+ end
53
+
54
+
55
+ end
data/test/test_base.rb ADDED
@@ -0,0 +1,125 @@
1
+ require File.join(File.dirname(__FILE__), "..", 'lib', 'active_mdb')
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), 'test_helper')
4
+
5
+ class BaseTest < Test::Unit::TestCase
6
+
7
+ class NonExistentTable < ActiveMDB::Base
8
+ set_mdb_file TEST_DB
9
+ end
10
+ class Employee < ActiveMDB::Base
11
+ set_mdb_file TEST_DB
12
+ set_table_name 'Employee'
13
+ set_primary_key 'Emp_Id'
14
+ end
15
+ class Room < ActiveMDB::Base
16
+ set_mdb_file TEST_DB
17
+ set_table_name 'Room'
18
+ set_primary_key 'Room'
19
+ end
20
+
21
+ class Person < ActiveMDB::Base
22
+ set_mdb_file RETREAT_DB
23
+ set_table_name 'tblIndividData'
24
+ set_primary_key 'IndKey'
25
+ end
26
+
27
+
28
+ def test_setting_mdb_file
29
+ assert_equal TEST_DB, Employee.mdb_file
30
+ end
31
+
32
+ def test_mdb_creation
33
+ mdb = Employee.mdb
34
+ assert_not_nil mdb
35
+ assert_respond_to mdb, :tables
36
+ end
37
+
38
+ def test_get_table_name
39
+ assert_equal 'non_existent_tables', NonExistentTable.table_name
40
+ ActiveMDB::Base.pluralize_table_names = false
41
+ assert_equal 'non_existent_table', NonExistentTable.table_name
42
+ end
43
+
44
+ def test_set_table_name
45
+ Employee.set_table_name('foo')
46
+ assert_equal 'foo', Employee.table_name
47
+ assert_equal 'Room', Room.table_name
48
+ Employee.set_table_name('Employee')
49
+ end
50
+
51
+ def test_table_exists
52
+ assert !NonExistentTable.table_exists?
53
+ assert Room.table_exists?
54
+ end
55
+
56
+ def test_get_and_set_primary_key
57
+ assert_equal 'Emp_Id', Employee.primary_key
58
+ Employee.set_primary_key 'employee_id'
59
+ assert_equal 'employee_id', Employee.primary_key
60
+ end
61
+
62
+ def test_count
63
+ assert_equal 92, Room.count
64
+ assert_equal 53, Employee.count
65
+ end
66
+
67
+ def test_instantiate
68
+ hash = {"Department"=>"Engineering", "Gender"=>"M", "Room"=>"6072", "Title"=>"Programmer", "Emp_Id"=>"1045", "First_Name"=>"Robert", "Last_Name"=>"Weinfeld"}
69
+ record = Employee.send(:instantiate, hash)
70
+ methodized_hash = {}
71
+ hash.each {|k,v| methodized_hash[MDBTools.methodize(k)] = v}
72
+ assert_equal methodized_hash, record.instance_variable_get('@attributes')
73
+ hash = {"Area"=>"135.38","Entity_Handle"=>"9D7A","Room"=>"6004","Room_Type"=>"STOR-GEN"}
74
+ record = Room.send(:instantiate, hash)
75
+ assert_kind_of Float, record.instance_variable_get('@attributes')['area']
76
+ end
77
+
78
+ def test_find_from_hash
79
+ record = Employee.find_from_hash(:Room => '6072').first
80
+ assert_kind_of Employee, record
81
+ record = Room.find_first(:Area => 135.38)
82
+ assert_kind_of Room, record
83
+ end
84
+
85
+ def test_find_where
86
+ weinstein = Employee.find_where( "Last_Name = 'Weinfeld'").first
87
+ assert_equal 'Robert', weinstein.First_Name
88
+ end
89
+
90
+ def test_conditions_from_hash
91
+ hash = {:Room => '6072'}
92
+ conditions = "Room LIKE '%6072%'"
93
+ assert_equal conditions, Employee.conditions_from_hash(hash)
94
+ hash = {:Area => 45}
95
+ conditions = "Area = 45"
96
+ assert_equal conditions, Room.conditions_from_hash(hash)
97
+ end
98
+
99
+ def test_attribute_magic
100
+ record = Employee.find_from_hash(:Room => '6072').first
101
+ assert_equal '6072', record.Room
102
+ end
103
+
104
+
105
+ def test_find_all_with_arg
106
+ assert_equal 2, Employee.find_all(:First_Name => 'G').size
107
+ end
108
+
109
+ def test_find_all_without_arg
110
+ assert_nothing_raised { @employees = Employee.find_all }
111
+ assert_kind_of Employee, @employees.first
112
+ end
113
+
114
+ def test_column_for_field
115
+ assert_kind_of Column, Employee.column_for_field(:Room)
116
+ assert_nil Employee.column_for_field('foo')
117
+ end
118
+
119
+ def test_column_for_method
120
+ assert_kind_of Column, Employee.column_for_method(:room)
121
+ end
122
+
123
+
124
+
125
+ end
@@ -0,0 +1,26 @@
1
+ require File.join(File.dirname(__FILE__), "..", 'lib', 'active_mdb')
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), 'test_helper')
4
+
5
+ class ColumnTest < Test::Unit::TestCase
6
+
7
+
8
+
9
+ def setup
10
+ @c1 = Column.new('is_available', 'Boolean', 0)
11
+ end
12
+
13
+ def test_boolean_column
14
+ assert_equal false, @c1.type_cast(0)
15
+ end
16
+
17
+
18
+
19
+ # def test_string_to_time
20
+ # time = '05/18/76 00:00:00'
21
+ # t2 = "02/17/47 00:00:00"
22
+ # assert_equal 1976, Column.string_to_time(time).year
23
+ # assert_equal 5, Column.string_to_time(time).month
24
+ # end
25
+
26
+ end
@@ -0,0 +1,19 @@
1
+ require 'test/unit'
2
+ TEST_DB = File.join(File.dirname(__FILE__), '..', 'db', 'sample.mdb')
3
+ RETREAT_DB = File.join(File.dirname(__FILE__), '..', 'db', 'retreat.mdb')
4
+ NOT_A_DB = File.join(File.dirname(__FILE__), '..', 'db', 'not_an_mdb.txt')
5
+
6
+ class Test::Unit::TestCase
7
+
8
+
9
+
10
+ protected
11
+
12
+ def create_mdb(options={})
13
+ excluded = options[:exclude]
14
+ included = options[:include]
15
+ assert_nothing_raised { @db = MDB.new(TEST_DB, :exclude => excluded, :include => included ) }
16
+ assert_not_nil @db
17
+ end
18
+
19
+ end
data/test/test_mdb.rb ADDED
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), "..", 'lib', 'active_mdb')
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), 'test_helper')
4
+
5
+ class MDBTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+
9
+ end
10
+
11
+ def test_table_exclusion
12
+ create_mdb(:exclude => ['Inventory'])
13
+ assert !@db.tables.include?('Inventory')
14
+ end
15
+
16
+ def test_table_inclusion
17
+ create_mdb(:include => ['Inventory', 'Room'])
18
+ assert_equal ['Inventory', 'Room'], @db.tables.sort
19
+ end
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+ end
28
+
@@ -0,0 +1,136 @@
1
+ require File.join(File.dirname(__FILE__), "..", 'lib', 'active_mdb')
2
+ require 'test/unit'
3
+ require File.join(File.dirname(__FILE__), 'test_helper')
4
+
5
+
6
+ class MDBToolsTest < Test::Unit::TestCase
7
+ include MDBTools
8
+
9
+ EXCLUDE = ['Inventory']
10
+ TEST_TABLES = %w{ Room Computer Employee }
11
+ TORBATI_Y = {"Department"=>"Engineering",
12
+ "Gender"=>"F",
13
+ "Room"=>"6044",
14
+ "Title"=>"Programmer",
15
+ "Emp_Id"=>"1000",
16
+ "First_Name"=>"Yolanda",
17
+ "Last_Name"=>"Torbati"}
18
+ README = File.join(File.dirname(__FILE__), "..", "README")
19
+
20
+ def setup
21
+ @employees_csv = mdb_export(TEST_DB, 'Employee', :format => 'csv', :headers => true)
22
+ end
23
+
24
+ def test_check_file
25
+ assert_nothing_raised { check_file TEST_DB }
26
+ assert_raise(ArgumentError) { check_file 'completely_bogus_filename' }
27
+ # the README file is obviously not an Access database
28
+ assert_raise(ArgumentError) { check_file README}
29
+ end
30
+
31
+ def test_valid_file
32
+ assert valid_file?(TEST_DB)
33
+ assert !valid_file?(NOT_A_DB)
34
+ end
35
+
36
+ def test_mdb_version
37
+ assert_equal 'JET3', mdb_version(TEST_DB)
38
+ assert_equal '', mdb_version(NOT_A_DB)
39
+ end
40
+
41
+ def test_check_table
42
+ assert_nothing_raised { check_table TEST_DB, 'Employee'}
43
+ assert_raises(ArgumentError) { check_table TEST_DB, 'foobarbaz' }
44
+ end
45
+
46
+ def test_mdb_tables
47
+ tables1 = mdb_tables(TEST_DB, :exclude => EXCLUDE)
48
+ assert_equal TEST_TABLES, tables1
49
+
50
+ assert_raises( ArgumentError) { mdb_tables(TEST_DB, :include => [], :exclude => []) }
51
+ tables4 = mdb_tables(TEST_DB, :exclude => 'Room')
52
+ assert_equal %w{Computer Employee Inventory}, tables4
53
+ end
54
+
55
+ def test_mdb_tables_with_include
56
+ tables = mdb_tables(TEST_DB, :include => ['Room', 'Computer'])
57
+ assert_equal ['Room', 'Computer'], tables
58
+ end
59
+
60
+ def test_mdb_tables_with_nils
61
+ assert_nothing_raised do
62
+ tables = mdb_tables(TEST_DB, :include => nil, :exclude => nil)
63
+ assert_not_nil tables
64
+ end
65
+ end
66
+
67
+ def test_mdb_schema
68
+ assert_nothing_raised { @schema = mdb_schema(TEST_DB, 'Employee') }
69
+ assert_match /DROP TABLE/, @schema
70
+ end
71
+
72
+ def test_describe_table
73
+ descriptions = describe_table(TEST_DB, 'Employee')
74
+ assert_kind_of Array, descriptions
75
+ assert_kind_of Hash, descriptions.first
76
+ assert_equal 3, descriptions.first.size
77
+ assert_not_nil descriptions.first['Type']
78
+ end
79
+
80
+ def test_mdb_sql
81
+ result = [{"Department"=>"Human Resources", "Gender"=>"F", "Room"=>"6150", "Title"=>"Vice President", "Emp_Id"=>"1025", "First_Name"=>"Kathy", "Last_Name"=>"Ragerie"}]
82
+ assert_equal result, mdb_sql(TEST_DB, "select * from Employee where Room = '6150'")
83
+ bees = mdb_sql(TEST_DB, "select * from Employee where Last_Name like 'B%'")
84
+ assert_kind_of Array, bees
85
+ b = bees.first
86
+ assert_kind_of Hash, b
87
+ end
88
+
89
+ def test_field_names_for
90
+ fields = ["Last_Name","First_Name", "Gender", "Title", "Department", "Room", "Emp_Id"]
91
+ assert_equal fields, field_names_for(TEST_DB, 'Employee')
92
+ end
93
+
94
+
95
+ # def test_csv_to_hashes
96
+ # employee_hash = csv_to_hashes(@employees_csv)
97
+ # assert_equal TORBATI_Y, employee_hash.first
98
+ # end
99
+
100
+ def test_sql_select_where
101
+ yo = {"Department"=>"Engineering", "Gender"=>"F", "Room"=>"6044", "Title"=>"Programmer", "Emp_Id"=>"1000", "First_Name"=>"Yolanda", "Last_Name"=>"Torbati"}
102
+ assert_equal yo, sql_select_where(TEST_DB, 'Employee', ['*'], "First_Name LIKE 'Yolanda'" ).first
103
+ end
104
+
105
+ def test_compile_conditions
106
+ conditions = {:foo => 'bar', :baz => 1, :nark => 'noo'}
107
+ assert_equal "baz like '%1%' AND foo like '%bar%' AND nark like '%noo%'", compile_conditions(conditions)
108
+ end
109
+
110
+ def test_compiled_sql_select_where
111
+ sql_select_where(TEST_DB, 'Employee', nil, compile_conditions(:first_name => 'Yolanda'))
112
+ end
113
+
114
+ def test_count
115
+ assert_equal 92, faked_count(TEST_DB, 'Room', 'Room', nil)
116
+ end
117
+
118
+ def test_export_table_to_sql
119
+ # this test is dependent on specific content in the sample database
120
+ export = mdb_export(TEST_DB, 'Computer', :format => 'sql').split(LINEBREAK)
121
+ assert_equal 172, export.size
122
+ assert export.first =~ /INSERT INTO.*MITSUBISHI.*HL6605ATK/
123
+ end
124
+
125
+ def test_export_table_to_csv
126
+ export = mdb_export(TEST_DB, 'Employee', :format => 'csv').split(LINEBREAK)
127
+ assert_equal 53, export.size
128
+ assert export.first =~ /\"Torbati\",/
129
+ end
130
+
131
+ def test_backends
132
+ assert_nothing_raised { backends }
133
+ end
134
+
135
+
136
+ end
@@ -0,0 +1,24 @@
1
+ # require File.join(File.dirname(__FILE__), "..", 'lib', 'active_mdb')
2
+ # require 'test/unit'
3
+ # require File.join(File.dirname(__FILE__), 'test_helper')
4
+ #
5
+ # class RecordTest < Test::Unit::TestCase
6
+ #
7
+ # def setup
8
+ # create_mdb
9
+ # @employee = @db.employee
10
+ # @line = ["Torbati","Yolanda", "F", "Programmer", "Engineering", "6044", "1000"]
11
+ # assert_nothing_raised { @record = Record.new(@employee, @line) }
12
+ # end
13
+ #
14
+ # def test_reflection
15
+ # assert_respond_to @record, 'gender'
16
+ # assert_respond_to @record, 'emp_id'
17
+ # assert_equal 'Yolanda', @record.first_name
18
+ # end
19
+ #
20
+ # def test_display
21
+ #
22
+ # end
23
+ #
24
+ # end
@@ -0,0 +1,50 @@
1
+ # require File.join(File.dirname(__FILE__), "..", 'lib', 'active_mdb')
2
+ # require 'test/unit'
3
+ # require File.join(File.dirname(__FILE__), 'test_helper')
4
+ #
5
+ # class TableTest < Test::Unit::TestCase
6
+ #
7
+ #
8
+ # def setup
9
+ # create_mdb
10
+ # @employee = @db.employee
11
+ # end
12
+ #
13
+ # def test_columns
14
+ # columns = @employee.columns
15
+ # assert_kind_of Array, columns
16
+ # assert_kind_of Column, columns.first
17
+ # assert_equal 7, columns.size
18
+ # end
19
+ #
20
+ # def test_create_record_struct
21
+ # assert_kind_of Class, @employee.record_struct
22
+ # members = @employee.record_struct.members
23
+ # assert_equal 7, members.size
24
+ # assert members.include?('emp_id')
25
+ # end
26
+ #
27
+ # def test_to_csv
28
+ # csv_text = @employee.to_csv
29
+ # assert_kind_of String, csv_text
30
+ # arrays = csv_text.split(MDBTools::LINEBREAK)
31
+ #
32
+ # # grab the headers and test for content
33
+ # assert arrays.shift.include?('Emp_Id')
34
+ # assert_equal 53, arrays.size
35
+ # end
36
+ #
37
+ # def test_find_first
38
+ # y = @employee.find_first(:first_name => 'Yolanda')
39
+ # assert_kind_of Record, y
40
+ # assert_equal 'Yolanda', y.first_name
41
+ # end
42
+ #
43
+ # def test_find_all
44
+ # a_names = @employee.find_all(:last_name => 'A')
45
+ # assert_kind_of Array, a_names
46
+ # assert_kind_of Record, a_names.first
47
+ # assert_equal 2, a_names.size
48
+ # end
49
+ #
50
+ # end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yob-activemdb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Matthew King
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: ActiveRecordy wrapper around MDB Tools, allowing POSIX platforms to read MS Access (.mdb) files
17
+ email: automatthew@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/active_mdb/mdb_tools.rb
26
+ - lib/active_mdb/column.rb
27
+ - lib/active_mdb/record.rb
28
+ - lib/active_mdb/table.rb
29
+ - lib/active_mdb/mdb.rb
30
+ - lib/active_mdb/base.rb
31
+ - lib/active_mdb.rb
32
+ - Rakefile
33
+ - README.txt
34
+ has_rdoc: true
35
+ homepage: http://activemdb.rubyforge.org/
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: ActiveRecordy wrapper around MDB Tools, allowing POSIX platforms to read MS Access (.mdb) files
60
+ test_files:
61
+ - test/test_column.rb
62
+ - test/test_record.rb
63
+ - test/test_activemdb.rb
64
+ - test/test_mdb_tools.rb
65
+ - test/test_table.rb
66
+ - test/test_mdb.rb
67
+ - test/test_base.rb
68
+ - test/test_helper.rb