Using the ibm_db gem, we can easily communicate with DB2 for i directory from Ruby.
In the article "Sinatra Sings, Ruby Plays," I introduced the Sinatra web framework , and we went through initial installation and a simple Hello World application. In this article, we will take that app further by introducing database access. Specifically, we will use the ibm_db Gem to query a DB2 table named USERS and display the contents back to the web page.
The ibm_db gem, included in the PowerRuby install, implements Rails' ActiveRecord pattern, which makes database interaction significantly simpler. As part of this exercise, we will install the sinatra-activerecord gem that "...extends Sinatra with extension methods and Rake tasks for dealing with an SQL database using the ActiveRecord ORM."
Instead of using the gem install command to install gems, we are going to use Bundler, which will provide a consistent environment for Ruby (i.e., Sinatra) projects by tracking and installing the exact gems and versions that are needed.
First, create the application directory if it doesn't yet exist, as shown below. Make sure to replace my name with your profile.
$ mkdir /home/aaron/git/sinatra_db
Change the directory into the new folder and run the bundle init command, which will create a file named Gemfile, as shown below.
$ cd /home/aaron/git/sinatra_db
$ bundle init
Writing new Gemfile to /home/aaron/git/sinatra_db/Gemfile
A Gemfile file is used to declare all of the gems our application requires. Occupy the Gemfile file with the contents below.
source "https://rubygems.org"
gem "activerecord", "4.0.12"
gem "sinatra"
gem "sinatra-activerecord"
The gems mentioned in Gemfile do not yet exist on the local machine, except for activerecord, which is delivered with PowerRuby. Before we install them using the bundle install command, we need to create a new folder for where the gems will be installed and set the GEM_HOME environment to that location, as shown below.
$ mkdir /home/aaron/gemsets/sinatra_db
$ export GEM_HOME=/home/aaron/gemsets/sinatra_db
Now run bundle install, which will review the Gemfile and check whether those gems already exist on the local machine. If gems don't exist, then they will be downloaded from RubyGems.org and placed into /home/aaron/gemsets/sinatra_db, as shown below. If it finds the gems, then it will state, "Using <gem name>", as shown below.
$ bundle install
Fetching gem metadata from https://rubygems.org/...........
Resolving dependencies...
Installing i18n 0.7.0
Using minitest 4.7.5
Using multi_json 1.10.1
Using thread_safe 0.3.4
Using tzinfo 0.3.42
Using activesupport 4.0.12
Using builder 3.1.4
Using activemodel 4.0.12
Using activerecord-deprecated_finders 1.0.3
Using arel 4.0.2
Using activerecord 4.0.12
Installing rack 1.6.0
Installing rack-protection 1.5.3
Using tilt 1.4.1
Installing sinatra 1.4.5
Installing sinatra-activerecord 2.0.4
Using bundler 1.7.6
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
Now that the Gems are installed we need to alter our GEM_PATH environment variable to include our new folder, as shown below. If we didn't do this, when we started our application it would error out saying it couldn't find certain Gems.
$ export GEM_PATH=/home/aaron/gemsets/sinatra_db:/PowerRuby/prV2R0/lib/ruby/gems/2.0.0
Now it's time to put together the app.rb file, which will contain the entirety of the application's functionality minus the view, shown in the code listing below.
require 'sinatra'
require 'sinatra/activerecord'
require 'active_record'
require 'ibm_db'
ActiveRecord::Base.establish_connection(
adapter: 'ibm_db',
database: '*LOCAL',
schema: 'MYLIB',
username: 'MYUSER',
password: 'MYUSER4321'
)
class User < ActiveRecord::Base
end
get '/' do
@users = User.all
erb :index, :locals => {:users => @users}
end
Listing 1: File app.rb contains the database connection, model, and request routing.
The first things we see are the require statements to bring in the necessary gem capabilities. Next, we declare and establish the database connection using ActiveRecord. You can use your own schema (aka library), username, and password; or use the ones defined later in this article. Next in Listing 1 we see the model declaration for the yet-to-be-created User table. This is an interesting aspect of Ruby in that I can include my models (aka database access layer) right in the app.rb file. Or I could instead put it in a file named models/user.rb. We are including the User class in app.rb for example and brevity's sake, but the best practice would be a separate file. The last thing we see is a single routing entry that will monitor for requests that come into the root of the application, i.e., "/". When a request comes into the root, it will get all user rows using User.all and place them into an array variable named @users. More on @users and the next erb line in a little bit.
Next, create what's called a database migration. Migrations allow you to evolve your database over time by storing the incremental modifications in separate time-stamped files. To use ActiveRecord's database migrations, we first need to create a file named Rakefile (no file extension) and occupy it with the following contents. This is necessary so that when we run the rake command, it will be able to resolve to the command we pass it (i.e., db:create_migration).
require './app'
require 'sinatra/activerecord/rake'
Now we are ready to generate a migration skeleton with the following command. As you can see from the logged output, it creates a directory structure of db/migrate and a file with the name we specified on our command.
$ rake db:create_migration NAME=create_users
db/migrate/20150129202229_create_users.rb
The migration doesn't know the makeup of the table we wish to create, so it's necessary to open the file ending in create_users.rb and enter the below code.
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :address
t.string :city
t.string :state
t.string :zip
t.text :note
t.timestamps
end
end
end
Migrations use what's called a Ruby DSL, or Domain Specific Language. The create_table line is actually a Ruby method being invoked, and the content of the Ruby block—from do to end—is where columns are defined for the new table. I consider this incredibly slick and a big timesaver over having to manually write out platform-specific SQL statements.
Before we can run this database migration, we first need to create the database using the below CREATE COLLECTION command within the STRSQL interface. I've also included CRTUSRPRF and CHGAUT commands for ease of setup, though feel free to use your own profile.
5250-STRSQL> CREATE COLLECTION MYLIB
5250> CRTUSRPRF USRPRF(myuser) PASSWORD() INLMNU(*SIGNOFF) USRCLS(*USER)
5250> CHGAUT OBJ('/qsys.lib/mylib.lib') USER(myuser) DTAAUT(*RWX) OBJAUT(*ALL)
Now that the schema defined in app.rb exists, we can run the below rake db:migrate command. This will look for table MYLIB/SCHEMA_MIGRATIONS and compare the migrations in it to what exists in the applications db/migrate folder. It will then run any outstanding migrations.
$ rake db:migrate
== 20150129202229 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.1460s
== 20150129202229 CreateUsers: migrated (0.1463s) =============================
At this point, we've set up the database and the controller and model layers, but we haven't yet touched the view layer, where we define the HTML. First create a views folder, as shown below.
$ mkdir views
Next, create a file named index.erb in the views folder and occupy it with the code in Listing 2. Here's where the previously mentioned @users array variable comes back into play. Variables starting with an @ symbol in the Ruby language are considered instance variables and when created in a controller (i.e., app.rb) can be made available to the view layer (i.e., index.erb).
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.first_name %></td>
<td><%= user.last_name %></td>
</tr>
<% end %>
</tbody>
</table>
Listing 2: File views/index.erb contains the HTML.
Going back to Listing 1, you'll remember the following line. This is declaring the name of the erb file to look for—namely, index—and what local variables to make available to it—namely, @users.
erb :index, :locals => {:users => @users}
At this point, the application is ready to be run, except for one issue. We have no data in the USERS table! We could add DB2 rows using UPDDTA or another tool, but I want to show you how it can be done using Interactive Ruby Shell (IRB). You can copy and paste the below into an irb session and it will effectively configure that session to be similar to the actual web application being invoked by a user from the browser. It loads the active_record and ibm_db gems, establishes a connection, declares the Ruby model, and finally it runs a small bit of code that will generate 10 new rows using the User.create method.
require 'active_record'
require 'ibm_db'
ActiveRecord::Base.establish_connection(
adapter: 'ibm_db',
database: '*LOCAL',
schema: 'MYLIB',
username: 'MYUSER',
password: 'MYUSER4321'
)
class User < ActiveRecord::Base
end
10.times.each { |i| User.create first_name: "Name#{i}", last_name:"Last#{i}" }
Now it is finally time to start the application using the following command:
$ ruby app.rb -o0.0.0.0
== Sinatra/1.4.5 has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.2 codename Doc Brown)
Maximum connections set to 1024
Listening on 0.0.0.0:4567, CTRL+C to stop
To see if it's working point your browser at http://<machine_ip>:4567 and you should see something similar to Figure 1.
Figure 1: The browser displays rows from the USERS table.
If you've done Rails before, you'll notice Sinatra requires a lot of the same things yet doesn't lay out as much infrastructure. For example, when you run the rails new command, it fleshes out an entire application's infrastructure in seconds, including folders for models, views, and controllers. It also creates files to hold things like database configurations. In Sinatra, that is all created by hand.
So why use Sinatra? There might be times where Rails is overkill. For example, maybe you just need a simple API + JSON server for responding to AJAX or non-browser web service calls. It is worth noting that Rails is very componentized, so there is the opportunity to remove components you don't need. I'd highly recommend playing with both and making your own determination.
LATEST COMMENTS
MC Press Online