MySQL Ruby
last modified July 6, 2020
This is a Ruby programming tutorial for the MySQL database. It covers the basics
of MySQL programming with Ruby. It uses the mysql
module. The examples
were created and tested on Ubuntu Linux.
There is a similar MySQL C API tutorial, MySQL Visual Basic tutorial, or MySQL Python tutorial on ZetCode.
You may also consider to look at the MySQL tutorial, too.
MySQL & Ruby
MySQL is a leading open source database management system. It is a multi user, multithreaded database management system. MySQL is especially popular on the web. It is on part of the very popular LAMP platform consisting of Linux, Apache, MySQL, and PHP. Currently MySQL is owned by Oracle. MySQL database is available on most important OS platforms. Ruby is a dynamic, reflective, general-purpose object-oriented programming language. Recently it became very popular in web programming, mainly due to the successful Ruby on Rails framework.
mysql module
The mysql
module is a Ruby interface to the MySQL server.
It provides the same functions for Ruby programs that the
MySQL C API provides for C programs.
$ sudo gem1.9 install mysql
Here we install the Ruby module for the MySQL database.
Before we start
We are going to create a new database user and a new database.
To do this, we use the mysql
client program.
$ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 30 Server version: 5.0.67-0ubuntu6 (Ubuntu) Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | +--------------------+ 2 rows in set (0.00 sec)
We connect to the MySQL server using the root account. We show all available
databases with the SHOW DATABASES
statement.
mysql> CREATE DATABASE mydb; Query OK, 1 row affected (0.02 sec)
We create a new mydb
database. We will use this database throughout
the tutorial.
mysql> CREATE USER user12@localhost IDENTIFIED BY '34klq*'; Query OK, 0 rows affected (0.00 sec) mysql> USE mydb; Database changed mysql> GRANT ALL ON mydb.* to user12@localhost; Query OK, 0 rows affected (0.00 sec) mysql> quit; Bye
We create a new database user. We grant all privileges to this user
for all tables of the mydb
database.
MySQL server version
In the first example, we will get the version of the MySQL database.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*' puts con.get_server_info rs = con.query 'SELECT VERSION()' puts rs.fetch_row rescue Mysql::Error => e puts e.errno puts e.error ensure con.close if con end
In this script, we get the server version. We do it in two different ways.
require 'mysql'
We import the mysql
module. The module has the classes and
methods to work with the MySQL database.
con = Mysql.new 'localhost', 'user12', '34klq*'
We create the connection object. The parameters include the host name, user name and password. In our case the host name is localhost, e.g. our computer.
puts con.get_server_info
The Mysql object that we have created has a get_server_info
method.
It returns the version of the MySQL server installed.
rs = con.query 'SELECT VERSION()' puts rs.fetch_row
Another way to get the version is to execute the SELECT VERSION
SQL statement. We fetch the data. Since we retrieve only one record, we
call the fetch_row
method.
rescue Mysql::Error => e puts e.errno puts e.error
We check for errors. This is important, since working with databases is error prone.
ensure con.close if con end
In the end, we release the resources.
$ ./version.rb 5.5.9 5.5.9
The output might look like the above.
Listing databases
The MySQL Ruby module has a list_dbs
method, which returns
available databases.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*' con.list_dbs.each do |db| puts db end rescue Mysql::Error => e puts e.errno puts e.error ensure con.close if con end
In this script, we print all available databases on our MySQL server.
con.list_dbs.each do |db| puts db end
The list_dbs
method returns an array of available database
names. Using the each method of the array, we print each item
of the array to the console.
$ ./listdb.rb information_schema mydb test world
On my system, I had the above databases created.
Creating and populating a table
We create a table and populate it with some data.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' con.query("CREATE TABLE IF NOT EXISTS \ Writers(Id INT PRIMARY KEY AUTO_INCREMENT, Name VARCHAR(25))") con.query("INSERT INTO Writers(Name) VALUES('Jack London')") con.query("INSERT INTO Writers(Name) VALUES('Honore de Balzac')") con.query("INSERT INTO Writers(Name) VALUES('Lion Feuchtwanger')") con.query("INSERT INTO Writers(Name) VALUES('Emile Zola')") con.query("INSERT INTO Writers(Name) VALUES('Truman Capote')") rescue Mysql::Error => e puts e.errno puts e.error ensure con.close if con end
We create a Writers table and add five authors to it.
con.query("CREATE TABLE IF NOT EXISTS \ Writers(Id INT PRIMARY KEY AUTO_INCREMENT, Name VARCHAR(25))")
To execute an SQL statement, we use the query
method.
This SQL statement creates a new database table called Writers. It has
two columns. Id and Name.
con.query("INSERT INTO Writers(Name) VALUES('Jack London')") con.query("INSERT INTO Writers(Name) VALUES('Honore de Balzac')") ...
We use the INSERT
statement to insert authors to the table. Here
we add two rows.
mysql> SELECT * FROM Writers; +----+-------------------+ | Id | Name | +----+-------------------+ | 1 | Jack London | | 2 | Honore de Balzac | | 3 | Lion Feuchtwanger | | 4 | Emile Zola | | 5 | Truman Capote | +----+-------------------+ 5 rows in set (0.00 sec)
After executing the script, we use the mysql
client tool to
select all data from the Writers table.
Retrieving data
Now that we have inserted some data into the database, we can retrieve it back.
#!/usr/bin/ruby require "mysql" begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' rs = con.query("SELECT * FROM Writers") n_rows = rs.num_rows puts "There are #{n_rows} rows in the result set" n_rows.times do puts rs.fetch_row.join("\s") end rescue Mysql::Error => e puts e.errno puts e.error ensure con.close if con end
In this example, we retrieve all data from the Writers table.
con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb'
The last parameter of the constructor is the database name, to which we connect.
rs = con.query("SELECT * FROM Writers")
This SQL statement selects all data from the Writers
table.
n_rows = rs.num_rows
We get the number of rows in the result set using the num_rows
method of the result set object.
n_rows.times do puts rs.fetch_row.join("\s") end
Here we fetch each row with the fetch_row
method. It returns
a row as an array of fields. By default, the fields are separated by new
line, when printed. With the join
method, we print each row
on one line. The fields are separated by one space.
$ ./retrieve1.rb There are 5 rows in the result set 1 Jack London 2 Honore de Balzac 3 Lion Feuchtwanger 4 Emile Zola 5 Truman Capote
Next we present another way to retrieve data from the table.
#!/usr/bin/ruby require "mysql" begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' rs = con.query("SELECT * FROM Writers") rs.each do |row| puts row.join("\s") end rescue Mysql::Error => e puts e.errno puts e.error ensure con.close if con end
We print all data from the Writers
table. This time we use
an each method of the result set to traverse the data.
rs.each do |row| puts row.join("\s") end
We iterate through the result set using the each method.
$ ./retrieve2.rb 1 Jack London 2 Honore de Balzac 3 Lion Feuchtwanger 4 Emile Zola 5 Truman Capote
This is the output of the example.
We can traverse data in form of a Ruby hash.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' rs = con.query "SELECT * FROM Writers WHERE Id IN (1, 2, 3)" puts "We have #{rs.num_rows} row(s)" rs.each_hash do |row| puts row['Id'] + " " + row['Name'] end rescue Mysql::Error => e puts e ensure con.close if con end
In the example, we use the each_hash
iterator. Records from
the result set can be retrieved by their column names.
rs.each_hash do |row| puts row['Id'] + " " + row['Name'] end
We go through the result set with the each_hash
method.
Each returned row is a Ruby hash; a collection of key-value pairs. The keys
are the column names.
$ ./retrieve3.rb We have 3 row(s) 1 Jack London 2 Honore de Balzac 3 Lion Feuchtwanger
Ouput of the example.
Multiple statements
MySQL supports multiple statement execution. This must be enabled by a special option.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' con.set_server_option Mysql::OPTION_MULTI_STATEMENTS_ON rs = con.query "SELECT Name FROM Writers WHERE Id=1; SELECT Name FROM Writers WHERE Id=2; SELECT Name FROM Writers WHERE Id=3" puts rs.fetch_row while con.next_result rs = con.store_result puts rs.fetch_row end rescue Mysql::Error => e puts e.errno puts e.error ensure con.close if con end
In this example, we have three SELECT
statements in one query.
con.set_server_option Mysql::OPTION_MULTI_STATEMENTS_ON
First we need to enable the multiple statements processing
with the Mysql::OPTION_MULTI_STATEMENTS_ON
.
rs = con.query "SELECT Name FROM Writers WHERE Id=1; SELECT Name FROM Writers WHERE Id=2; SELECT Name FROM Writers WHERE Id=3"
Here we define three SELECT
statements. They are separated by
a semicolon.
puts rs.fetch_row
The query method returns the first result set. We fetch a row from this result set.
while con.next_result rs = con.store_result puts rs.fetch_row end
We get additional result sets until there are no more statements left to process.
$ ./multiplest.rb Jack London Honore de Balzac Lion Feuchtwanger
Running the example.
Metadata
Metadata is information about the data in the database. Metadata in a MySQL system contains information about the tables and columns, in which we store data. Number of rows affected by an SQL statement is a metadata. Number of rows and columns returned in a result set belong to metadata as well.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' rs = con.query "SELECT * FROM Writers WHERE Id IN (1, 2, 3)" puts "We have #{con.field_count} fields" puts "We have #{rs.num_rows} row(s)" puts rs.fetch_row.join("\s") rescue Mysql::Error => e puts e ensure con.close if con end
In this script, we find out the number of rows and columns from an SQL query.
rs = con.query "SELECT * FROM Writers WHERE Id IN (1, 2, 3)"
This SQL statement returns three rows. Each row has two columns.
puts "We have #{con.field_count} fields" puts "We have #{rs.num_rows} row(s)"
These two lines return the number of columns and rows in the result set. Note that here a field is a synonym for a column. The returned data is metadata.
puts rs.fetch_row.join("\s")
Here we return one row from the result set. This is the original data stored in our database table.
For INSERT
, DELETE
and UPDATE
statements
there is a method called rows_affected
. This method returns the number
of rows affected by these three statements.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' con.query "DELETE FROM Writers WHERE Id IN (1, 2, 3)" puts "The query has affected #{con.affected_rows} rows" rescue Mysql::Error => e puts e ensure con.close if con end
In our example, we delete first three rows from the Writers
table.
con.query "DELETE FROM Writers WHERE Id IN (1, 2, 3)"
An SQL statement, which deletes first three rows of the Writers
table.
puts "The query has affected #{con.affected_rows} rows"
Here we get the number of rows that were affected by the above SQL statement. This number belongs to the metadata.
$ ./affected.rb The query has affected 3 rows mysql> SELECT * FROM Writers; +----+---------------+ | Id | Name | +----+---------------+ | 4 | Emile Zola | | 5 | Truman Capote | +----+---------------+ 2 rows in set (0.00 sec)
We execute the affected.rb script and check for changes in the
Writers
table. Three rows have been deleted.
In the next example, we are going to check for metadata about a field.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' rs = con.query "SELECT * FROM Writers WHERE Id=1" field = rs.fetch_field_direct 1 puts "Table name: #{field.table}" puts "Field name: #{field.name}" puts "Field length: #{field.length}" puts "Field type: #{field.type}" rescue Mysql::Error => e puts e ensure con.close if con end
We get one record from the database. We get the field's table name, colum name, length and type.
rs = con.query "SELECT * FROM Writers WHERE Id=1"
This query returns one row. It has two columns.
field = rs.fetch_field_direct 1
Using the fetch_field_direct
method, we get
a specific record. More precisely, the record from the
intersection of the first row, second column.
puts "Table name: #{field.table}" puts "Field name: #{field.name}" puts "Field length: #{field.length}" puts "Field type: #{field.type}"
We get the metadata using attribute readers of the field object.
$ ./metadata.rb Table name: Writers Field name: Name Field length: 25 Field type: 253
This is the output of the example.
In our last example relating to the metadata, we will print all rows from the table with their column names.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' rs = con.query "SELECT * FROM Writers" fields = rs.fetch_fields puts "%3s %s" % [fields[0].name, fields[1].name] rs.each_hash do |row| puts "%3s %s" % [row['Id'], row['Name']] end rescue Mysql::Error => e puts e ensure con.close if con end
We print the contents of the Writers
table to the
console. Now, we include the names of the columns too.
fields = rs.fetch_fields puts "%3s %s" % [fields[0].name, fields[1].name]
In the first step, we get the column names. They are printed using the standard Ruby string formatting abilities.
rs.each_hash do |row| puts "%3s %s" % [row['Id'], row['Name']] end
Now the data is fechted and printed to the console. We do some formatting too.
$ ./columnheaders.rb Id Name 1 Jack London 2 Honore de Balzac 3 Lion Feuchtwanger 4 Emile Zola 5 Truman Capote
Ouput of the script.
Prepared statements
Now we will concern ourselves with prepared statements. When we write prepared statements, we use placeholders instead of directly writing the values into the statements. Prepared statements increase security and performance.
#!/usr/bin/ruby require 'mysql' name = "Stefan Zweig" begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' pst = con.prepare "INSERT INTO Writers(Name) VALUES(?)" pst.execute name rescue Mysql::Error => e puts e ensure con.close if con pst.close if pst end
In the above example, we insert a new row into the Writers
table. We use a prepared statement.
pst = con.prepare "INSERT INTO Writers(Name) VALUES(?)"
The prepare
method is used to create a prepared
statement. The ?
character is a placeholder. Later
we bind a value to this placeholder.
pst.execute name
We bind a value from the name variable to the placeholder and execute the prepared statement.
pst.close if pst
The prepared statement is closed.
mysql> SELECT * FROM Writers; +----+-------------------+ | Id | Name | +----+-------------------+ | 1 | Jack London | | 2 | Honore de Balzac | | 3 | Lion Feuchtwanger | | 4 | Emile Zola | | 5 | Truman Capote | | 6 | Stefan Zweig | +----+-------------------+ 6 rows in set (0.00 sec)
After the script was successfully run, we see a new author in the Writers table.
Writing images
Some people prefer to put their images into the database, some prefer to keep them
on the file system for their applications. Technical difficulties arise when we
work with lots of images. Images are binary data. MySQL database has a special
data type to store binary data called BLOB
(Binary Large Object).
mysql> CREATE TABLE Images(Id INT PRIMARY KEY AUTO_INCREMENT, Data MEDIUMBLOB); Query OK, 0 rows affected (0.06 sec)
For this example, we create a new table called Images
.
#!/usr/bin/ruby require 'mysql' begin fin = File.open("woman.jpg" , "rb") img = fin.read rescue SystemCallError => e puts e ensure fin.close if fin end begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' pst = con.prepare("INSERT INTO Images SET Data='%s'" % img.unpack('H*')) pst.execute rescue Mysql::Error => e puts e ensure con.close if con pst.close if pst end
In the above script, we read a JPG image and insert it into the Images
table.
fin = File.open("woman.jpg" , "rb") img = fin.read
We open and read an image. The read
method returns the data as string.
pst = con.prepare("INSERT INTO Images SET Data='%s'" % img.unpack('H*'))
This string data is placed into the prepared statement. Before doing so, it is
decoded using the unpack
method of the Ruby string object.
Decoding is necessary, because the image object has many special characters which cannot
be processed normally.
Reading images
In the previous example, we have inserted an image into the database table. Now we are going to read the image back from the table.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' rs = con.query "SELECT Data FROM Images LIMIT 1" f = File.new "woman2.jpg", "wb" f.write rs.fetch_row.pack 'H*' rescue Mysql::Error, SystemCallError => e puts e ensure con.close if con f.close if f end
We read one image from the Images table.
rs = con.query "SELECT Data FROM Images LIMIT 1"
We select one record from the table.
f = File.new "woman2.jpg", "wb"
We create a writable binary file.
f.write rs.fetch_row.pack 'H*'
We fetch the data from the previous SQL statement and write it to
the file. The fetch_row
method returns an array object.
Before the data is written to the
file, it is put back into the original format with the pack
method of the array. For both operations, decoding and reversing, we use
the same directive, 'H*'
. It stands for hex string.
Now we should have an image called woman2.jpg
in
our current directory. We can check if it is the same
image that we have inserted into the table.
Transaction support
A transaction
is an atomic unit of database operations
against the data in one or more databases. The effects of all the
SQL statements in a transaction can be either all committed to
the database or all rolled back.
By default, MySQL runs in the autocommit mode. In this mode, all changes to the tables
are immediately effective. To prevent this, we have to turn off the autocommit mode.
After disabling autocommit, changes to
transaction-aware tables are not permanent immediately. To store the changes,
we must call the COMMIT
statement or ROLLBACK
to revert them. The Ruby MySQL has convenience methods for these SQL statements,
commit
and rollback
.
The MySQL database has different types of storage engines. The most common are the MyISAM
and the InnoDB engines. There is a trade-off between data security and
database speed. The MyISAM tables are faster to process and they do not support transactions.
The commit
and rollback
methods are not implemented.
They do nothing. On the other hand, the InnoDB tables are more safe against the data loss.
They support transactions. They are slower to process.
mysql> SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES -> where TABLE_SCHEMA = 'mydb' AND TABLE_NAME='Writers'; +------------+--------+ | TABLE_NAME | ENGINE | +------------+--------+ | Writers | InnoDB | +------------+--------+ 1 row in set (0.00 sec)
The engine of the Writers
table is InnoDB, which
supports transactions.
#!/usr/bin/ruby require 'mysql' begin con = Mysql.new 'localhost', 'user12', '34klq*', 'mydb' con.autocommit false pst = con.prepare "UPDATE Writers SET Name = ? WHERE Id = ?" pst.execute "Leo Tolstoy", "1" pst.execute "Boris Pasternak", "2" pst.execute "Leonid Leonov" con.commit rescue Mysql::Error => e puts e con.rollback ensure pst.close if pst con.close if con end
In this script, we try to update three rows.
con.autocommit false
The autocommit mode is disabled.
pst = con.prepare "UPDATE Writers SET Name = ? WHERE Id = ?" pst.execute "Leo Tolstoy", "1" pst.execute "Boris Pasternak", "2" pst.execute "Leonid Leonov"
We execute three UPDATE
statements. The last one
is incorrect. The second parameter is missing.
con.commit
If all is OK, changes are committed to the table.
rescue Mysql::Error => e puts e con.rollback
In case of an error, changes are rolled back.
$ ./update.rb execute: param_count(2) != number of argument(1) mysql> SELECT * FROM Writers; +----+-------------------+ | Id | Name | +----+-------------------+ | 1 | Jack London | | 2 | Honore de Balzac | | 3 | Lion Feuchtwanger | | 4 | Emile Zola | | 5 | Truman Capote | | 6 | Stefan Zweig | +----+-------------------+ 6 rows in set (0.00 sec)
Running the script gives an error. However, the transaction was rolled back and the first two rows were not changed.
This was MySQL Ruby tutorial. You may be also interested in PostgreSQL Ruby tutorial, PyMySQL tutorial, SQLite Ruby tutorial, or MongoDB Ruby tutorial.