Mittwoch, 23. Januar 2013

Ruby On Rails calling SAP

Some years ago I had to tap several RFC of SAP. Believe me, communicating with SAP is no fun (in fact SAP is the acronym for Shitty Awful Pain or more seriously Slow Account Program), but without the RFC connector of Piers Harding it would have been impossible. Since SAP is a so called monopolist in the ERP layer, you might have to deal with it and know how to do.
First step is to download the current sapnwrfc of Piers:
user$ curl -o sapnwrfc-0.26.tar.gz http://www.piersharding.com/download/ruby/sapnwrfc/sapnwrfc-0.26.tar.gz
untar it:
user$ tar -xzf sapnwrfc-0.26.tar.gz
build a gem:
user$ gem build sapnwrfc.linux.gemspec
and install the generated gem. The name depends on the version and architecture of your machine. For example:
user$ gem install sapnwrfc-0.26-x86_64-darwin-11.gem
Check, if it is working in your irb:
require 'sapnwrfc' 
Preparations finished.
The basic requisite is an existing RFC module you want to interact with and to know the specific connection parameters. Put them into a configuration file (config/sap.yml):
ashost: your.sap_host.com
sysnr: "00"
client: "510"
user: your_user
passwd: your_pass
lang: EN
trace: 1
Furthermore you have to know the SAP RFC you want to call. In my case it is the fictive RFC "zEquipments". I create a model (models/equipment.rb) as the calling Ruby endpoint:
require 'sapnwrfc'
SAPNW::Base.config_location = "#{Rails.root}/config/sap.yml"
SAPNW::Base.load_config

class Equipment
private
  def self.connect(&block)
    rfc_connection = SAPNW::Base.rfc_connect
    discovered_connection = rfc_connection.discover("CRHD")
    rfc = discovered_connection.new_function_call
    block.call rfc
    rfc_connection.close
  end

public
  def self.find_all_by_site site
    equipments = []
    self.connect do |rfc|
      rfc.EQUIPMENT_PARAMS {
        "WERKS" => site.to_s
      }
      rfc.invoke
      rfc.RETURN.each do |equipment|
        equipments << equipment["ARBPL"].strip
      end
    end
    equipments.uniq!
  end

  def self.find_by_objid objid    
    name = nil
    self.connect do |rfc|
      rfc.EQUIPMENT_PARAMS {
        "ARBPL" => objid.to_s
      }
      rfc.invoke
      name = rfc.RETURN.first["ARBPL"].to_s.strip
    end
    name
  end
end
The first line is self-explanatory. The two following lines load the SAP configuration initially. I first coded a separate private class method for connecting to the SAP system, because I use the logic for connecting twice. The first public class finder method "find_all_by_site" expects the name of the site where the equipments are located in. It connects to the RFC and passes the site parameter. The result collects the names of the found equipments and removes all duplicates (you never know with SAP).
Please note the implemented block for connection. I'll cover the use of closures in the post "Yield Your Block in Ruby".
The second public class finder method searches for a certain equipment having a specific OBJID (the unique identifier for every equipment in SAP) and returns its name.
Conclusion: 2 finder methods calling the same SAP RFC with different parameters returning a collection of equipment names or a certain equipment name.

Supported by Ruby 1.9.3, RubyOnRails 3.1 and SapNwRfc 0.26