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 +69 -0
- data/Rakefile +17 -0
- data/lib/active_mdb.rb +19 -0
- data/lib/active_mdb/base.rb +184 -0
- data/lib/active_mdb/column.rb +69 -0
- data/lib/active_mdb/mdb.rb +50 -0
- data/lib/active_mdb/mdb_tools.rb +229 -0
- data/lib/active_mdb/record.rb +34 -0
- data/lib/active_mdb/table.rb +95 -0
- data/test/test_activemdb.rb +55 -0
- data/test/test_base.rb +125 -0
- data/test/test_column.rb +26 -0
- data/test/test_helper.rb +19 -0
- data/test/test_mdb.rb +28 -0
- data/test/test_mdb_tools.rb +136 -0
- data/test/test_record.rb +24 -0
- data/test/test_table.rb +50 -0
- metadata +68 -0
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
|
data/test/test_column.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED
@@ -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
|
data/test/test_record.rb
ADDED
@@ -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
|
data/test/test_table.rb
ADDED
@@ -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
|