web-dev-qa-db-fra.com

Obtenir des noms de colonnes avec ActiveRecord

Existe-t-il un moyen d'obtenir le nom réel des colonnes avec ActiveRecord?

Lorsque j'appelle find_by_sql ou select_all avec une jointure, s'il existe des colonnes du même nom, la première est remplacée:

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

Dans l'exemple ci-dessus, j'obtiens ce qui suit:

#<Location id: 22, name: ... 
>

Où id est celui de la dernière image s3_. select_rows est la seule chose qui a fonctionné comme prévu:

Model.connection.select_rows("SELECT id,name FROM users") => [["1","amy"],["2","bob"],["3","cam"]]

J'ai besoin d'obtenir les noms de champs pour les lignes ci-dessus. Ce message se rapproche de ce que je veux mais semble obsolète (fetch_fields ne semble plus exister Comment obtenez-vous les lignes et les colonnes dans le résultat d'une requête avec ActiveRecord? )

La méthode de jointure ActiveRecord crée plusieurs objets. J'essaie d'obtenir le même résultat "comprend" reviendrait, mais avec une jointure gauche.

J'essaie de renvoyer beaucoup de résultats (et parfois des tableaux entiers) c'est pourquoi les inclusions ne conviennent pas à mes besoins.

39
Abdo

Active Record fournit un #column_names méthode qui renvoie un tableau de noms de colonnes.

Exemple d'utilisation: User.column_names

67
TheIrishGuy

deux options

Model.column_names

ou

Model.columns.map(&:name)

Exemple de modèle nommé Rabbit avec nom des colonnes, âge, on_facebook

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

retour

["id", "name", "age", "on_facebook", "created_at", "updated_at"] 
13
thedanotto

C'est juste ainsi que la méthode d'inspection de l'enregistrement actif fonctionne: elle répertorie uniquement les colonnes de la table du modèle. Les attributs sont toujours là cependant

record.blah

retournera l'attribut blah, même s'il provient d'une autre table. Vous pouvez aussi utiliser

record.attributes

pour obtenir un hachage avec tous les attributs.

Cependant, si vous avez plusieurs colonnes avec le même nom (par exemple, les deux tables ont une colonne id), l'enregistrement actif écrase simplement les choses ensemble, en ignorant le nom de la table.Vous devrez alias les noms de colonne pour les rendre uniques.

4
Frederick Cheung

D'accord, je voulais faire quelque chose de plus efficace depuis un moment.

Veuillez noter que pour très peu de résultats, incluez les travaux très bien. Le code ci-dessous fonctionne mieux lorsque vous souhaitez rejoindre un grand nombre de colonnes.

Afin de faciliter la compréhension du code, j'ai d'abord élaboré une version facile et je l'ai développée.

Première méthode:

# 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

J'ai ensuite remarqué que nous pouvons avoir des tables "à travers" (relations nxm)

merge_through! résout ce problème:

  # 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

Voici comment je l'appelle:

 lambmerge = lambda do |lhash, rhash, lid, rid| 
                         lhash[lid].keywords << rhash[rid] 
                end
    Location.merge_through!(Location.all, Keyword.all, LocationsKeyword.all, lambmerge)

Maintenant, pour la méthode complète (qui utilise 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

Voici comment j'utilise mon code:

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

Vous pouvez modifier le code si vous souhaitez charger des attributs paresseux qui sont un à plusieurs (comme une ville est à un emplacement) Fondamentalement, le code ci-dessus ne fonctionnera pas car vous devrez parcourir le hachage principal et définir le ville du deuxième hachage (il n'y a pas de table "city_id, location_id"). Vous pouvez inverser la ville et l'emplacement pour obtenir tous les emplacements du hachage de la ville, puis extraire. Je n'ai pas encore besoin de ce code donc je l'ai sauté =)

0
Abdo