使用ActiveRecord获取列名称

时间:2012-06-13 08:56:24

标签: ruby-on-rails activerecord

有没有办法用ActiveRecord获取实际的列名?

当我使用连接调用find_by_sql或select_all时,如果存在具有相同名称的列,则第一个被覆盖:

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

在上面的示例中,我得到以下内容:

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

其中id是最后一个s3_image的id。 select_rows是唯一按预期工作的东西:

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

我需要获取上面行的字段名称。 这篇文章接近我想要的但看起来过时了(fetch_fields似乎不再存在How do you get the rows and the columns in the result of a query with ActiveRecord?

ActiveRecord join方法创建多个对象。我正在尝试实现相同的结果“包含”将返回但是使用左连接。

我试图返回大量结果(有时整个表格),这就是为什么包含不适合我的需要。

4 个答案:

答案 0 :(得分:62)

AR提供了#column_names方法,该方法返回列名称数组

答案 1 :(得分:13)

两个选项

Model.column_names

Model.columns.map(&:name)

实施例 名为Rabbit的模型,列名为age,on_facebook

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

返回

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

答案 2 :(得分:4)

这就是主动记录的检查方法的工作原理:它只列出模型表中的列。虽然

仍然存在属性
record.blah

将返回blah属性,即使它来自另一个表。您也可以使用

record.attributes

获取包含所有属性的哈希值。

但是,如果您有多个具有相同名称的列(例如,两个表都有一个id列),则活动记录只会将事物混合在一起,忽略表名。您必须使用列别名别名以使其唯一。

答案 3 :(得分:0)

好吧,我一直想做一些效率更高的事情。

请注意,对于非常少的结果,包括工作正常。如果您想加入很多列,下面的代码会更好。

为了更容易理解代码,我首先编写了一个简易版本并对其进行了扩展。

第一种方法:

# 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

然后我注意到我们可以有“通过”表(nxm关系)

merge_through!解决了这个问题:

  # 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

这就是我所说的:

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

现在为完整的方法(使用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

以下是我使用代码的方式:

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

如果您希望延迟加载一对多的属性(例如City是一个位置),您可以修改代码。基本上,上面的代码不起作用,因为您必须循环主哈希并从第二个哈希设置城市(没有“city_id,location_id”表)。您可以撤消城市和位置以获取城市中的所有位置哈希然后提取回来。我还不需要那个代码所以我跳过它=)