Monday, June 4, 2007

Using Ruby & ADO to Work with MS Access Databases

A reader in the Ruby forum recently asked about using Ruby to get data from a Microsoft Access database. This can be done easily using the ActiveX Data Objects (ADO), via the win32ole library. Let's walk through it...

As usual, require the win32ole library:


require 'win32ole'

Next, create a new ADODB Connection object, which will manage the connection to the database:

connection = WIN32OLE.new('ADODB.Connection')

To open a connection to the Access database, call the Connection object's Open method, passing it the Provider and Data Source (your Access mdb file) parameters:

connection.Open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\baseball\lahman54.mdb')

To execute an SQL statement that does not return a recordset (ie, CREATE, ALTER, INSERT), you can call the Connection object's Execute method:

connection.Execute("INSERT INTO HallOfFame VALUES ('Dave', 'Concepcion');")

To perform a query that returns a recordset of data, first create a new ADODB Recordset object:

recordset = WIN32OLE.new('ADODB.Recordset')

Then call the Recordset object's Open method, passing it the SQL statement and your existing Connection object:

recordset.Open(sql, connection)

This loads the query results into the Recordset object. The Recordset object's GetRows method returns an array of columns (not rows, as you might expect), so we'll use the Ruby array's transpose method to convert it to an array of rows:

data = recordset.GetRows.transpose

Close the Connection object by calling its Close method:

recordset.Close

We could create a simple class to wrap up this functionality:

class AccessDb
attr_accessor :mdb, :connection, :data, :fields

def initialize(mdb=nil)
@mdb = mdb
@connection = nil
@data = nil
@fields = nil
end

def open
connection_string = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source='
connection_string << @mdb
@connection = WIN32OLE.new('ADODB.Connection')
@connection.Open(connection_string)
end

def query(sql)
recordset = WIN32OLE.new('ADODB.Recordset')
recordset.Open(sql, @connection)
@fields = []
recordset.Fields.each do |field|
@fields << field.Name
end
begin
@data = recordset.GetRows.transpose
rescue
@data = []
end
recordset.Close
end

def execute(sql)
@connection.Execute(sql)
end

def close
@connection.Close
end
end

This class is very similar to an SqlServer class I previously discussed.
We would use this AccessDb class as follows:

db = AccessDb.new('c:\Baseball\lahman54.mdb')
db.open

db.query("SELECT * FROM AllStar WHERE playerID = 'conceda01';")
field_names = db.fields
rows = db.data

db.execute("INSERT INTO HallOfFame VALUES ('Dave', 'Concepcion');")

db.close

That about wraps up our show for today. As always, feel free to post a comment here or email me with questions, comments, or suggestions.

Thanks for stopping by!

Digg my article

15 comments:

Charles Roper said...

Awesome, this blog gets better and better. This kind of information is really sorely lacking out there in the Rubysphere and you're filling that gap nicely. Kudos.

David Mullet said...

Thank you very much, Charles!

David

Revence said...

I guess Charles says it all very well. Not all, but he tries. :o)

I really think we are missing the message in this post. The power of Win32 OLE has been leveraged here. It's not just accessing DBs. It is using OLE.

Daniel Spiewak said...

So, what about an ActiveRecord provider for Access?

Khaoz said...

It's possible to use ADODB.Connection to work wiht excel files like this python example:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440661

David Mullet said...

@Daniel:

I haven't seen a Jet (MS Access) database adapter yet. I know that an SqlServer adapter exists, though. If it utilizes ADO, it may be possible to modify its connection string to work with Jet databases. Perhaps I'll look into it, if no one else has done so; but I don't know how soon that could be.

Anonymous said...

Dont forget that you need set sql value. In fact that value is the one that is in connection.execute. On the top of the page. connection.Execute("INSERT INTO HallOfFame VALUES ('Dave', 'Concepcion');")
You can change it by:
sql = "INSERT INTO HallOfFame VALUES ('Dave', 'Concepcion');"
I hope this will be usefull. Bye

Anonymous said...

Thanks for the hint in using the powerfull 'win32ole'.
For my needs I tried to use AdoX with Ruby to replace spaces in Access field names, and it worked fine. Here is the code:

# SpaceRep.rb
require 'win32ole'
connection = WIN32OLE.new('ADODB.Connection')
'Data Source = is your Access file
connection.Open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=users.mdb')
cat = WIN32OLE.new("ADOX.Catalog")
cat.ActiveConnection = connection
print "Inform your table name: "
yourtable = gets.chomp
print yourtable, "\n"
# loop through each field in the table replacing the Spaces by what you want - in my case "_"
for col in 0..cat.Tables(yourtable).Columns.Count - 1
cat.Tables(yourtable).Columns(col).Name = cat.Tables(yourtable).Columns(col).Name.gsub(" ", "_")
end
print "Task completed! Check your database."
gets

Anonymous said...

Sorry, please correct the above line to be a ruby comment starting with #, as following:
# Data Source = is your Access file

Patrick Wright said...

Thanks. This post just saved me several hours. I'm a big fan of code which integrates between open source and 'closed' systems, and this particular wedge allows me to crack a closed system's data wide open.

David Mullet said...

You're welcome, Patrick!

"This post just saved me several hours."

Another Ruby on Windows success story!

[grin]

David

daniel said...

A Microsoft Access adapter for Rails has been made (Rails MS Access Adapter).
It probably won't work with the latest ActiveRecord, as it's not an adapter gem; though with older versions it works great.

John said...

Very nice posting. I got a software stellar phoenix access repair which repair and recover corrupted mdb database and MS access files. It is a powerful and robust software and easy to use.

Thanks

Access 2007 AIO Desk Reference for Dummies said...

Wow thanks for this post! It is really helpful.. best regards, nikolai

afx said...

It just works. Thank you!