format internet:

…please wait (42% completed)…

Archive for December, 2006

includes anidados / encadenados en rails (eager loading)

Posted by javier ramirez on December 10, 2006

Ya sabemos que mediante el uso de Associations podemos conseguir eliminar el problema de las n+1 selects cuando queremos cargar datos de una tabla y su tabla asociada. Esto es lo que se conoce como eager loading.

Siguiendo la documentación, está claro que se puede hacer include de n tablas de golpe haciendo :include =>[tabla1,tabla2], y obtendremos datos tanto de nuestra tabla original como de las tablas tabla1 y tabla2. Un punto a tener en cuenta es que el tipo de joins que se hacen son LEFT OUTER, por lo que obtendremos todos los registros de la tabla original y solamente los que tengan datos de las tablas relacionadas.

Hasta aquí, todo perfecto cuando quieres hacer una select con tablas que están relacionadas con tu modelo directamente; pero si quieres hacer de una tacada una query que incluya las tablas relacionadas y a su vez las tablas relacionadas de éstas, no parece que sea posible. No al menos mirando solamente la documentación.

Sin embargo, una de las cosas interesantes del open source es que le podemos echar un vistazo al código fuente y ver qué es lo que hace exactamente, teniendo de esta forma la mejor documentación posible.

Tras una investigación alrededor de ActiveRecord(*ver más abajo), encontramos que, cuando se definen asociaciones, a la hora de montar la query se diferencia entre tres casos diferentes: si la asociación es un Symbol/String, si es un Array o si es una Hash. Aquí es donde está la gracia. En la documentación sólo nos explican el caso de Symbol/String y de Array, pero… y si le paso una Hash?.

Si le paso una Hash, consigo justamente lo que quería, poder incluir en la query las tablas relacionadas y las relacionadas con ellas. La sintáxis ,no tan obvia, para esto es la siguiente:

:include=>[{:tabla1=>[:tabla1_A,:tabla1_B]}]

Esto me carga las relaciones de mi modelo con tabla1, y a su vez las de tabla1 con tabla1_A y con tabla1_B, ahorrándonos bastante carga contra la base de datos.

* A continuación muestro el extracto de la clase JoinDependency, definida en Associations.rb donde se lleva a cabo la magia de las asociaciones

def construct(parent, associations, joins, row)
  case associations
  when Symbol, String
    while (join = joins.shift).reflection.name.to_s != associations.to_s
    raise ConfigurationError, “Not Enough Associations” if joins.empty?
    end
    construct_association(parent, join, row)
  when Array
    associations.each do |association|
    construct(parent, association, joins, row)
    end
  when Hash
    associations.keys.sort{|a,b|a.to_sb.to_s}.each do |name|
    association = construct_association(parent, joins.shift, row)
    construct(association, associations[name], joins, row) if association
    end
  else
    raise ConfigurationError, associations.inspect
  end
end



def construct_association(record, join, row)
  case join.reflection.macro
  when :has_many, :has_and_belongs_to_many
    collection = record.send(join.reflection.name)
    collection.loaded
    return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
    association = join.instantiate(row)
    collection.target.push(association) unless collection.target.include?(association)
  when :has_one, :belongs_to
    return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
    association = join.instantiate(row)
    record.send(“set_#{join.reflection.name}_target”, association)
  else
    raise ConfigurationError, “unknown macro: #{join.reflection.macro}”
  end
  return association
end

searchwords: include, nested include, chained include, eager loading, associations, JoinDependency

Posted in development, javier ramirez, ruby on rails | Leave a Comment »

Parsear XML o HTML mal formado

Posted by javier ramirez on December 6, 2006

Si necesitais parsear XML o HTML mal formado hay unas cuantas opciones interesantes en ruby / rails. De las que he visto, me ha parecido que htree da más que suficiente y tira rápido.

Yo la acabo de utilizar para leer un html de una web, que me lo convierta a XML cerrando los tags abiertos estilo <br>, que me cierre los tags que se han olvidado como algún <span> y algún <tr> y luego poder acceder al html como si fuera xml directamente, pudiendo usar XPATH para recorrerlo.

Ejemplo de la vida misma

require ‘open-uri’
require ‘htree’

#Primero me conecto a una web y leo su contenido
str_xml = ”
open(“https://formatinternet.wordpress.com&#8221;, ‘User-Agent’=>”Ruby/#{RUBY_VERSION}”)
  { |file| str_xml = file.read }


#ahora parseo con HTree y me aseguro que me da un XHTML bien formado,
dejando el documento en un documento rexml
parsed_xml = HTree.parse(str_xml).to_rexml


#y aquí ya puedo, por ejemplo, obtener un array de todos los divs en
el html
a_divs = parsed_xml.elements.to_a(‘//div’)

searchwords: malformed XML, malformed HTML, parser, htree

Posted in development, javier ramirez, ruby on rails | Leave a Comment »

XML y Rails como herramienta de integración

Posted by javier ramirez on December 4, 2006

Si quieres descargar el material de la ponencia sobre XML y Rails que presenté en la conferencia rails, puedes hacerlo desde la página de descarga

Si prefieres verla online, puedes verla aquí

searchwords: XML, REXML, SimpleXML, to_xml, rxml, xml builder, SAX, STAX, push parser, pull parser, DOM

Posted in conferenciarails2006, development, javier ramirez, ruby on rails | 2 Comments »

rails,ajax,charset (ii)

Posted by javier ramirez on December 3, 2006

Como veíamos aquí cuando utilizamos ajax y rails tenemos que ser cuidadosos si queremos usar un juego de caracteres distinto a UTF-8. Ya hemos resuelto cómo mostrar correctamente la información que traemos desde el servidor, pero todavía tenemos que resolver cómo enviarla correctamente.

Cuando usamos los helpers de rails, las llamadas ajax funcionan enviando la información al servidor serializada mediante la función “serialize” definida en prototype.js. Esta función a su vez utiliza por debajo la llamada a la función javascript estándar “encodeURIComponent”. Por último, esta función hace un encoding pasando primero del charset que tenga la página a UTF-8 y luego escapando los caracteres.

Como resultado, cualquier llamada vía ajax que hagamos a través de prototype.js va a llegar al servidor con los parámetros en UTF-8. Una vez conscientes de este mecanismo, la forma de solucionarlo es simplemente convertir cada parámetro que recibamos en el servidor al encoding deseado, para lo que podemos usar Iconv.

Para evitar tener que pasar cada parámetro al encoding deseado, podemos hacer algo mucho más simple, consistente en convertir todos los parámetros del objeto “params”, de forma que no tenemos que andar en cada acción discriminando qué tenemos que convertir y qué no. Obviamente esto lo haremos solamente cuando detectemos que la llamada se hizo vía ajax.

Para mayor comodidad, lo que he hecho en mi caso es definir una función nueva llamada “iconv”, para la clase Hash, que directamente me recorre una Hash y todos sus hijos (siempre que soporten el método iconv), convirtiendo todas las cadenas que encuentra al encoding deseado. El código necesario para esto lo muestro a continuación:

class Hash
  #converts the encoding of all the values in the (can be nested) hash,by using iconv
  def iconv(c_to,c_from,obj_iconv=nil)
    obj_iconv = obj_iconv || Iconv.new(c_to,c_from )
    self.each do |key,value|
      next if value.blank?
      if value.is_a? String
        self[key] = obj_iconv.iconv(value)
      elsif value.respond_to? ‘iconv’
        self[key] = value.iconv(c_to,c_from,obj_iconv)
      end
    end
    return self
  end
end

Este código convertirá las Strings (y las Strings dentro de una Hash) que encuentre en el objeto que se le pase. Normalmente no necesitaremos nada más, pero si fuera necesario implementar la funcionalidad para Arrays u otros objetos, sería trivial definir un método “iconv” que los recorra de forma análoga al mostrado.

Ahora ya todo lo que tenemos que hacer es definir un filtro para todas nuestras acciones o bien escribir al principio de cada una el siguiente código

  params.iconv(‘iso-8859-1′,’utf-8’) if request.xhr?

Y ya podemos ser felices escribiendo y aceptando cadenas en nuestro propio idioma.

searchwords: charset, utf-8, iso-8859-1, iconv, prototype.js, javascript, encodeURIComponent, ajax

Posted in development, javier ramirez, ruby on rails | Leave a Comment »