HashCode

An organizer of symbols

Archive for August, 2008

RESTful - Parent Load, com método preload

Friday, August 29th, 2008

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}, {:only => [: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 …

RGhost on Mac OSX

Monday, August 25th, 2008

Abaixo instruções de instalação do RGhost no MacOS

Requirimentos:
- Ruby
- RubyGems
- Xcode
- Mac Port

Primeiro instale o Ghostscript via macport

sudo port install ghostscript

Agora RGhost

sudo gem install rghost

Agora precisamos saber onde o executável do Ghostscript foi instalado para isso execute

hashcode:tmp shairon$ type gs
gs is /opt/local/bin/gs

Observe que na minha instalação o path ficou /opt/local/bin/gs.
Vamos configurar e testar em um arquivo .rb ou via IRB

require 'rubygems'
require 'rghost'

#use o path do seu gs aqui
RGhost::Config::GS[:path]="/opt/local/bin/gs"
RGhost::Config.is_ok?.render :pdf, :filename => "/tmp/is_ok.pdf"

Uma página será gerada como essa
You environment is done.

Em breve no manual em English.