47

有没有办法使用 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 的。select_rows 是唯一按预期工作的东西:

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

我需要获取上面行的字段名称。这篇文章接近我想要的,但看起来已经过时了(fetch_fields 似乎不再存在了如何获取 ActiveRecord 查询结果中的行和列?

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

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

4

4 回答 4

84

Active Record 提供了#column_names一种返回列名数组的方法。

使用示例:User.column_names

于 2012-06-13T12:40:04.260 回答
21

两个选项

Model.column_names

或者

Model.columns.map(&:name)

名为 Rabbit 的示例模型,列名称、年龄、on_facebook

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

返回

["id", "name", "age", "on_facebook", "created_at", "updated_at"] 
于 2015-03-05T04:01:09.980 回答
5

这只是活动记录的检查方法的工作方式:它只列出模型表中的列。属性仍然存在

record.blah

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

record.attributes

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

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

于 2012-06-13T12:47:35.100 回答
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 关系)

合并通过!解决这个问题:

  # 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 is to a Location),您可以修改代码。基本上,上面的代码不起作用,因为您必须遍历主哈希并设置来自第二个哈希的城市(没有“city_id,location_id”表)。您可以反转城市和位置以获取城市哈希中的所有位置,然后提取回来。我还不需要那个代码所以我跳过了它=)

于 2012-06-13T14:56:13.130 回答