A Ruby And RPG Conversation
March 17, 2015 Aaron Bartell
“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, which is 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 percent 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 The 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).
--- 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
The first four lines are Ruby require statements that are similar to doing /copy in RPG–bringing outside functionality into the program.
Next we establish the database connection details where a specific library (a.k.a., 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 username and password.
Next we have the configuration of the call to PGM1 in MYLIB by calling XMLService::I_PGM.new, which then uses the left-shift operator (<<) to append the two parameters, mychar1 and mydec1. Note that a default value is also being specified for both parameters, ‘a’ and 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.
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!
Aaron Bartell is Director of IBM i Innovation for Krengel Technology, Inc. Aaron facilitates adoption of open source technologies on IBM i through professional services, staff training, speaking engagements, and the authoring of best practices within industry publications and www.litmis.com. With a strong background in RPG application development, Aaron covers topics that enable IBM i shops to embrace today’s leading technologies including Ruby on Rails, Node.js, Git for RPG source change management and RSpec for unit testing RPG. Aaron is a passionate advocate of vibrant technology communities and the corresponding benefits available for today’s modern application developers. Connect with Aaron via email at firstname.lastname@example.org. Aaron lives with his wife and five children in Southern Minnesota. He enjoys the vast amounts of laughter having a young family brings, along with camping and music. He believes there’s no greater purpose than to give of our life and time to help others.