Problema con consulta a base de datos

Buenas, tengo un problema que me está volviendo loca.

Cuando hago esto:

lista_locales = Local.find_by_sql(“SELECT locals.nombre as nom_loc,
locals.id as id_loc, edificios.nombre as edifi_loc FROM locals inner
join edificios on locals.edificio_id=edificios.id”)

obtengo el siguiente error:


Showing
vendor/plugins/active_scaffold/frontends/default/views/_form_attribute.rhtml
where line #3 raised:

undefined local variable or method `nombre’ for #Local:0xb74736e8

Extracted source (around line #3):

1: <% scope ||= nil %>
2: <label for="<%= “record_#{column.name}” %>"><%= column.label
%>
3: <%= form_column column, scope %>
4: <% if column.description -%>
5: <%= column.description %>
6: <% end -%>

Trace of template inclusion:
/vendor/plugins/active_scaffold/frontends/default/views/_form.rhtml,
/vendor/plugins/active_scaffold/frontends/default/views/_update_form.rhtml,
/vendor/plugins/active_scaffold/frontends/default/views/update_form.rhtml

RAILS_ROOT: script/…/config/…
Application Trace | Framework Trace | Full Trace

/usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1860:in
method_missing' app/models/local.rb:7:into_s’

Si dejo así:

lista_locales = Local.find_by_sql(“SELECT locals.nombre, locals.id as
id_loc, edificios.nombre as edifi_loc FROM locals inner join edificios
on locals.edificio_id=edificios.id”)

Alguien sabe por qué y cómo solucinarlo,

Gracias.

Si dejo así, funciona:

lista_locales = Local.find_by_sql(“SELECT locals.nombre, locals.id as
id_loc, edificios.nombre as edifi_loc FROM locals inner join edificios
on locals.edificio_id=edificios.id”)

Hola María,

estoy seguro que hay una manera “más rails” para hacer lo que intentas,
si nos puedes mostrar los modelos de tu aplicación para ver las
relaciones entre ambos y que es lo que esperas que te entregue la vista
te podremos ayudar mejor.

Saludos.

Sergio Guamán B.
La Serena
Chile

Maria escribió:

María,

el log es tu amigo, ¿qué dice el development.log?, ¿ejecutaste ese
Local.find_by_sql desde la consola de rails?

saludos,

Maria escribió:

Hola,

lista_locales = Local.find_by_sql(“SELECT locals.nombre, locals.id as
id_loc, edificios.nombre as edifi_loc FROM locals inner join edificios
on locals.edificio_id=edificios.id”)
Tu problema es muy simple. En el primer caso tienes una query tal que
así “select locals.nombre AS nom_loc,…”. Lo que estás haciendo es
seleccionar campos de la base de datos y meterlos en el resultado con el
nombre “nom_loc” en lugar de nombre y así sucesivamente (esto te pasa
por usar AS). Cuando rails encuentra los resultados intenta cargarlos en
objetos de tipo local, pero no encuentra por ninguna parte los campos
que él espera, con lo que no te los carga.

Luego en tu, al parecer de un scaffold, partial intentas acceder
directamente al campo nombre, que como no existe en la query que estás
ejecutando, no existe en el objeto local. Y de ahí tu error.

En el segundo caso, cuando eliminas los alias de las columnas todo va
bien.

De todos modos, bajo mi punto de vista te estás equivocando al utilizar
rails. Cuando se decide utilizar un framework de desarrollo que tenga
mapeo ORM (es decir, conversión de tablas a objetos), lo suyo es
utilizar siempre que sea posible el mecanismo que te da el framework.
Con eso vas a conseguir que todos los mecanismos complementarios tales
como validaciones, filtros, etc… funcionen perfectamente. Además, te
permite hacer código más legible y mantenible, y permite que
desarrolladores sin experiencia en base de datos puedan hacer consultas
que serían más susceptibles a error de otro modo.

Cuando te saltas los mecanismos normales de tu ORM, estás usando un
framework que realmente carga tu sistema con más cosas de las que
usarías con una conexión plana, y no obtienes ninguno de los beneficios.
Es una situación pierde-pierde.

A veces, con consultas complejas para las que rails no está diseñado, no
queda otra opción. Rails llega hasta donde llega y alguna vez toca hacer
queries a mano (especialmente en el caso de subselects y similares), ya
que si lo quieres hacer de la forma que rails es capaz, acabarías
tirando varias consultas más y haciendo matching de objetos en memoria.
En esos casos conscientemente te saltas el mecanismo ORM y manejas la db
“a pelo”. Pero siendo consciente de lo que estás haciendo y de lo que te
pierdes a cambio.

En tu caso, creo que no está justificado usar sql direcamente. Lo que
querías hacer en tu query en rails se hace así

Local.find(:all, :include=>:edificio)

Hay dos diferencias en esta query con respecto a la tuya. La primera es
que te devuelve todos los campos, y no solamente nombre e id (si
trabajas con scaffold, te vendrá bien). La segunda es que la query es
una outer join, con lo que te encuentra todos los locales tengan
edificio asignado o no, y además para los que tengan asignado un
edificio también te lo pone directamente en el resultado.

Saludos,

javier ramírez

On 5/16/07, Maria [email protected] wrote:

from (equipos inner join armarios on equipos.armario_id=armarios.id)
inner join edificios on edificios.id=armarios.edificio_id Order by
edificios.nombre, armarios.nombre, equipos.nombre")

y no me ha dado ningún error. Esto si que no consigo hacerlo al estilo
ROR…

Muchas gracias.

Cuando en el primer ejemplo utilizas la variable “column” ¿de dónde la
sacas? Supongo que de un Local.columns iterando sobre cada columna. El
problema entonces está que Rails obtiene los nombres reales de las
columnas (“nombre” en vez de “nom_loc”), pero tus instancias de
locales solo conocen “nom_loc”.

Supongo que en el segundo caso no iteraís sobre las columnas para
mostrar los datos y utilizáis directamente “edific”, “armar”,
“equip”… por lo que no tenéis problemas.

De cualquier forma, es dificil de saber ya que no hemos visto los
archivos de vista completos y hay cosas de esas 5 líneas que muestras
que sin contexto no se a lo que se refieren (“scope”,
“form_column”,…).

Pero como te dice Javier, deberías adaptar tu estilo de
programaciónal framework, y ver que esas consultas de find_by_sql se pueden hacer
utilizando mecanismos de Rails, que al final serán más manejables y
sencillos. Por ejemplo tu primera busqueda se reduce a:

lista_locales = Local.find(:all, :include => :edificio)

(Teniendo un belongs_to :edificio en local.rb)

Con ello obtendrias un array de locales que tendrían como atributos
“id”, “nombre” y “edificio.nombre”.

Y la segunda busqueda a:

lista_equipos = Equipo.find(:all, :include => { :armario => :edificio
}, :order => “edificios.nombre, armarios.nombre, equipos.nombre”)

(Teniendo belongs_to :armario en equipo.rb y belongs_to :edificio en
armario.rb)

Obteniendo como atributos “id”, “nombre”, “armario.nombre” y
“armario.edificio.nombre”.

A mi parecer mucho más claro que leer el SQL “crudo” que si no me
equivoco “arrastraís” de un proyecto anterior que no estaba en Rails.

Bai.

Buenas,

En este caso ya se como se hace al estilo ROR, sólo lo he hecho así para
hacer pruebas y no entiendo por que me da el error.

Anteriormete he hecho esto:

lista_equipos = Equipo.find_by_sql(“SELECT edificios.nombre as edific,
armarios.nombre as armar, equipos.nombre as equip, equipos.id as elid
from (equipos inner join armarios on equipos.armario_id=armarios.id)
inner join edificios on edificios.id=armarios.edificio_id Order by
edificios.nombre, armarios.nombre, equipos.nombre”)

y no me ha dado ningún error. Esto si que no consigo hacerlo al estilo
ROR…

Muchas gracias.

javier ramirez wrote:

Hola,

lista_locales = Local.find_by_sql(“SELECT locals.nombre, locals.id as
id_loc, edificios.nombre as edifi_loc FROM locals inner join edificios
on locals.edificio_id=edificios.id”)
Tu problema es muy simple. En el primer caso tienes una query tal que
así “select locals.nombre AS nom_loc,…”. Lo que estás haciendo es
seleccionar campos de la base de datos y meterlos en el resultado con el
nombre “nom_loc” en lugar de nombre y así sucesivamente (esto te pasa
por usar AS). Cuando rails encuentra los resultados intenta cargarlos en
objetos de tipo local, pero no encuentra por ninguna parte los campos
que él espera, con lo que no te los carga.

Luego en tu, al parecer de un scaffold, partial intentas acceder
directamente al campo nombre, que como no existe en la query que estás
ejecutando, no existe en el objeto local. Y de ahí tu error.

En el segundo caso, cuando eliminas los alias de las columnas todo va
bien.

De todos modos, bajo mi punto de vista te estás equivocando al utilizar
rails. Cuando se decide utilizar un framework de desarrollo que tenga
mapeo ORM (es decir, conversión de tablas a objetos), lo suyo es
utilizar siempre que sea posible el mecanismo que te da el framework.
Con eso vas a conseguir que todos los mecanismos complementarios tales
como validaciones, filtros, etc… funcionen perfectamente. Además, te
permite hacer código más legible y mantenible, y permite que
desarrolladores sin experiencia en base de datos puedan hacer consultas
que serían más susceptibles a error de otro modo.

Cuando te saltas los mecanismos normales de tu ORM, estás usando un
framework que realmente carga tu sistema con más cosas de las que
usarías con una conexión plana, y no obtienes ninguno de los beneficios.
Es una situación pierde-pierde.

A veces, con consultas complejas para las que rails no está diseñado, no
queda otra opción. Rails llega hasta donde llega y alguna vez toca hacer
queries a mano (especialmente en el caso de subselects y similares), ya
que si lo quieres hacer de la forma que rails es capaz, acabarías
tirando varias consultas más y haciendo matching de objetos en memoria.
En esos casos conscientemente te saltas el mecanismo ORM y manejas la db
“a pelo”. Pero siendo consciente de lo que estás haciendo y de lo que te
pierdes a cambio.

En tu caso, creo que no está justificado usar sql direcamente. Lo que
querías hacer en tu query en rails se hace así

Local.find(:all, :include=>:edificio)

Hay dos diferencias en esta query con respecto a la tuya. La primera es
que te devuelve todos los campos, y no solamente nombre e id (si
trabajas con scaffold, te vendrá bien). La segunda es que la query es
una outer join, con lo que te encuentra todos los locales tengan
edificio asignado o no, y además para los que tengan asignado un
edificio también te lo pone directamente en el resultado.

Saludos,

javier ramírez

Hola

En este caso ya se como se hace al estilo ROR, sólo lo he hecho así para hacer pruebas y no entiendo por que me da el error.

Como te comenté en el mail anterior, el error te lo da porque tú
recuperas las columnas con un alias, pero luego en el resultado que
obtienes intentas acceder con el nombre original, no con el alias. El
problema no está en la query en sí misma, sino en que la lista de campos
de tu query y el nombre de los campos que luego usas deben
corresponderse; si no, rails es incapaz de adivinar qué es lo que
quieres hacer.

Si en unas queries te funciona y en otras no, es porque en unas accedes
con los nombres tal cuál los marcas en la query, y en la otra no.

Saludos,

javier ramirez

Hola,

(select vlans.numero from vlans inner join conexions_vlans on
conexions_vlans.vlan_id=vlans.id where conexions_vlans.conexion_id=1)

Vlan.find(:all,:include=>:tag).map{|v| v.conexion_id=nil unless
v.conexion_id=1}

Eso te encuentra todos los Vlan con su correspondiente conexión via
outer join. Como en tu query por algún motivo quieres obtener sólo el id
de una de las conexiones lo que hago es luego poner a nil todos los id
excepto el de la conexión que buscas.

La verdad es que tu query era un pelín rebuscada y en casos así ya
podría ser que hiciera falta usar sql directamente. Sobre todo para
casos de subselects y unions ya podría pasar (aunque hay que ver si
realmente prefieres una union o dos finds y luego concatenar los
resultados).

Básicamente la idea es que un find con un include te hace una outer
join, y como te decían antes en otro mail puedes concatenar varios
includes. Para el resto de opciones, lo suyo es la documentación, probar
un poco e ir siguiendo el log a ver el sql generado.

Saludos,

j

Hola,

No entinedo bien tu solución, yo en realidad tengo estas tablas:

conexions
id

vlans
id

conexions_vlans
vlan_id
conexion_id

Gracias por todo. La verdad que uso SQL porque es lo que he usado
siempre, ROR estoy aprendiendo ahora y estoy haciendo una pequeña
aplicación para practicar un poco.

Cómo sería una consulta más compleja como ésta?:

Tengo estos dos modelos:

class Conexion < ActiveRecord::Base
belongs_to :untag, :class_name => ‘Vlan’
has_and_belongs_to_many :tag, :class_name => “Vlan”

end

class Vlan < ActiveRecord::Base
has_many :untag, :class_name => “Conexion”
has_and_belongs_to_many :tag, :class_name => “Conexion”

end

Quiero obtener los tags y el numero de conexion, para un determinado
número de conexión. Pero quiero que obtener TODOS los tags.

Yo en SQL hago esto. Por ejemplo, para la conexión con id=1:

select vlans.numero, conexions_vlans.conexion_id from vlans inner join
conexions_vlans on conexions_vlans.vlan_id=vlans.id where
conexions_vlans.conexion_id=1
union
select numero, null as conexion_id from vlans where vlans.numero not in
(select vlans.numero from vlans inner join conexions_vlans on
conexions_vlans.vlan_id=vlans.id where conexions_vlans.conexion_id=1)

Cómo se podría hacer esto en la forma de ROR?

Muchas gracias.

Hola, quería acotar algo

javier ramirez escribió:

Hola,
Local.find(:all, :include=>:edificio)

Hay dos diferencias en esta query con respecto a la tuya. La primera es
que te devuelve todos los campos, y no solamente nombre e id (si
trabajas con scaffold, te vendrá bien). La segunda es que la query es
una outer join, con lo que te encuentra todos los locales tengan
edificio asignado o no, y además para los que tengan asignado un
edificio también te lo pone directamente en el resultado.
Este comportamiento en sql se refiere a una LEFT OUTER JOIN o también
LEFT JOIN a secas, para OUTER JOIN hay 2 tipos básicos, la LEFT join que
te devuelve todos los registros de la primera tabla aúnque no tenga un
registro asociado en la segunda tabla(por lo que para los campos de la
segunda tabla devuelve NULL); y la RIGHT join que es al revés, es decir,
todas los registros que esté en la segunda tabla y que tengan o no
registro asociado en la primera tabla(este es extraño y nunca lo he
utilizado :slight_smile: ). Hay algunos SGBDR que implementan algunas otras que no
recuerdo como DB2.

sintaxis SQL
select * from tabla-a [LEFT | RIGHT] [OUTER] JOIN tabla-b ON (…)

Creo que me explayé demasiado

Saludos,

javier ramírez


Ror-es mailing list
[email protected]
simplelogica.net

Saludos

Sergio Guamán Bravo
La Serena
Chile

Hola,

cuando pongo:
Vlan.find(:all,:include=>:tag).map{|v| v.conexion_id=nil unless
v.conexion_id=1}

me da el error:
undefined method `conexion_id=’ for #Vlan:0xb76fbb7c

Extracted source (around line #7):

4:
5: <tr <%= classAttr %> id=“<%= element_row_id(:action => :list, :id =>
record.id) %>”>
6: <% active_scaffold_config.list.columns.each do |column| %>
7: <% column_value = render_column(record, column) -%>
8:
9:
10: <% if column.link -%>

listas.sguamanb wrote:

Hola, quería acotar algo

javier ramirez escribió:

Hola,
Local.find(:all, :include=>:edificio)

Hay dos diferencias en esta query con respecto a la tuya. La primera es
que te devuelve todos los campos, y no solamente nombre e id (si
trabajas con scaffold, te vendrá bien). La segunda es que la query es
una outer join, con lo que te encuentra todos los locales tengan
edificio asignado o no, y además para los que tengan asignado un
edificio también te lo pone directamente en el resultado.
Este comportamiento en sql se refiere a una LEFT OUTER JOIN o también
LEFT JOIN a secas, para OUTER JOIN hay 2 tipos básicos, la LEFT join que
te devuelve todos los registros de la primera tabla aúnque no tenga un
registro asociado en la segunda tabla(por lo que para los campos de la
segunda tabla devuelve NULL); y la RIGHT join que es al revés, es decir,
todas los registros que esté en la segunda tabla y que tengan o no
registro asociado en la primera tabla(este es extraño y nunca lo he
utilizado :slight_smile: ). Hay algunos SGBDR que implementan algunas otras que no
recuerdo como DB2.

sintaxis SQL
select * from tabla-a [LEFT | RIGHT] [OUTER] JOIN tabla-b ON (…)

Creo que me explayé demasiado

Saludos,

javier ramírez


Ror-es mailing list
[email protected]
simplelogica.net

Saludos

Sergio Guamán Bravo
La Serena
Chile

Hola de nuevo,

cuando pongo:
Vlan.find(:all,:include=>:tag).map{|v| v.conexion_id=nil unless
v.conexion_id=1}

Aquí lo importante es entender lo que estás haciendo. Cuando te puse ese
trozo de código estaba “adivinando” tu modelo de datos con la
información que habías mandado, así que tendrás que adaptarlo un poquito
a lo que tengas.

Lo primero que tienes que entender es que estás haciendo primero una
select de vlan y tag (no sé qué tabla tienes en esa relación) y que la
select es una outer (left outer) join.

Si ejecutas en la consola esa sentencia, puedes ver lo que te devuelve,
que serán básicamente n vlans y para cada una tendrás un método .tag que
tiene todos los campos de la segunda tabla.

Ahora, como tú querías tener informado el id de la conexión solamente
para el caso de que la conexión sea 1 y para los demás no, entonces
estaba usando map para recorrer los resultados y poner a nil ese campo.
De esta forma no necesitas utilizar unions y subselects, sino que puedes
hacer todo el proceso en memoria.

Entendiendo lo que hace el find y lo que hace el map, ya deberías poder
adaptar a tus necesidades y a tu modelo la sentencia anterior.

Saludos,

j