RESTful - Parent Load, com método preload
Friday, August 29th, 2008Para 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 …




