Category: Rails

Posting JSON data to Rails Application

I’ve been playing around with many RESTful requests with Riak, ElasticSearch, MongoDB and Rails. In my scripts I used to use curb, cURL or even jQuery.

As you may know Rails receives data inside of an action as a hash, aka params, let’s give you some background before talk about post as JSON format.

Basic scenario

Given you have a model called Product you may want to create a new product with some fields, you must format your fields to fit as a Ruby Hash(or using Rails form helper), normally the format is model[field], in a HTML form would be

<form action="index" method="POST">
  <input type="text" name="product[name]" value="Responsive Web Design with HTML5 and CSS3"/>
  <input type="text" name="product[sku]" value="1849693188"/>
  <input type="text" name="product[publisher]" value="Packt Publishing (April 10, 2012)"/>
</form>

When you post data without specify enctype attribute for form tag, the default content type is application/x-www-form-urlencoded, behind the scene content type goes as a HTTP header and in HTTP body fields and values will be escaped and concatenated by &

product%5Bname%5D%3DResponsive+Web+Design+with+HTML5+and+CSS3&product%5Bsku%5D%3D1849693188&product%5Bpublisher%5D%3DPackt+Publishing+%28April+10%2C+2012%29

Using curl to post this

curl "localhost:3000/mytest" -d "product[name]=Responsive Web Design with HTML5 and CSS3&product[sku]=1849693188&product[publisher]=Packt Publishing (April 10, 2012)"

The raw HTTP looks like this

POST /mytest HTTP/1.1
User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5
Host: localhost:3000
Accept: */*
Content-Length: 132
Content-Type: application/x-www-form-urlencoded

product%5Bname%5D%3DResponsive+Web+Design+with+HTML5+and+CSS3&product%5Bsku%5D%3D1849693188&product%5Bpublisher%5D%3DPackt+Publishing+%28April+10%2C+2012%29

In Rails side you can get data from params:

{
  "product"=> {
    "publisher"=>"Packt Publishing (April 10, 2012)",
      "name"=>"Responsive Web Design with HTML5 and CSS3",
      "sku"=>"1849693188"
  }
}

Posting as JSON

Only two things are needed to post data as JSON, first is set Content-Type header to application/json (or enctype for forms) and of course some data in JSON format, For example same data from product model:

curl -XPOST -H "Content-Type: application/json" "localhost:3000/mytest" -d '
{
  "product": {
    "publisher": "Packt Publishing (April 10, 2012)",
    "name": "Responsive Web Design with HTML5 and CSS3",
    "sku": "1849693188"
  }
}'

I think this is very useful when you have a tool to serialize json from a model and also for purpose of the testing, it’s more readable, you avoid unnecessary conversion.

You can choose what’s easier for you.

Netbeans 7.1 + Ruby, JRuby and Ruby on Rails installation issues

If you are experiencing problems to install Netbeans 7.1 with Ruby, JRuby and Ruby on Rails plugin, there’s a workaround from Tom Enebo.

The installation should be by zip file for now. Read the post above to get more details.

Hooks em Ruby

Ruby é uma linguagem dinâmica e utiliza algumas caracteristicas interessantes para manipular objetos e classes no seu ciclo de vida em run-time, como por exemplo usar singleton methods em um objeto, realizar mixin, adicionar métodos em uma classe e outras coisas legais. Para capturar esse eventos que alteram o comportamento de classes, módulos e objetos, Ruby provê alguns Hooks.

O que seriam Hook propriamente dito(pergunta clássica no curso de metaprogramação)? Hooks são métodos com um nome específico para capturar um mudança no objeto. Esses hooks podem ser definidos por uma API ou mesmo pela a especificação da linguagem. Um exemplo de API que utilize hooks são os callbacks do ActiveRecord, como after_save, before_save, etc, esses métodos são invocados automaticamente após a chamada “save” do model como descrito na especificação.

Hooks da Linguagem Ruby

O intuito deste poste é falar sobre alguns hooks da linguagem Ruby, vamos iniciar capturando o evento de herança de uma classe, isso pode ser muito útil se sua super-classe precisa conhecer a subclass. Para esse hook funcionar temos que definir o método self.inherited na superclass. Exemplo

class A
  def self.inherited(subclass)
    puts "Eu #{self} sendo herdada por #{subclass}"
  end
end

class B < A
end

#=> "Eu A sendo herdada por B"

O conteúdo do método inherited é executado pelo interpretador no momento em que a sub classe é carregada na vm. Lembra do final public class A{} em java? Para quem nunca usou java o final public class é forma de não permitir que uma classe seja herdada por outra. Em ruby você pode simular esse comportamento lançando uma exceção dentro do self.inherited, veja:

class A
  def self.inherited(subclass)
    raise "A classe #{self} nao pode ser herdada!"
  end
end

class B < A
end 

#E=> RuntimeError: A classe A nao pode ser herdada!
 

self.included

O self.inherited é usado apenas para classes, se você precisar detectar mix-in entre módulos e classes, defina o método self.included dentro do módulo que será mixado. O exemplo abaixo demonstra como capturar o evento de mix-in(include):

module M
  def self.included(from)
    puts "#{self} foi mixado por #{from} "
  end
end

class A
  include M
end

Particularmente acho o self.included muito útil, com ele podemos adicionar funcionalidades durante o mix-in. Por exemplo, vamos criar um plugin chamado Printable, na verdade é só um módulo com um pouco de metaprogramação para detectar os accessor(setters e getters) e definir um novo método com o prefixo print_ para imprimir o retorno do método original, seria basicamente assim.

class Person
  attr_accessor :name, :code
end

p = Person.new
p.name ="Foobar"
p.print_name #ainda não existe

Vamos detectar os accessor, se um método tem dois métodos com o mesmo nome mas sendo um terminado com o operador de atribuição(=) então é um acessor.

Antes vamos criar a funcionalidade apenas para classe Person

class Person
  attr_accessor :name, :code
end

#pegando os métodos de instância
methods = Person.instance_methods false
#=> ["code", "name=", "code=", "name"]

#detectando quais deles são accessors
 acessors=methods.select{|m| methods.include? "#{m}=" }
#=> ["code", "name"]

#definindo os métodos na classe
acessors.each do |acessor|
  Person.send :define_method, "print_#{acessor}" do
    puts "Printing #{send(acessor)}..."
  end
end

#Testando
p = Person.new
p.name = "FFUUUU"
p.code = 44444
p.print_name  #=> Printing FFUUUU ...
p.print_code  #=> Printing 44444 ...

Perfeito! Agora vamos criar um módulo e utilizar o hook self.included, com isso, quando uma classe necessitar dessa funcionalidade é só fazer o include de Printable, veja

module Printable
  def self.included(klazz)
    methods = klazz.instance_methods false
    acessors=methods.select{|m| methods.include? "#{m}=" }
    acessors.each do |acessor|
      klazz.send :define_method, "print_#{acessor}" do
      puts "Printing #{send(acessor)}..."
    end
  end
end

Fazendo mix-in em Person e em OtherClass

class Person
  attr_accessor :name, :code
    include Printable
  end

class OtherClass
  attr_accessor :a, :b
  include Printable
end

Testando

p = Person.new
p.name = "FOO"
p.print_name

obj = OtherClass.new
obj.a = "Hey Everybody!"
obj.b = "It's working"
obj.print_a
obj.print_b

Output

Printing FOO...
Printing Hey Everybody!...
Printing It's working...

self.method_added

Se você leu esse post até aqui provavelmente você conhece o method_missing, aquele que captura a nome do método e os parâmetros caso o objeto não responda a esse método. Pois é, não falaremos do method_missing nesse post, vamos falar do primo dele, o self.method_addded. O self.method_added é um hook que captura o evento quando um método é adicionado ao objeto ou classe. Exemplo.

class Foo
  def self.method_added(method)
    puts "Adicionado o metodo #{method} na classe #{self}"
  end

  def testing
    puts "testing"
  end
end
 #=> "Adicionado o metodo testing na classe Foo"

Veja que na própria classe o evento é disparado. Em uma herança, o mesmo comportamento

class Foo
  def self.method_added(method)
    puts "Adicionado o metodo #{method} na classe #{self}"
  end
end

class Bar < Foo
  def other_method
    puts "say hello"
  end
end

#=> Adicionado o metodo other_method na classe Bar

Ou ainda, você pode capturar singleton methods com o mesmo hook.

class Foo
  def self.method_added(method)
    puts "Adicionado o metodo #{method} na classe #{self}"
  end
end

foo = Foo.new

def foo.new_method
  puts "hey I'm adding ..."
end

Object.const_missing

Sabemos que o method_missing captura o evento quando o método não existe, podemos ter uma funcionalidade semelhante para constantes, com o Object.const_missing. Sempre que uma constante não for encontrada na vm, o const_missing recebe o nome da constante. Esse método deve ser definido em Object, dado que constantes o escopo é global. Veja

UmaConstante #E=> NameError: uninitialized constant UmaConstante

def Object.const_missing(const)
  puts "Hey nao encontrei #{const} mas nao vou lançar error"
end 

UmaConstante
#=> Hey nao encontrei UmaConstante mas nao vou lanar error

Embora isso seja uma coisa legal, se você apenas quer capturar o evento, uma boa prática é fazer a sua lógica usando a constante que está faltando e depois usar o super para lançar a exceção via Kernel. Portando algo como

UmaConstante #E=> NameError: uninitialized constant UmaConstante

def Object.const_missing(const)
  puts "Hey nao encontrei #{const} "
  super
end 

UmaConstante

#=> Hey nao encontrei UmaConstante
#=> NameError: uninitialized constant UmaConstante
#=>	 from (irb):119:in `const_missing'
#=>	 from (irb):122
#=>	 from :0

Se seu é convencionado utilizando o nome do arquivo em snake case e o nome da classe em camel, você pode fazer um plugin para carregar seus arquivos automaticamente caso a classe não esteja visível no trecho do seu código. Exemplo, no mesmo diretório 3 arquivos

Arquivo person.rb

class Person
  attr_accessor :name, :state
end

Arquivo state.rb

class State
  attr_accessor :code
end

Arquivo principal main.rb

p = Person.new
p.state = State.new

Rode com

ruby main.rb

De cara o erro

#E=> main.rb:1: uninitialized constant Person (NameError)

Pois não colocamos o requires necessários para cada arquivo.
Vamos fazer um algoritmo para carregar esses arquivos automaticamente usando Kernel.autoload.
Então no main.rb fica com o conteúdo:

def Object.const_missing(const)
  #convertendo CamelCase to snake_case
  filename = const.to_s.gsub(/([A-Z])/, ' \1').split(' ').join('_').downcase
  #Associando a constante ao nome do arquivo
  autoload const, filename
  #binding a nova constante
  Object.const_get const
end  

p = Person.new
p.state = State.new

Se você rodar novamente o main.rb vai funcionar.

Conclusão

No dia-a-dia e em projetos pequenos não é comum capturar eventos no nível de interpretação mas se você esta depurando um sistema ou mesmo desenvolvendo uma API, Hooks podem ser essenciais.

Ops! RailsTree, de novo?

Puts! Foi mal Akita e Carlos esse projeto é de mil novecentos e Titanic mas obrigado por mencionar. Eu repostei ele sem querer.

O post real está aqui

RESTful – Parent Load, com método preload

Para quem usa RESTful e Nested Resources existe uma ação de carregamento do objeto pai na maioria dos controllers filho.

Se você não entendeu nada do que eu falei, dê uma lida em
RESTful Rails Development, by Ralf Wirdemann & Thomas Baustert

Por exemplo tenho dois models um Person e o outro Personality sendo que Person tem uma Personality e Personality pertence a Person.
Ficando assim o relacionamento no AR

#app/models/person.rb
class Person < ActiveRecord::Base
  has_one :personality
end
#app/models/personality.rb
class Personality < ActiveRecord::Base
  belongs_to :person
end

e as rotas desta forma

ActionController::Routing::Routes.draw do |map|
  map.resources :personalities, :belongs_to => :people, :singular => "personality"
  map.resources :people, :has_one => :personality
end

Ok até agora. “Feitisso” podemos criar um registro Person via /people/new e depois criar um registro Personality via /people/1/personality/new
Quando for criado o novo registro Personality o id do pai(Person) vai como parametro

params[:person_id]

Então você já deve ter visto muito a estrategia de carregamento do objeto pai em uma variável de instância via filtro, em um construção do tipo abaixo:

class PersonalitiesController < ApplicationController
  before_filter :get_person

  private
  def get_person
    @person = Person.find(params[:person_id])
  end

end

Portando antes de executar a action o pai é carregado via filtro.

Para evitar está estrategia que estava repetindo em quase todos meus controllers crie o método preload.
Com preload posso ter uma contrução mais clara e com o mesmo efeito. Então agora posso fazer assim

class PersonalitiesController < ApplicationController
  preload :person
end

Podemos também utilizar include, order, limit, etc

class PersonalitiesController < ApplicationController
  preload :person, :include => :personality
end

Ou até especificar em quais actions o preload será utilizado

class PersonalitiesController < ApplicationController
  preload :person, {:include => :personality}, { :o nly => [:create]}
end

Olha a origem da mágica

class ApplicationController < ActionController::Base
  def self.preload_object(name,options,filter_options={} )
    before_filter(filter_options) do |controller|
      controller.instance_variable_set("@#{name}", name.to_s.camelize.constantize.find(controller.params["#{name}_id"], options))
    end
  end
end

Apenas um método de classe dentro do ApplicationController e um pouco de meta programação.
Agora vou tentar explicar o médodo preload.

Começando por

  name.to_s.camelize.constantize

o name vem como Symbol no nosso caso :person, então temos

name.to_s  		#=> "person"

O método camelize analiza o conteúdo da String e adapta ao padrão de nome de classes utilizadas em Ruby, exemplo “foo_bar” camelizado passa a ser “FooBar”.

name.to_s.camelize #=> "Person"

Invocando a constante

name.to_s.camelize.constantize #=> Person

ou poderia utilizar

Object.const_get name.to_s.camelize

Já que tenho a classe Person utilizo o find passando o padrão pai_id.

O bloco do before_filter passa um yield do controller, precisamos do controller neste escopo pois estamos em um contexto estático, por conseguinte apenas params[:pai_id] não é visível então utilizei controller.params[:pai_id]

E por último criar a instância @pai apontando para o retorno do Person.find, vou dar um zoom no código :)

class ApplicationController < ActionController::Base
  def self.preload_object(name,options,filter_options={} )
    before_filter(filter_options) do |controller|
		  parent_id = controller.params["#{name}_id"]
			find_return = name.to_s.camelize.constantize.find(parent_id)
			controller.instance_variable_set("@#{name}", find_return)
    end
  end
end

Ou você prefere a versão One-Line-Brain-Fucker?

class ApplicationController < ActionController::Base

  def self.preload_object(name,options,filter_options={} )
    before_filter(filter_options) { |controller| controller.instance_variable_set("@#{name}", name.to_s.camelize.constantize.find(controller.params["#{name}_id"], options)) }
  end

end

Mais DRY, mais DRY …

Executive IT Meeting em Goiânia


Gostaria de Convidar a todos a participarem do Executive IT Meeting l em Goiânia, 29..31 de outubro de 2007.
Vou representar a comunidade(espero ser capaz) :)
O meu amigo Alexandro Lima fez uma Charge para o evento.

palestra_rubi_contec.jpg

Nossa participação será no dia 31 na parte da tarde:

Palestra: Linguagem Ruby – Minerando e Lapidando.
Filosofia, idiossincrasia e pragmática da linguagem.

Tutorial: Ruby on Rails – Overview sobre o Framework do Ano.
Desenvolvendo um aplicação MVC em Rails.

Espero vcs lá hein! :)

Rails Tree Release 0.8.5

Nova versão do plugin railstree com criação via Hash e bugfix RSS.


  tabelas1=%w(starttelephony startvoip customers  gateways)
  tabelas2=%w(teste1 teste2 ) 

  d1=Node.new(:label => "radius", :icon => "/images/data.png",  :o pen => true)
  d2=Node.new(:label => "teste", :icon => "/images/data.png",  :o pen => false)

  tabelas1.each do |t|
    no=Node.new(:label => t, :icon => "/images/table_sql.png")
    d1 << no
  end

  tabelas1.each do |t|
    no=Node.new(:label => t, :icon => "/images/table_sql.png")
    d1 << no
  end

  tabelas2.each do |t|
    no=Node.new(:label => t, :url => 'link aqui', :icon => "/images/table_sql.png")
    d2 << no
  end

  tr = Tree.new(:label => "Databases[solaris]")
  tr << d1
  tr << d2
Version Download Howto Repository
0.8.5 rubyforge.png

 

 

RailsTree 0.8.5

Estava procurando uma tree para utilizar em rails e não achei, aí migrei a tree que tinhamos desenvolvido baseado no javascript do Geir Landr[2003] do formj para um plugin rails.
Exemplo de árvore dos dois estilos por padrão.
Gnome Style(default)

Explorer Style

tree2.png

Mac X/Acqua Style
Me mande que coloco aqui :-)
Anatomia
Após a instalação o railstree adiciona os seguintes arquivos no RAILS_ROOT

/lib/node.rb                 # o nó
/lib/tree.rb                 # a tree que também é um nó(raíz)
/public/images/tree/*.gif    # imagens(pasta,documento,etc)
/public/javascripts/tree.js  # O script da árvore
/public/stylesheets/tree.css # humm! o estilo.

Talvez o estilo da sua aplicações interfira diretamente no esboço da árvore.
Gnome Style é o default, para usar o Explorer Style descompacte img_tree_explorer.tar.gz em /public/images/tree
Instalação
Via svn
Adicione o repositório

script/plugin source svn://rubyforge.org/var/svn/railstree/vendor/plugins

Depois instale com

script/plugin install tree

Via local
Descompacte o arquivo railstree0.8.5.zip no diretório vendor/plugins do seu Rails. Entre no diretório tree e execute

ruby install.rb

O railstree funciona em conjunto com os scripts de ajax e javascripts default do rails, portando adicione na sua view o no seu layout os scripts

  &lt;%= javascript_include_tag 'prototype' %&gt;
  &lt;%= javascript_include_tag 'effects' %&gt;
  &lt;%= javascript_include_tag 'dragdrop' %&gt;
  &lt;%= javascript_include_tag 'controls' %&gt;

e depois o script e o css do railstree

  &lt;%= stylesheet_link_tag 'tree' %&gt;
  &lt;%= javascript_include_tag 'tree' %&gt;

ficando assim

  &lt;%= stylesheet_link_tag 'tree' %&gt;
  &lt;%= javascript_include_tag 'tree' %&gt;
  &lt;%= javascript_include_tag 'prototype' %&gt;
  &lt;%= javascript_include_tag 'effects' %&gt;
  &lt;%= javascript_include_tag 'dragdrop' %&gt;
  &lt;%= javascript_include_tag 'controls' %&gt;

Já tá pronto pra funcionar
Usando
A idéia é básica. Um nó pode ter filhos ou não. Para criar sua estrutura siga sempre a lógica de colocar um nó dentro do outro e um objeto da classe Tree para impressão dos dados na tela.
Exemplo:

a=Node.new(:label => 'A', :url => '/letras/a') # nome , link
b=Node.new(:label => 'B', :url => '/letras/b')
c=Node.new(:label => 'C', :url => '/letras/c')
d=Node.new(:label => 'D', :url => '/letras/d')
e=Node.new(:label => 'E', :url => '/letras/e')
f=Node.new(:label => 'F', :url => '/letras/f') # nó em filho
a &lt;&lt; b  #adicionando b em a
c &lt;&lt; a  #adicionando a em c
d &lt;&lt; e  #adicionando e em d

tree=Tree.new(:label => 'Root',:url=> '/letras/todas')
tree &lt;&lt; c  #Taca 'c' na raiz da tree
tree &lt;&lt; d  #Taca 'd' na raiz da tree
tree &lt;&lt; f  #Taca 'f'(sem filho) na raiz da tree

Podemos passar blocos para os nós. Exemplo da mesma estrutura acima, usando blocos


tree=Tree.new(:label => "Root",:url =>"/letras/todas") do |t|
    t << Node.new(:label => "C",:url =>"/letras/c") do |n|
      n << Node.new(:label => "A",:url =>"/letras/a") {|a| a << Node.new(:label => "B",:url =>"/letras/b")}
    end
 end

tree << Node.new(:label => "D",:url =>"/letras/d") {|d| d << Node.new(:label => "E",:url =>"/letras/E") }
tree << Node.new(:label => "F",:url =>"/letras/f")

E que Deus continue inspirando Yukihiro Matsumoto
Vc escolhe!
Tem mais uma, todo o node tem nodes e aceita um bloco(opcional)

a=Node.new("A","/letras/a")
 a.nodes do |n|
   #faça algo com os filhos
end

Personalizando…
Você poderá mudar os ícones de um nó fechado ou aberto, fazer referênciar pelo atributo href de uma âncora ou onclick para acessar uma função em javascript, falar para o nó aparecer aberto ou fechado, etc.

Mudando os ícones


a=Node.new(:label => "A",:url => "/letras/a")
a.icon='/images/db.png'
a.icon_open='/images/outroicone.png'

Exemplo, esse código


  tabelas1=%w(starttelephony startvoip customers  gateways)
  tabelas2=%w(teste1 teste2 ) 

  d1=Node.new(:label => "radius", :icon => "/images/data.png",  :o pen => true)
  d2=Node.new(:label => "teste", :icon => "/images/data.png",  :o pen => false)

  tabelas1.each do |t|
    no=Node.new(:label => t, :icon => "/images/table_sql.png")
    d1 << no
  end

  tabelas1.each do |t|
    no=Node.new(:label => t, :icon => "/images/table_sql.png")
    d1 << no
  end

  tabelas2.each do |t|
    no=Node.new(:label => t, :url => 'link aqui', :icon => "/images/table_sql.png")
    d2 << no
  end

  tr = Tree.new(:label => "Databases[solaris]")
  tr << d1
  tr << d2

Gera isto:
tree3.png

Falando para um node aparecer aberto ou fechado(se tiver filhos!)


# :o pen => true ou false, esse valor é passado para o javascript
a=Node.new(:label => "A",:url => "/letras/a",  :o pen => true)
#ou
a.open=true

Link e eventos

Temos o atributo variável event_name que pode ser qualquer coisa que vc quer mais trabalha em conjunto com o atributo href, não obstante para criar um link teremos o seguinte:


Node.new(:label => "A",:url => "/letras/a")

é a mesma coisa de fazer isso


Node.new(:label => "A",:href => '/letra/a', :event_name => :href)

isso vai criar uma tag assim para o nó
<a href=’/letras/a’ > A </a>

Hummm!
Então para criar um nó que chama um javascript pode ser assim


Node.new(:label => "A", :url => "alert('nossa que doído!')", :event_name =>  :o nclick )

#ou

a=Node.new
a.label="A"
a.url="alert('nossa que doído!')"
a.event_name=  :o nclick

Link for Rails
Tem um esquema legal, que é usar a mesmo mecanismo de link do Rails o link_to e o link_to_remote.
É necessário a adição da opção :base referenciando uma instância de qualquer controller para que o mecanismo interno do método url_for(rails) funcione adequadamente.

link_to

Node.new  :label => "Criar usuário",
          :link_to => {
            :base =&gt; @controller,
            :controller =&gt; "usuarios",
            :action =&gt; "adicionar" ,
            :id =&gt; 1
          }

assumindo que @controller é uma instância do controller, se tiver dentro de um controller troque @controller por self ficando :base => self.
Para um roteamento padrão do Rails isso é a mesmo coisa de


n=Node.new(:label => "Criar usuário",url => "/usuarios/adicionar/1")

Pode também fazer requisições em ajax utilizando a criação padrão do link_to_remote mais :base.

link_to_remote

Node.new  :label => "Adicionar usuário ",
          :link_to_remote => {
            :base =&gt; @controller,
            :controller =&gt; "usuarios",
            :update =&gt; "id_do_div",
            :url =&gt;{ :action =&gt; 'adicionar', :c=&gt; 'variavel C', :a =&gt; 'variavel A'}
          }
 }

Montando uma árvore de diretório
Vou demonstrar aqui gerando o path do RAILS_ROOT

 Tree.for(RAILS_ROOT)

Pronto. Isso gera
tree4.png

Extra Sintaxe

t=Tree.new(:label => 'Raiz')
t.child(:label => 'Pai').child(:label => 'filho').child(:label => 'espirito santo').child(:label => 'amem')

amem.png
ou para um filho


t.child(:label => 'Pai'){ |p| p.child(:label => 'filho') }
#ou
t.child Node.new(:label => 'Pai').child Node.new(:label => 'filho')

Para vários irmão


t.child Node.new(:label => 'a'),Node.new(:label => 'b'), Node.new(:label => 'c')

ou

t.mknodes(5) do |n,s|
      n.label="Link #{s}"
      n.url="/link/#{s}"
end

Obs.: Sugestão do Ronie Uliana

Obs2.: Criação dos nodes passando Hash foi sugestão do Marcelo Júnior

rghost – Plugin ou Gem?

Você escolhe!!
rghost_plugin.png
Pra quem pediu, disponibilizei o rghost como plugin no repositório do rghost no RubyForge só descompactar no RAILS_ROOT/lib e reiniciar a aplicação.

Impressão direta via TCP/IP com RubyGhost

Geralmente as impressoras de médio e grande porte disponibilizam uma porta TCP conhecida como ‘porta bruta’ de número 9100. Demonstrarei aqui uma forma de imprimir diretamente neste tipo de printer, para o nosso exemplo usaremos o driver postscript genérico da Adobe(PS-Adobe-3.0).
Models Contas e Clientes

class Contas < ActiveRecord::Base
  belongs_to :clientes
end
class Clientes < ActiveRecord::Base
  has_one :contas
end

Configurando as colunas

require 'rghost'
clientes=Clientes.find :all, :include => :contas, :limit => 10000

grid=DataGrid::RailsGrid.new :width => 4 , :align => :center
grid.col :codigo, :title => "Código do Cliente", :width => 5
grid.col :nome, :title => "Nome"
grid.col :created_on, :title => "Data de Cadastro ", :format => :eurodate
grid.col lambda { contas.login }, :title => "Login"
grid.data(clientes)

Criando o documento com dados do ActiveRecord

doc=Document.new :paper => :A4, :landscape => true, :duplex => true
doc.before_page_create do
  set Image.for("/tmp/fatura.eps")
end
doc.set grid
doc.done

Enviando o documento

printer = TCPSocket.open('192.168.1.70', 9100)
printer.write doc.render_stream(:ps)

Deve ter saído lá na impressora… :)

Para mais informações no RGhost – RubyForge . Documentação em português no RubyForge