Stop_deletion_if_has_children: metaprogramación en Rails
Posted by joahking Mon, 15 Oct 2007 05:42:46 GMT
¿Has intentado definir en una asociación que se detenga el borrado del objeto si existen otros que lo refieran? Por ejemplo: Padre, tiene Hijos y Nietos, y no quieres que sea destruido si tiene algún hijo o nieto. La asociación has_many de Rails provee tres opciones para mantener la integridad de datos con :dependent => {:destroy | :delete_all | nullify}. Pero pasa por alto este caso tan común.
Por suerte Rails brinda posibilidades aun en el caso de que algo falte, y esta es una buena oportunidad para un plugin y usar un poco de metaprogramación. El plugin stop_deletion_if_has_children resuelve esto de manera sencilla:
La declaración de la restricción en el modelo es así:
# padre.rb
has many :hijos
has_many :nietos
stop_deletion_if_has_children :hijo, :nieto
(Nótese que el singular es importante en la declaración de cada parámetro pasado al plugin, según la implementación dada.)
# init.rb
require 'stop_deletion_if_has_children'
ActiveRecord::Base.send(:extend,Qvitta::StopDeletionIfHasChildren)
El plugin recorrerá la lista de objetos enlazados pasados como parámetros y chequeará que no existan records en el momento de destruir el objeto.
Esto es lo que hace:
# stop_deletion_if_has_children.rb
module Qvitta #:nodoc:
module StopDeletionIfHasChildren #:nodoc:
def stop_deletion_if_has_children(*children)
define_method "children_check" do
ret = true
for child in children do
if self.send("#{child}".to_s.pluralize).length > 0
self.errors.add_to_base "Error: #{self.class} contiene #{child.to_s.camelize.pluralize}"
ret = false
end
end
return ret
end
before_destroy :children_check
end
end
end
Las claves en el código anterior son:
*children: el asterisco le dice a Rails que el parámetro del método es un arreglo de valores.
before_destroy :children_check: que interpone el método children_check justo antes de la destrucción del objeto, y permite la destrucciónreturn trueo la detienereturn false(se devuelven todos los mensajes de errores posibles en una sola pasada).
define_method "children_check" do: define al vuelo el método children_check.
Si como yo, tienes modelos con nombres en español la linea corta:
if self.send("#{child}".to_s.pluralize).length > 0
puede fallar pues pluralize lo hará en ingles. En ese caso necesitas hacer el chequeo desde los descendientes hacia el padre con esta más larga:
if "#{child}".to_s.camelize.constantize.send("find_all_by_#{self.class.to_s.downcase}_id",self.id).length > 0
Esta linea más larga nos da el pie didáctico para más de metaprogramación:
- camelize: para convertir la cadena ‘hijo’ en ‘Hijo’.
- contantize: que convierte la cadena ‘Hijo’ en la clase Hijo.
- send: que invoca el método pasado como parámetro en la clase obtenida con el tratamiento anterior (en nuestro caso: Hijo.find_all_by_padre_id).
Pues eso es. A ver si con un poco de suerte (y tiempo) ponemos el repositorio de plugins de Qvitta accesible esta semana, y asi se podrá descargar.





