2012-06-13 144 views
30

有沒有辦法使用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連接方法創建多個對象。我試圖達到相同的結果「包括」將返回,但左連接。

我試圖返回一大堆結果(有時是整個表),這就是爲什麼包含不適合我的需求。

+1

您可以通過使用** Model.columns.map(&:name)**獲取ActiveRecord模型的所有列,但我不確定這是否是您想要的。 – MurifoX

+0

當我進行連接時,我不知道哪些值屬於哪個模型。這就是爲什麼我想要專欄的名字。 – Abdo

回答

54

AR提供了一個#column_names方法返回列名的數組

+6

對於以後來這裏的任何人,這種方法已被棄用http://apidock.com/rails/ActiveRecord/Base/column_names/class – pahnin

+0

感謝分享,@pahnin;如果你(或任何其他人)可以推薦替代品,那將是非常好的。 – Abdo

+16

column_names()對於ActiveRecord類仍然可用,它剛剛被移動到不同的模塊中。 – nocache

4

這只是這樣的活動記錄的檢查方法的工作原理:它只是從模型的表列出列的。雖然

record.blah 

將返回blah屬性,即使它是來自另一個表。您還可以使用

record.attributes 

以獲得包含所有屬性的散列。但是,如果你有多個具有相同名稱的列(例如,兩個表都有一個id列),那麼活動記錄只是將所有東西混合在一起,忽略表名。您必須爲列名設置別名。

+0

感謝您的回答。我嘗試了別名,但在AR中沒有得到任何不同。它總是一致的結果。 AR有沒有具體的別名? – Abdo

+0

每列名稱必須是唯一的,例如選擇table1。*,table2.id作爲t2_id,table2.name作爲t2_name等AR不會做任何聰明的事情,但它會停止一切碰撞 –

+0

這意味着我將不得不別名每一個重複的列...非常乏味:-( – Abdo

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 

然後我注意到,我們可以有 「通過」 表(n×m的關係)

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_id,location_id」表)。你可以改變城市和位置來獲取城市哈希中的所有位置,然後提取回來。我不需要這樣的代碼,但讓我跳過它=)

10

兩個選項

Model.column_names 

Model.columns.map(&:name) 
命名兔子列姓名,年齡,

例 型號on_facebook

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

返回

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