有没有办法用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方法创建多个对象。我正在尝试实现相同的结果“包含”将返回但是使用左连接。
我试图返回大量结果(有时整个表格),这就是为什么包含不适合我的需要。
答案 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”表)。您可以撤消城市和位置以获取城市中的所有位置哈希然后提取回来。我还不需要那个代码所以我跳过它=)