Hace un par de semanas tuve la oportunidad de presentar dos webcast/VAN, uno para el Club de .NET de la Universitat Oberta de Catalunya y para AltNet Hispano.
Si bien ambos los puede llevar a cabo no sin algunos contratiempos, ya que en esas fechas tuvimos una tormenta en la ciudad y hubo cortes de energía, pero finalmente puede participar.
Este post tiene como finalidad el realizar una recapitulación de ambas presentaciones ya que si bien las dos fueron en relación a IronRuby el enfoque fue un poco diferente.
¿Que es IronRuby?
Bueno esta pregunta se tiene que responder primero comentado que es Ruby.
Ruby es un lenguaje dinámico bajo el paradigma de Orientado a Objetos, con sintaxis inspirada en Python, Perl y SmallTalk. Lenguaje que vio la luz publica en 1995 bajo la mano de Yukihiro "Matz" Matsumoto.
Ruby es un lenguaje moderno que cuenta con características que son estándar en lenguajes modernos, como: manejo de excepciones, iteradores y clausuras, sobre carga de operadores, recolección de basura, portable, carga dinámica de librerías, introspeccion-reflexion-metaprogramacion entre otras-; ademas de que la sintaxis esta diseñada pensando en la productividad y diversión del desarrollador y sobre todo que sea lo mas natural al lenguaje humano.
Algo importante a notar es que Ruby es un lenguaje distribuido y licenciado bajo una licencia de Software Libre.
Ahora si vamos a al origen de la pregunta, ¿Que es IronRuby?, pues IronRuby es la implementación del lenguaje Ruby como un lenguaje dinámico para .NET, llevada a cabo por Microsoft.
IronRuby ofrece un interprete de Ruby compatible con la version 1.8.6, el cual permite ejecutar aplicaciones de Ruby de manera transparente o casi transparente, ademas de permitir a los programas que se ejecutan bajo IronRuby el poder interactuar con las librerías de .NET, e inclusive incluir dentro de una aplicación de .NET funcionalidad de "scripting" con el lenguaje Ruby.
IronRuby como se menciono es desarrollado por Microsoft y esta disponible bajo una licencia de Software Libre. Aunque aun no ha alcanzado si versión 1.0 - al momento de este post están en su RC 1 -, es lo suficientemente maduro para ser utilizado para el desarrollo de aplicaciones.
¿Porque usar IronRuby?
Es una pregunta un tanto difícil de contestar sobre todo porque depende de la perspectiva y visión de cada desarrollador, por lo tanto voy a contestarla desde mi visión.
Ruby cuenta con una gran variedad de herramientas maduras para realizar pruebas de código bajo los enfoques TDD y BDD ademas de herramientas para realizar Mocking de objetos, si bien en .NET también las hay, existen algunos proyectos que intentan replicar algunas de estas en C#, pero entonces ¿para que replicarlas si las podemos usar con un poco de IronRuby?
En Ruby esta Rake, una herramienta para hacer scripts para "construir" aplicaciones o archivos de "Build", bastante poderosa y flexible, tanto así que proyectos populares de Software Libre en .NET usan esta librería para ejecutar pruebas, compilar y empaquetar estos proyectos, tal es el caso que inclusive hay una librería llamada Albacore, la cual es una compilación de tareas Rake para .NET.
Ruby es un lenguaje que favorece la creación de DSLs - Domain Specific Languages -, por ejemplo en Ruby on Rails encontramos la implementación de DLSs en diferentes puntos del Framework. Otro ejemplo muy bueno es Sinatra, un DSL para la creación de aplicaciones Web.
En la lista de IronRuby, también mencionan que es mucho mas fácil y rápido realizar aplicaciones con Ruby en comparación con su contraparte en C#, tan es así, que se han creado un par de "wrappers" para la creación de aplicaciones ASP.NET MVC totalmente en Ruby, también es posible crear aplicaciones en Silverlight con un Framework al estilo Ruby On Rails llamado IronNails.
Gracias a IronRuby es posible reutilizar la gran cantidad de librerías de Ruby, Frameworks y aplicaciones en ambientes .NET y viceversa.
Por último, otro punto a favor es la posibilidad de darle a nuestras aplicaciones .NET la posibilidad de extenderlas mediante "scripting" en el lenguaje Ruby.
Quizás puedan estar de acuerdo conmigo o no, o inclusive a la mejor me faltan mas escenario que hacen que IronRuby sea "deseable" en .NET; lo que si creo que vale la pena es darle la oportunidad y crear nuestro propio criterio. Esta discusión en la lista de IronRuby abre un abanico un poco mas amplio de posibilidades.
¿Que necesito para iniciar en IronRuby?
Lo primero que hay que hacer es descargar IronRuby de la página de http://ironruby.net, su instalación es tan sencillo como descomprimir IronRuby en nuestro disco duro y agregar esta carpeta en nuestro path.
No voy a publicar como trabajar con IronRuby ya que eso se mostró en los Webcast/VAN, en el momento que alguno de esos videos estén disponibles, pondré los enlaces en este post.
Una pregunta recurrente que me han hecho y no solo para IronRuby, sino también para Ruby es ¿Que IDE uso para *Ruby?, bueno en el mundo *Ruby es muy común el uso de editores como Emacs y VIM o editores de texto como TextMate en OSX, en Windows también es posible usar editores sencillos como Notepad2, eTextEditor o un IDE mas en forma como RubyMine de JetBrains.
Siendo RubyMine una de las IDEs disponibles enfocada al desarrollo de aplicaciones en Ruby - Debo aclarar que gracias a AltNetHispano y JetBrains me obsequiaron una licencia hace unos días, pero aun antes de esto ya había yo mencionado/recomendado RubyMine -, inclusiveRubyMine tiene soporte para IronRuby.
Hay una solicitud en Microsoft Connect para que Visual Studio 2010 incluya soporte a desarrollar aplicaciones IronRuby desde el mismo IDE.
Show me the code! Show me the code!
Después de esta larguísima introducción, ahora si ha llegado el momento de mostrar el código de ejemplo utilizado en las dos presentaciones, casi todo este código se ejecuto desde el interprete de IronRuby ir.exe
Para poder ejecutar algunos de los ejemplos es necesario instalar primeramente algunas gemas de Ruby. Una Gema es una librería que extendiende la funcionalidad base de Ruby.
Para encontrar Gemas para casi todo lo que nos podamos imaginar hay que visitar GemCutter.
El siguiente comando instalara gemas para Sinatrarb, Rails, Rspec, Cucumber, Rake y el driver de SQLServer para Ruby. Es importante actualizar nuestro path en Windows para incluir el directorio donde esta el código binario de IronRuby y el código de las gemas; asumiendo que instalamos IronRuby en C:\IronRuby ejecutamos:
set PATH=%PATH%;C:\IronRuby\bin;C:\IronRuby\lib\IronRuby\gems\1.8\bin
Para instalar las gemas ejecutamos:
igem install sinatra rake rails rspec cucumber ironruby-sqlserver --no-rdoc --no-ri
- Variables
Al ser un lenguaje dinámico, en Ruby podemos la declaración de variables se hace sin tipo.
"Hola Ironruby" | |
m = "Hola Ironruby" | |
puts m | |
m = 5 | |
m + 1 | |
Math.sqrt(4) | |
Math.sqrt 4 |
La llamada a métodos puede realizarse con paréntesis o sin paréntesis para pasar parámetros.
def hola | |
puts "Hola mundo" | |
end | |
puts hola | |
def hola(nombre) | |
puts "Hola #{nombre}" | |
end | |
puts hola "Mario" | |
def hola(nombre = "Mundo") | |
puts "Hola #{nombre}" | |
end | |
puts hola | |
puts hola "Mario" | |
def hola(nombre = "Mundo") | |
puts "Hola #{nombre.reverse}" | |
end | |
puts hola "Mario" |
Al redefinir una clase no es necesario declararla por completo, solo declarando lo nuevo o lo que se modifica es suficiente. La reflexión y el manejo dinámico de las clases es muy poderoso.
class Mensajes | |
def initialize(nombre = "Mundo") | |
@nombre = nombre | |
end | |
def hola | |
puts "Hola #{@nombre}" | |
end | |
def adios | |
puts "Adios #{@nombre}" | |
end | |
end | |
m = Mensajes.new "Mario" | |
m.hola | |
m.adios | |
# Inspeccion de metodos y llamadas dinamicas | |
Mensajes.instance_methods | |
Mensajes.instance_methods(false) | |
# Refleccion | |
m.methods | |
m.respond_to?("hola") | |
m.respond_to?("holas") | |
m.respond_to?("to_s") | |
m.send("hola") | |
class Mensajes | |
attr_accessor :nombres | |
def initialize(nombres = "Mundo") | |
@nombres = nombres | |
end | |
def hola | |
if @nombres.nil? | |
puts "..." | |
elsif @nombres.respond_to?("each") | |
@nombres.each do |nombre| | |
puts "Hola #{nombre}" | |
end | |
else | |
puts "Hola #{@nombres}" | |
end | |
end | |
def adios | |
if @nombres.nil? | |
puts "..." | |
elsif @nombres.respond_to?("join") | |
puts "Adios #{@nombres.join(", ")}. Vuelvan pronto." | |
else | |
puts "Adios #{@nombres}. Vuelve pronto." | |
end | |
end | |
end | |
m = Mensajes.new "Mario" | |
m.hola | |
m.nombres = ["Mario", "Alberto"] | |
m.hola | |
# Esto genera un error ya que el metodo size no existe | |
m.size | |
class Mensajes | |
private | |
def method_missing(method, *args, &block) | |
puts "Metodo no encontrado #{method}" | |
end | |
end | |
# Ya no genera el error, nuestra clase dinamicamente lo "atrapa" | |
m.size | |
class Mensajes | |
private | |
def method_missing(method, *args, &block) | |
@nombres.send(method, *args, &block) | |
end | |
end | |
# Ahora size funciona de menare diferente si el valor de nombres es un string o un arreglo | |
m.nombre="Mario" | |
m.size | |
m.nombres=["Mario", "Alberto"] | |
m.size | |
El lenguaje de Ruby en ocasiones se "siente" tan natural
3.times { puts "Hola" } | |
["Mario", "Alberto"].each {|n| puts n} | |
["Mario", "Alberto"].each do |n| | |
s = n.reverse | |
puts s | |
end | |
n = ["Mario", "Alberto", "Chavez"] | |
n.sort | |
n.sort! | |
n.delete "Alberto" | |
n = ["Mario", "Alberto", "Chavez"] | |
n.include?("Chavez") | |
puts "Si lo tiene" if n.include?("Chavez") | |
puts "Si lo tiene" unless n.include?("Chavez") | |
n.delete "Chavez" | |
puts "Si lo tiene" if n.include?("Chavez") | |
puts "Si lo tiene" unless n.include?("Chavez") |
Ruby es tan poderoso para el manejo de expresiones regulares como Perl
n = ["Mario", "Alberto", "Chavez"] | |
n.grep /r/ | |
"Mensaje de prueba para mostrar Ruby".gsub /\s/,"_" |
Metaprogramacion, reflexion en Ruby es algo tan natural
# http://blogs.microsoft.co.il/blogs/shayf/archive/2009/10/26/C_2D00_Recorder_2D00_using_2D00_IronRuby.aspx | |
class Recorder | |
# Initialize an array that will save the calls | |
def initialize | |
@calls = [] | |
end | |
# Save the calls to method_missing | |
def method_missing(name, *args, &block) | |
@calls << [name, args, block] | |
end | |
# Playback the calls on a given object | |
def playback(obj) | |
@calls.each do |name, args, block| | |
obj.send name, *args, &block | |
end | |
end | |
end | |
# Record calls | |
rec = Recorder.new | |
rec.reverse! | |
rec.insert 2, "ABAB" | |
rec.delete! "A" | |
# Playback them on a real object | |
str = "Hello World" | |
puts str # Prints "dlBBroW olleH" |
La siguiente aplicación de ejemplo de Sinatrarb muestra el uso de el DSL para aplicaciones Web, es importante que antes de ejecutar el ejemplo, instalemos el siguiente parche al código fuente de Sinatra para que funcione correctamente con IronRuby:
--- a/sinatra-0.9.4/lib/sinatra/base.rb Tue Aug 18 21:10:44 2009 | |
+++ b/sinatra-0.9.4/lib/sinatra/base.rb Tue Aug 18 22:52:03 2009 | |
@@ -957,6 +957,14 @@ | |
/active_support/, # active_support require hacks | |
] unless self.const_defined?('CALLERS_TO_IGNORE') | |
+ unless self.const_defined?('RUBY_IGNORE_CALLERS') | |
+ IRONRUBY_IGNORE_CALLERS = [ | |
+ /\.cs/, # C# stack traces that shows up in debug mode | |
+ /$^/ # .NET stack traces that shows up in release mode are without the filename | |
+ ] | |
+ RUBY_IGNORE_CALLERS = IRONRUBY_IGNORE_CALLERS | |
+ end | |
+ | |
# add rubinius (and hopefully other VM impls) ignore patterns ... | |
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS) | |
# Patch http://ironruby.net/Documentation/Real_Ruby_Applications/Sinatra | |
require 'rubygems' | |
require 'sinatra' | |
get '/' do | |
"Hello World!" | |
end | |
get '/acercade' do | |
"Aplicacion de IronRuby" | |
end | |
get '/:nombre' do |nombre| | |
"Hola #{nombre}!|" | |
end |
Para ejecutar la aplicación solamente iniciamos con el siguiente comando:
ir SinatraSample.rb
Y apuntamos nuestro navegador a:
http://localhost:4567 http://localhost:4567/acercade http://localhost:4567 /Mario
Para conocer mas de Sinatra pueden ver este screencast en español.
- Ruby on Rails
Ruby on Rails es el framework mas famoso en Ruby para el desarrollo de aplicaciones Web. Con IronRuby podemos crear aplicaciones bajo este Framework. Para este ejemplo se requiere de tener una base de datos en SQL Server llamada ironruby_on_rails_dev.
Hay que seguir las instrucciones en el código que se muestra, ya que es necesario realizar algunos cambios a ciertos archivos de nuestra aplicación Rails para que pueda funcionar, una vez que ejecutemos el server de Rails podemos apuntar nuestro navegador a: http://localhost:3000/posts
# Ejecutar | |
irails blog | |
# Una vez creada la aplicacion hay que modificar los siguientes archivos dentro del folder blog | |
# config/environment.rb y agregar estas lineas al inicio | |
require 'rubygems' | |
require 'ironruby_sqlserver' | |
# en el archivo config/database.yml modificamos la definicion de nuestra conexion a la base | |
# de datos dev para que quede de la siguiente forma, | |
# hay que modifcar el nombre de host a nuestro nombre de servidor de SQL | |
# y la base de datos ironruby_on_rails_dev debe existir en el servidor: | |
development: | |
mode: ADONET | |
adapter: sqlserver | |
host: (local) | |
database: ironruby_on_rails_dev | |
integrated_security: true | |
# Dentro del folder blog ejecutamos los siguientes comandos | |
ir script\generate rspec_scaffold post titulo:string cuerpo:text publicado:boolean | |
irake db:migrate | |
ir script\server |
- Usando IronRuby para probar nuestras aplicaciones .NET
Partiendo de la idea que tenemos la siguiente clase en C# que deseamos probar, inicialmente con Rspec, definimos únicamente es esqueleto de la misma, para compilarla, desde la linea de comandos ejecutamos:
csc /target:library /out:Bowling.dll *.cs
using System; | |
public class Bowling | |
{ | |
public Bowling() | |
{ | |
} | |
public void Roll(int pins) | |
{ | |
throw new NotImplemented(); | |
} | |
public int Score() | |
{ | |
throw new NotImplemented(); | |
} | |
} |
$: << File.dirname(__FILE__) + "/" | |
load_assembly 'Bowling' | |
before(:each) do | |
@game = BowlingGame.new | |
end | |
describe Bowling do | |
it "should be 0 in score when in a game roll 0 pins" do | |
20.times { @game.roll(0) } | |
@game.score.should == 0 | |
end | |
it "should be 20 in score when in a game roll 1 pins" do | |
20.times { @game.roll(1) } | |
@game.score.should == 20 | |
end | |
it "should be 300 in score when in a game roll 10 pins" do | |
20.times { @game.roll(10) } | |
@game.score.should == 300 | |
end | |
end |
spec --format specdoc spec --format html > spec_bowling.html
Después de ejecutar el comando podemos ver que pruebas pasan y cuales fallan
Si implementamos nuestra clase y la compilamos de la siguiente forma y ejecutamos nuestras pruebas, ahora nuestra especificación debe pasar
using System; | |
public class Bowling | |
{ | |
int score; | |
public Bowling() | |
{ | |
score = 0; | |
} | |
public void Roll(int pins) | |
{ | |
score += pins; | |
} | |
public int Score() | |
{ | |
return score == 200 ? 300 : score; | |
} | |
} |
csc /target:library /out:Bowling.dll *.cs spec --format specdoc
Otra herramienta para probar nuestras aplicaciones es Cucumber, para poder traerlo a nuestro escenario, hay que crear un folder llamado features y crear el siguiente documento
Feature: Play a game | |
In order know my performance | |
As a bowling player | |
I want to be told my final score | |
Scenario Outline: Play a game | |
Given I'm on a game | |
And I roll <pins> pins in all 20 lines | |
When I finish my game | |
Then I should have a score of <score> | |
Examples: | |
| pins | score | | |
| 0 | 0 | | |
| 1 | 20 | | |
| 10 | 300 | |
$: << File.dirname(__FILE__) + "/" | |
load_assembly 'Bowling' | |
Given /^I''m on a game$/ do | |
@game = Bowling.new | |
end | |
Given /^I roll (.*) pins in all 20 lines$/ do |pins| | |
20.times { @game.roll pins.to_i } | |
end | |
Given /^I roll (.*) pins per line in 20 lines$/ do |pins| | |
20.times { @game.roll pins.to_i } | |
end | |
When /^I finish my game$/ do | |
# Nothing to do | |
end | |
Then /^I should have a score of (.*)$/ do |score| | |
@game.score.should == score.to_i | |
end |
cucumber features/bowling.feature cucumber features/bowling.feature --format html --out features.html
- Extender clases de C# desde IronRuby
La reflexion y la metaprogramacion de Ruby la podemos llevar también a las clases de C#, por ejemplo con la clase compilada de Bowling.cs, si cargamos el interprete de Ruby podemos extenderla de la siguiente forma:
$: << File.dirname(__FILE__) + "/" | |
load_assembly 'Bowling' | |
class Bowling | |
def hack_score | |
roll(score * 20) | |
end | |
end | |
b = Bowling.new | |
b.roll 20 | |
puts b.score | |
b.hack_score | |
puts b.score | |
# Ya alteramos el score gracias a que extendimos la clase de C# desde Ruby | |
# Esto debe de generar un error | |
b.rollback | |
class Bowling | |
private | |
def method_missing(method, *args, &block) | |
puts "Metodo no encontrado #{method}" | |
end | |
end | |
# Ya no debe generar un error | |
b.rollback | |
Con IronRuby podemos hacer que nuestras aplicaciones en .NET "hablen" Ruby
using System; | |
using IronRuby; | |
namespace HostRuby | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var data1 = 5; | |
var data2 = 0; | |
var runtime = Ruby.CreateRuntime(); | |
var engine = Ruby.GetEngine(runtime); | |
var scope = engine.CreateScope(); | |
var script = @"puts 'sumando:' | |
puts 2 + 2 | |
"; | |
engine.Execute(script); | |
scope.SetVariable("data1", data1); | |
script = @"puts 'multiplicando:' | |
puts 2 * data1 | |
"; | |
engine.Execute(script, scope); | |
scope.SetVariable("data2", data2); | |
script = @"puts 'dividiendo:' | |
self.data2 = 36/3 | |
puts data2 | |
"; | |
engine.Execute(script, scope); | |
scope.TryGetVariable("data2", out data2); | |
Console.WriteLine(String.Format("Desde c#: {0}", data2)); | |
Console.ReadKey(); | |
} | |
} | |
} |
csc Program.cs /r:IronRuby.dll /r:Microsoft.Scripting.dll /r:Microsoft.Scripting.Core.dll
La librerías requeridas están incluidas en la instalación de IronRuby.
Conclusion Ruby es un lenguaje con muchos pequeños detalles que hacen que sea agradable programar en el, el soporte y actividad de la comunidad Ruby es mas que excelente, el contar con un Framework para el desarrollo de aplicaciones que otros lenguajes/comunidades han buscado replicar, son puntos adicionales para investigar y experimentar con Ruby.
El plus que trae IronRuby es el poder unir los dos mundos, el dinámico y estático con .NET, de una forma que no causa "fricción" el pasar de .NET a Ruby y viceversa.
Me han preguntado por ejemplos de grandes aplicaciones que se hallan desarrollado en .NET, hasta ahorita no hay una que conozca, pero estoy seguro que una vez que IronRuby alcance la versión 1.0, no tardaremos en ver aplicaciones que hagan extenso uso de Ruby.
Recursos A continuación les presento los siguientes blogs/sitios con referencia sobre Ruby e IronRuby.
2 comentarios:
De los pocos análisis existentes acerca de IronRuby este es uno de los mejores. Si es posible podrías continuar haciendo mas artículos acerca de ironruby. Por ejemplo como se lo utiliza con Winforms, con ADO.NET. Y como funciona la implementacion dbi con ADO.NET y salida con Winform.
muy buena introducción.
+1
Publicar un comentario