2012-06-13 25 views
30

¿Hay alguna manera de obtener el nombre de las columnas reales con ActiveRecord?Obtener nombres de columnas con ActiveRecord

Cuando llamo find_by_sql o select_all con una combinación, si hay columnas con el mismo nombre, el primero uno y obtenga 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, me sale el 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 How do you get the rows and the columns in the result of a query with ActiveRecord?)

El método de unión de 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.

+1

Puede obtener todas las columnas de un modelo ActiveRecord utilizando: ** Model.columns.map (&: name) **, pero no estoy seguro de si esto es lo que desea. – MurifoX

+0

Cuando hago una unión, no sé qué valores pertenecen a qué modelos. Es por eso que quería los nombres de las columnas. – Abdo

Respuesta

54

AR proporciona un método #column_names que devuelve una matriz de nombres de columna

+6

para cualquiera que venga aquí en el futuro, este método está en desuso http://apidock.com/rails/ActiveRecord/Base/column_names/class – pahnin

+0

Gracias por compartir, @pahnin; sería genial si usted (o cualquier otra persona) puede recomendar alternativas – Abdo

+16

column_names() todavía está disponible para clases ActiveRecord, acaba de ser movido a un módulo diferente. – nocache

4

Esto es sólo forma el método de inspeccionar registro activo funciona: sólo se enumera la columna de de la mesa del modelo. Los atributos todavía están allí aunque

record.blah 

devolverán el atributo blah, incluso si es de otra tabla. También puede 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.Tendrás que aliar los nombres de las columnas para que sean únicos .

+0

Gracias por su respuesta. Intenté aliasing pero no conseguí nada diferente en AR's. Siempre son los mismos resultados mezclados. ¿Hay algún aliasing específico que AR espera? – Abdo

+0

cada nombre de columna debe ser único, p. seleccione table1. *, table2.id como t2_id, table2.name como t2_name, etc. AR no hará nada inteligente con eso, pero detendrá todo lo que colisiona –

+0

Esto significa que tendría que alias cada columna duplicada ... Muy tedioso :-( – Abdo

0

De acuerdo, he estado queriendo hacer algo que sea más eficiente por un tiempo.

Tenga en cuenta que para muy pocos resultados, incluya los trabajos 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 trabaje 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 

entonces me di cuenta de que podemos tener "a través de" tablas (relaciones nxm)

merge_through!aborda esta cuestión:

# 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 yo 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 se usa el 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 

Puedes modifique el código si desea cargar atributos perezosos que son de uno a muchos (como una ciudad es una ubicación) Básicamente, el código anterior no funcionará porque tendrá que recorrer el hash principal y configurar la 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 que el código todavía, así que salté =)

10

dos opciones

Model.column_names 

o

Model.columns.map(&:name) 

Ejemplo modelo de conejo llamado con el nombre columnas, edad, on_facebook

Rabbit.column_names 
Rabbit.columns.map(&:name) 

devuelve

["id", "name", "age", "on_facebook", "created_at", "updated_at"] 
Cuestiones relacionadas