Ruby-on-rails 使用 ActiveRecord 获取列名

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11011402/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 03:31:48  来源:igfitidea点击:

Get columns names with ActiveRecord

ruby-on-railsactiverecord

提问by Abdo

Is there a way to get the actual columns name with ActiveRecord?

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

When I call find_by_sql or select_all with a join, if there are columns with the same name, the first one get overridden:

当我使用连接调用 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

In the example above, I get the following:

在上面的示例中,我得到以下信息:

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

Where id is that of the last s3_image. select_rows is the only thing that worked as expected:

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

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

I need to get the field names for the rows above. This post gets close to what I want but looks outdated (fetch_fields doesn't seem to exist anymore How do you get the rows and the columns in the result of a query with ActiveRecord?)

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

The ActiveRecord join method creates multiple objects. I'm trying to achieve the same result "includes" would return but with a left join.

ActiveRecord 连接方法创建多个对象。我试图达到相同的结果“包含”会返回但带有左连接。

I am attempting to return a whole lot of results (and sometimes whole tables) this is why includes does not suit my needs.

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

回答by TheIrishGuy

Active Record provides a #column_namesmethod that returns an array of column names.

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

Usage example: User.column_names

用法示例: User.column_names

回答by thedanotto

two options

两种选择

Model.column_names

or

或者

Model.columns.map(&:name)

Example Model named Rabbit with columns name, age, on_facebook

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

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

returns

回报

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

回答by Frederick Cheung

This is just way active record's inspect method works: it only lists the column's from the model's table. The attributes are still there though

这就是活动记录的检查方法的工作方式:它只列出模型表中的列。属性还是有的

record.blah

will return the blah attribute, even if it is from another table. You can also use

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

record.attributes

to get a hash with all the attributes.

获取所有属性的哈希值。

However, if you have multiple columns with the same name (e.g. both tables have an id column) then active record just mashes things together, ignoring the table name.You'll have to alias the column names to make them unique.

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

回答by Abdo

Okay I have been wanting to do something that's more efficient for a while.

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

Please note that for very few results, include works just fine. The code below works better when you have a lot of columns you'd like to join.

请注意,对于很少的结果,include 效果很好。当您有很多要加入的列时,下面的代码效果更好。

In order to make it easier to understand the code, I worked out an easy version first and expanded on it.

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

First method:

第一种方法:

# 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

I then noticed that we can have "through" tables (nxm relationships)

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

merge_through! addresses this issue:

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

  # 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

This is how I call it:

我是这样称呼它的:

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

Now for the complete method (which makes use of merge_through)

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

Here's how I use my 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

You can modify the code if you wish to lazy load attributes that are one to many (such as a City is to a Location) Basically, the code above won't work because you'll have to loop through the main hash and set the city from the second hash (There is no "city_id, location_id" table). You could reverse the City and Location to get all the locations in the city hash then extract back. I don't need that code yet so I skipped it =)

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