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