ruby-on-rails - left - rails sql query
Obtener nombres de columnas con ActiveRecord (4)
AR proporciona un método #column_names
que devuelve una matriz de nombres de columna
¿Hay alguna manera de obtener el nombre de las columnas reales con ActiveRecord?
Cuando llamo find_by_sql o select_all con un join, si hay columnas con el mismo nombre, el primero queda anulado:
select locations.*, s3_images.* from locations left join s3_images on s3_images.imageable_id = locations.id and s3_images.imageable_type = ''Location'' limit 1
En el ejemplo anterior, obtengo lo siguiente:
#<Location id: 22, name: ...
>
Donde id es el de la última s3_image. select_rows es lo único que funcionó como se esperaba:
Model.connection.select_rows("SELECT id,name FROM users") => [["1","amy"],["2","bob"],["3","cam"]]
Necesito obtener los nombres de campo para las filas de arriba. Esta publicación se acerca a lo que quiero pero parece obsoleta (fetch_fields ya no parece existir ¿Cómo se obtienen las filas y las columnas en el resultado de una consulta con ActiveRecord? )
El método de unión ActiveRecord crea múltiples objetos. Estoy tratando de lograr el mismo resultado que "incluye" volvería pero con una combinación izquierda.
Estoy intentando devolver un montón de resultados (y, a veces, tablas completas) esta es la razón por la que incluye no se ajusta a mis necesidades.
Así es como funciona el método de inspección del registro activo: solo enumera las columnas de la tabla del modelo. Los atributos todavía están allí, aunque
record.blah
devolverá el atributo blah, incluso si proviene de otra tabla. También puedes usar
record.attributes
para obtener un hash con todos los atributos.
Sin embargo, si tiene varias columnas con el mismo nombre (p. Ej., Ambas tablas tienen una columna de id.), El registro activo simplemente mezcla las cosas, ignorando el nombre de la tabla. Deberá alias los nombres de las columnas para que sean únicas.
De acuerdo, he querido hacer algo que sea más eficiente por un tiempo.
Tenga en cuenta que para muy pocos resultados, incluya los trabajos muy bien. El siguiente código funciona mejor cuando tienes muchas columnas a las que te gustaría unirte.
Para facilitar la comprensión del código, primero trabajé en una versión fácil y la amplié.
Primer método:
# takes a main array of ActiveRecord::Base objects
# converts it into a hash with the key being that object''s id method call
# loop through the second array (arr)
# and call lamb (a lambda { |hash, itm| ) for each item in it. Gets called on the main
# hash and each itm in the second array
# i.e: You have Users who have multiple Pets
# You can call merge(User.all, Pet.all, lambda { |hash, pet| hash[pet.owner_id].pets << pet }
def merge(mainarray, arr, lamb)
hash = {}
mainarray.each do |i|
hash[i.id] = i.dup
end
arr.each do |i|
lamb.call(i, hash)
end
return hash.values
end
Luego noté que podemos tener tablas "directas" (relaciones nxm)
merge_through! aborda este problema:
# this works for tables that have the equivalent of
# :through =>
# an example would be a location with keywords
# through locations_keywords
#
# the middletable should should return as id an array of the left and right ids
# the left table is the main table
# the lambda fn should store in the lefthash the value from the righthash
#
# if an array is passed instead of a lefthash or a righthash, they''ll be conveniently converted
def merge_through!(lefthash, righthash, middletable, lamb)
if (lefthash.class == Array)
lhash = {}
lefthash.each do |i|
lhash[i.id] = i.dup
end
lefthash = lhash
end
if (righthash.class == Array)
rhash = {}
righthash.each do |i|
rhash[i.id] = i.dup
end
righthash = rhash
end
middletable.each do |i|
lamb.call(lefthash, righthash, i.id[0], i.id[1])
end
return lefthash
end
Así es como lo llamo:
lambmerge = lambda do |lhash, rhash, lid, rid|
lhash[lid].keywords << rhash[rid]
end
Location.merge_through!(Location.all, Keyword.all, LocationsKeyword.all, lambmerge)
Ahora para el método completo (que hace uso de merge_through)
# merges multiple arrays (or hashes) with the main array (or hash)
# each arr in the arrs is a hash, each must have
# a :value and a :proc
# the procs will be called on values and main hash
#
# :middletable will merge through the middle table if provided
# :value will contain the right table when :middletable is provided
#
def merge_multi!(mainarray, arrs)
hash = {}
if (mainarray.class == Hash)
hash = mainarray
elsif (mainarray.class == Array)
mainarray.each do |i|
hash[i.id] = i.dup
end
end
arrs.each do |h|
arr = h[:value]
proc = h[:proc]
if (h[:middletable])
middletable = h[:middletable]
merge_through!(hash, arr, middletable, proc)
else
arr.each do |i|
proc.call(i, hash)
end
end
end
return hash.values
end
Así es como uso mi código:
def merge_multi_test()
merge_multi!(Location.all,
[
# each one location has many s3_images (one to many)
{ :value => S3Image.all,
:proc => lambda do |img, hash|
if (img.imageable_type == ''Location'')
hash[img.imageable_id].s3_images << img
end
end
},
# each location has many LocationsKeywords. Keywords is the right table and LocationsKeyword is the middletable.
# (many to many)
{ :value => Keyword.all,
:middletable => LocationsKeyword.all,
:proc => lambda do |lhash, rhash, lid, rid|
lhash[lid].keywords << rhash[rid]
end
}
])
end
Puede modificar el código si desea cargar atributos que son de uno a varios (como una ciudad es una ubicación). Básicamente, el código anterior no funcionará porque tendrá que recorrer el hash principal y establecer el ciudad desde el segundo hash (no hay tabla "city_id, location_id"). Puede invertir la ciudad y la ubicación para obtener todas las ubicaciones en el hash de la ciudad y luego extraer de nuevo. No necesito ese código todavía, así que lo salté =)
dos opciones
Model.column_names
o
Model.columns.map(&:name)
Modelo de ejemplo llamado Conejo con nombre de columnas, edad, on_facebook
Rabbit.column_names
Rabbit.columns.map(&:name)
devoluciones
["id", "name", "age", "on_facebook", "created_at", "updated_at"]