A Ruby and RPG Conversation
Originally published: IT Jungle on 3/17/2015
Updated author notes: The Ruby iToolkit is now maintained by Litmis and IBM on Bitbucket.
“Due Diligence” and “Risk Assessment” are phrases that should be running through your head anytime technology decisions are being made where new tooling or ideas are being put into production. The same is true when considering whether the Ruby language has a place in your shop – after-all, it is a significant change in direction when introducing a new language to your technology stack. What many people don’t know is the adoption of Ruby (and the Rails web framework) can be done in incremental fashion if that is what works best for you. What I mean by that is not only can you make use of your existing DB2 tables but also your significant investment in RPG code. How? Well, that’s what this article is all about. Let’s dive in.
The Ruby language has this concept called RubyGems – a package manager for ease of distributing and maintaining Ruby code (usually open source). Think of a RubyGem being similar to RPG *SRVPGMs being stored in a *SAVF for distribution to other machines, except all done based on an agreed upon specification that includes tools for downloading, installing and updating. Or maybe better stated, imagine a world where we had so much open source RPG code that we had to come up with streamlined ways to share it – that’s RubyGems. I will cover RubyGems more thoroughly in a future article.
Included with PowerRuby is a RubyGem named the xmlservice gem. The xmlservice gem is a native-to-Ruby way of communicating with the platform-and-language-agnostic XMLSERVICE open source project developed in IBM Rochester by IBM’er Tony Cairns. The xmlservice gem can be used to communicate with nearly any resource on IBM i including DB2 tables, data queues, and most notably an RPG program or service program.
Below we have a very simple RPG program that receives in two parameters passed by reference, alters their values, and then returns. Note, this is the new 100% free-form RPG introduced in V7.1 TR7. If you are like me you might still be adjusting. I didn’t want to be told I wasn’t modern with RPG so I am trying to change my ways 😉
--- MYLIB/PGM1 --- dcl-pr pgm1 extpgm; char1 char(1); dec1 packed(7:4); end-pr; dcl-pi pgm1; char1 char(1); dec1 packed(7:4); end-pi; char1 = 'C'; dec1 = 321.1234; return;
Our goal is to invoke PGM1 from Ruby and display the value back to the screen. In my article Testing Ruby Waters, I introduced the idea of testing Ruby code from irb (interactive Ruby shell) and that’s what we will do here. Below we have the entirety of the Ruby code necessary to invoke the aforementioned RPG program using the xmlservice gem. In this scenario we are making use of the database adapter for communication with the other option being the RESTful adapter (i.e. invoke the RPG program via an HTTP request).
The first four lines are Ruby
require statements which are similar to doing
/copy in RPG – bringing outside functionality into the program.
Next we establish the database connection details where a specific library (aka schema) needs to be specified, though this has no bearing on where the actual RPG program resides most likely because the world of database adapters isn’t used to the concept of storing executable programs inside of what they’d consider a database schema. In short, you will need to supply an existing library for the
schema value and also a valid
Next we have the configuration of the call to
MYLIB by calling
XMLService::I_PGM.new which then uses the left-shift operator (<<) to append the two parameters,
mydec1. Note that a default value is also being specified for both parameters,
11.1111 respectively. You could have instead used variables but I’ve hard-coded for the sake of brevity.
At this point the variable
pgm1 contains all the information it needs to invoke the RPG program. On the next line the
call method does the actual invocation of RPG and waits for a response. As mentioned before, this particular scenario we are calling RPG through a DB2 connection which is effectively a generic stored procedure – very fast.
The last two
puts lines are conveying the resulting variable values to the terminal.
--- Lines of Ruby code to paste into IRB --- require 'active_support' require 'active_record' require 'ibm_db' require 'xmlservice' ActiveRecord::Base.establish_connection( adapter: 'ibm_db', database: '*LOCAL', schema: 'MYLIB', username: 'xxxxx', password: 'xxxxx' ) pgm1 = XMLService::I_PGM.new("PGM1", 'MYLIB') << XMLService::I_a.new('mychar1', 1, 'a') << XMLService::I_p.new('mydec1', 7, 4, 11.1111) pgm1.call puts pgm1.response.mychar1 puts pgm1.response.mydec1
When you paste the above into an irb session it will have a lot more output conveyed back to you, as shown in this gist. What is a gist you ask? A way to easily share a snippet of code, or logs, or other text; either privately or with the world.
The above code would be too verbose to use multiple times in an application so it would be better to encapsulate it into something like the following:
require 'active_support' require 'active_record' require 'ibm_db' require 'xmlservice' class Pgm1 attr_accessor :char1, :dec1 def initialize ActiveRecord::Base.establish_connection( adapter: 'ibm_db', database: '*LOCAL', schema: 'MYLIB', username: 'xxxxx', password: 'xxxxx' ) @char1 = nil @dec1 = nil end def call(c1, d1) pgm1 = XMLService::I_PGM.new("PGM1", 'MYLIB') << XMLService::I_a.new('mychar1', 1, c1) << XMLService::I_p.new('mydec1', 7, 4, d1) pgm1.call @char1 = pgm1.response.mychar1 @dec1 = pgm1.response.mydec1 end end
Then you can more simply invoke it as follows.
p = Pgm1.new p.call('a', 11.1111) puts p.char1 puts p.dec1
Another code refinement would be to place the
establish_connection portion in a separate file rather than have it in each Ruby class that represents an RPG program. As you may have guessed, we’ve officially ventured into authoring object oriented Ruby code without me first explaining it. I did that on purpose because sometimes it’s better to have a full working example and then go back and learn the individual pieces. Here is a good one-pager to learn the object oriented aspects of Ruby.
That wraps up this installment of Ruby learning. It is worth noting that the XMLSERVICE API is very flexible when compared to its predecessors (i.e. PCML, Java Toolbox) and the simplicity of my example is not a reflection of XMLSERVICE’s capabilities. It should also be noted that XMLSERVICE allows you to create stateful jobs on the server side so you can make subsequent calls and have things be retained (i.e. open data paths, global variables in a service program, etc). This opens the door for Ruby and XMLSERVICE to be used for RPG unit testing – something I hope to cover in a future article. Stay tuned!
- Call web APIs from RPG
- Process XML and JSON
- Offer web services
Affordable IBM i cloud hosting
CyberSource Toolkit for i
- Process credit cards from native RPG
- Integrate CyberSource payment services
Need a jumpstart with Ruby on IBM i? Contact us to start a conversation.