Al ver el título se estarán preguntando ¿en qué estaba pensando este sujeto al utilizar Oracle con Ruby on Rails teniendo base de datos de código abierto como PostgreSQL o MySQL?, supongo es como pasar al lado oscuro de la fuerza ¿no creen?, pero la verdad es que todos en algún momentos hemos trabajado con tecnologías con licencias pagas. Antes de enamorarme de Rails trabajé durante años con tecnología Oracle, por lo tanto mientras más trabajaba con Rails más crecía mi curiosidad de cómo integrarlo con Oracle y sus distintos objetos PL/SQL (que fue lo que más utilicé) y obviamente probar su desempeño.
Finalmente, luego de varios proyectos, he tenido la oportunidad de integrarlo gracias al requerimiento de un cliente. Sabemos que estas licencias son caras por lo tanto antes de este proyecto solo había podido probarlo en versiones Express gratuitas (Oracle XE), por lo que ésta se convirtió en la oportunidad perfecta para medir desempeño en una base de datos Oracle 11g.
Lo que nos llevó a realizar esta implementación fuera de un entorno experimental fue la necesidad del cliente de contar con un portal web capaz de conectarse con objetos Oracle llamados paquetes (Packages), dentro de estos se encuentran definidos los procedimientos y funciones que contiene lógica de su negocio, los cuáles han ido trabajando durante años y se ajustan a las necesidades de su aplicación principal la cual fue desarrollada en Oracle Forms (capa de presentación) y PL/SQL (capa de negocios), ésta es utilizada en su operativa diaria pero sólo para uso interno y trabajando directamente dentro de su intranet.
Aquí la palabra clave es la “reutilización”. Todos aquellos procesos que existen se podrían reutilizar cambiando básicamente la forma de cómo invocarlos y los tipos de parámetros a utilizar, el propósito es ejecutar procesos complejos con datos simples y/o obtener colecciones de datos (Objetos Cursor) que son realmente lo más importante para visualizar multi-registros. Ahora se estarán preguntando ¿por qué no hacerlo entonces con un Framework JS, por ejemplo AngularJS ó Backbone?, la respuesta es simple: además de que nos encanta Ruby on Rails (como lo habrán notado) también tenemos pensado utilizar ActiveRecord para procesos que no sean específicamente de negocios sino de aplicativo, por ejemplo autenticación, sesiones de usuarios, roles, menús dinámicos, configuración de sistema, parametrización, etc., convirtiendo nuestro proyecto en una aplicación híbrida.
También es posible dejar nuestra aplicación Rails sólo para manejar visualización de componentes HTML, mejor dicho solo utilizar la vista y el controlador, pero ya en ese punto en mi opinión cuestionaría el uso de Ruby on Rails, pero supongo es cuestión de gustos.
Partiendo de esta explicación configuraremos nuestra aplicación para poder trabajar con dos casos: conexión de procesos de negocios en lenguaje procedimental (PL/SQL) y nuestra capa de modelo (ActiveRecord) conectado directamente a Oracle.
Para esta prueba se utilizará un servidor con Linux Centos 7 64bits, con la versión 2.1.2 de Ruby y Ruby on Rails versión 4.2.4, no se instalará Oracle en el servidor ya que este se encuentra en otro servidor físico, la versión de Oracle es 11g pero en teoría esta misma configuración podría funcionar con un Oracle XE ejecutándose localmente.
La instalación de Ruby y RoR se les dejo a ustedes, ahora lo primero que tenemos que hacer es hacer una instalación del cliente Oracle, para esto tendremos que bajar los siguientes archivos de la página web de Oracle:
instantclient-basic-linux.x64-11.2.0.4.0.zip
instantclient-sdk-linux.x64-11.2.0.4.0.zip
instantclient-sqlplus-linux.x64-11.2.0.4.0.zip
Aquí solo debemos descomprimir los archivos en el lugar que nosotros lo necesitamos, en nuestro caso lo haremos en: /opt/oracle
mkdir /opt
mkdir /opt/oracle
cd /opt/oracle
Al hacer unzip de los archivos, nos generará una carpeta llamada “instantclient_11_2/”
unzip instantclient-basic-linux.x64-11.2.0.4.0.zip
unzip instantclient-sdk-linux.x64-11.2.0.4.0.zip
unzip instantclient-sqlplus-linux.x64-11.2.0.4.0.zip
cd /opt/oracle/instantclient_11_2
Creamos un link simbólico
cd instantclient_11_2
ln -s libclntsh.so.11.1 libclntsh.so
Cargamos la librería en nuestra variable de ambiente:
LD_LIBRARY_PATH=/opt/oracle/instantclient_11_2
export LD_LIBRARY_PATH
Para cargarla en cada sesión lo colocamos en el bash profile
vi ~/.bash_profile
LD_LIBRARY_PATH=/opt/oracle/instantclient_11_2
export LD_LIBRARY_PATH
Luego, para conectar Ruby con Oracle necesitamos una interfaz que nos haga las cosas fáciles. Para esto tenemos la gema ruby-oci8, asi q solo tenemos que ejecutar :
gem install ruby-oci8
y el resultado debe ser:
Fetching: ruby-oci8-2.2.1.gem (100%)
Building native extensions. This could take a while...
Successfully installed ruby-oci8-2.2.1
1 gem installed
Para probar que funcione con una conexión remota ejecutamos el siguiente comando:
ruby -r oci8 -e "OCI8.new('usuario', 'password', '101.0.0.178:1521/xe').exec('select \'test\' from dual') do |r| puts r.join(','); end"
IP Servidor remoto: 101.0.0.178
Puerto: 1521
SID: xe
Si la respuesta nos muestra “test” significa que vamos por buen camino.
El siguiente paso es crear una aplicación Ruby on Rails para iniciar nuestras pruebas
rails new app_oracle
cd app_oracle
Dentro de nuestro Gemfile incluimos nuestros Gems necesarios para utilizar objetos Oracle.
#oracle
gem 'ruby-oci8'
gem 'ruby-plsql'
gem 'activerecord-oracle_enhanced-adapter'
El primero ya lo hemos instalado y sabemos para qué funciona, el segundo sirve para conectar con PL/SQL y demás objetos Oracle y el tercero es un adaptador ActiveRecord para conectarse a Oracle.
En config/initializers creamos el archivo oracle.rb y le incluimos lo siguiente:
plsql.activerecord_class = ActiveRecord::Base
Configuramos la base de datos, aquí es importante utilizar distintos usuarios para desarrollo y pruebas básicamente por que dentro de ellos se crean todos los objetos. Esto se hace en caso de que no quieras perder tus datos de prueba.
config/database.yml
default: &default
adapter: oracle_enhanced
pool: 5
timeout: 5000
development:
<<: *default
database: //172.16.247.152:1521/xe
username: dev
password: test
test:
<<: *default
database: //172.16.247.152:1521/xe
username: test
password: test
production:
<<: *default
database: //172.16.247.154:1521/xe
username: prod
password: test
Ahora probemos lo más básico, un scaffold para el mantenimiento de un modelo, en este caso HighScore será nuestro modelo
rails generate scaffold HighScore game:string score:integer
rake db:migrate
rails s
Ponemos en funcionamiento el CRUD del modelo High Score, si todo sale bien deberías poder probarlo sin dificultad
http://localhost:3000/high_scores
Es tiempo de conectarnos con PL/SQL, para esto utilizaremos la gema ruby-plsq la cual nos da una serie de métodos para hacer conexiones a paquetes, procedimientos y funciones. En la primera prueba definiremos un procedimiento dentro un Package al cual le enviaremos una variable y devolveremos la misma variable con una concatenación de texto, algo bastante sencillo.
Primero se define el package specification:
create or replace package test_oracle is
procedure return_same_value(name IN varchar2, result OUT varchar2);
end test_oracle;
Luego definimos el package body
create or replace package body test_oracle is
procedure return_same_value(name IN varchar2, result OUT varchar2) is
begin
result := name|| ': Success' ;
end;
end test_oracle;
Ya devuelta en Rails, se crea una acción dentro del controlador HighScoresController
def get_info_from_oracle
@param_value = plsql.test_oracle.return_same_value(params[:name])[:result]
end
Y se edita routes.rb
resources :high_scores do
collection do
get 'get_info_from_oracle'
end
end
Creamos la vista views/high_scores/ get_info_from_oracle.html.erb
Hello parameter: <%=@param_value%>
En el index se crea un enlace para la prueba views/ high_scores/index.html.erb
<%= link_to 'Test Oracle', get_info_from_oracle_high_scores_path(name:"Calling Oracle Procedure")%>
Si vemos el mensaje de “Success” significa que lo hemos logrado
Ahora para la siguiente prueba creamos en el mismo Package una función (Function) que nos devuelva un cursor que luego iteraremos en la vista. Aquí tenemos que usar un tipo de dato especifico (ref_cursor) en nuestro código PL/SQL.
Specification:
function get_collection(name IN varchar2) return sys_refcursor;
Body:
function get_collection(name IN varchar2) return sys_refcursor is
l_cursor sys_refcursor;
BEGIN
open l_cursor for
SELECT LEVEL, 'John' || LEVEL name, 'Domme' || LEVEL lastname
FROM DUAL
CONNECT BY LEVEL <= 5;
return l_cursor;
END;
Modificamos un poco la acción creada para incluir el llamado a la función PL/SQL:
def get_info_from_oracle
@param_value = plsql.test_oracle.return_same_value(params[:name])[:result]
plsql.test_oracle.get_collection(params[:name]) do |cursor|
@values = cursor.fetch_all
end
end
También modificamos la vista para mostrar los datos provenientes de la función en views/high_scores/get_info_from_oracle.html.erb
<h1>Hello parameter: <%=@param_value%> </h1>
<table border="1">
<tr>
<th>
ID
</th>
<th>
Name
</th>
<th>
Lastname
</th>
</tr>
<%@values.each do |value|%>
<tr>
<td>
<%=value[0]%>
</td>
<td>
<%=value[1]%>
</td>
<td>
<%=value[2]%>
</td>
</tr>
<%end%>
</table>
Refrescamos el navegador y debemos ver nuestra tabla con valores provenientes del Cursor
Estas son las pruebas más básicas de conexión de objetos Oracle. Obviamente existirán procesos que por su complejidad deberán ser cambiados o tratados de una forma diferente, ya sea en Oracle y/o en Rails.
Espero que esta pequeña guía les haya ayudado, si tienen alguna duda o sugerencia no duden en escribirme.