在Ruby中动态创建多维哈希

我是一名PHP开发人员,他试图获得一些Ruby熟练程度。 我现在正在削减其中的一个项目是源代码审计工具,它可以扫描webapp文件,查找多种Web编程语言中的潜在危险函数。 找到匹配项后,脚本会将相关信息保存在poi (兴趣点)类中,以便稍后显示。

该类的示例实例看起来像这样(在YAML中建模):

 poi: file_type: "php" file: "the-scanned-file.php" line_number: 100 match: "eval()" snippet: "echo eval()" 

在展览中,我想组织这些兴趣点,如下所示:

 - file_type -- file --- match (the searched payload) 

因此,在演示之前,我正在尝试将一个平面的poi对象数组构成一个镜像上面结构的哈希。 这将允许我简单地遍历散列中的项目以产生所需的屏幕上组织。 (或者至少,这是计划。)

现在,对于我的问题:我如何在Ruby中做到这一点?

在PHP中,我可以很容易地做到这样的事情:

 file_type][$point->file][$point->match][] = $point; } ?> 

我试过将这种思想从PHP翻译成Ruby,但无济于事:

 sorted_pois = {} @points_of_interest.each_with_index do |point, index| sorted_pois[point.file_type.to_sym][point.file.to_sym][point.match.to_sym].push point end 

我已经花了几个小时在这上面,而且此刻我的头撞到了墙上,所以我想我离开了基地。 在Ruby中处理这个问题的正确方法是什么?

更新:

作为参考,这是我定义的精确方法:

 # sort the points of interest into a structured hash def sort sorted_pois = {} @points_of_interest.each_with_index do |point, index| sorted_pois[point.file_type.to_sym][point.file.to_sym][point.match.to_sym].push point end end 

这是我运行代码时收到的错误:

 ./lib/models/vulnscanner.rb:63:in `sort': undefined method `[]' for nil:NilClass (NoMethodError) from /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `each_with_index' from ./lib/models/vulnscanner.rb:62:in `each' from ./lib/models/vulnscanner.rb:62:in `each_with_index' from ./lib/models/vulnscanner.rb:62:in `sort' from ./webapp-vulnscan:69 

第62行(你可以推断)特别是这一行:

 @points_of_interest.each_with_index do |point, index| 

作为一个额外的参考,这里是转换为YAML时@points_of_interest的一个(片段):

 - !ruby/object:PoI file: models/couponkimoffer.php file_type: php group: :dangerous_functions line_number: "472" match: ` snippet: ORDER BY `created_at` DESC - !ruby/object:PoI file: models/couponkimoffer.php file_type: php group: :dangerous_functions line_number: "818" match: ` snippet: WHERE `company_slug` = '$company_slug' - !ruby/object:PoI file: models/couponkimoffer.php file_type: php group: :dangerous_functions line_number: "819" match: ` snippet: ORDER BY `created_at` DESC 

@ John的Enumerable#group_by建议是解决您需求的一种好方法。 另一种方法是创建一个自动生存的哈希(就像你似乎在PHP中一样),如下所示:

 hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) } hash[:a][:b][:c] = 42 p hash #=> {:a=>{:b=>{:c=>42}}} 

请注意,如果您访问不存在的密钥,这种自动生存可能是“危险的”,因为它会为您创建它们:

 p hash["does this exist?"] #=> {} p hash #=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}} 

如果你使用key?你仍然可以使用vivifying default_proc而不会遇到这种危险key? 首先测试密钥:

 val = hash["OH NOES"] if hash.key?("OH NOES") #=> nil p hash #=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}} 

FWIW,你得到的错误说: “嘿,你把[]放在评价nil东西之后,而nil没有[]方法。” 具体来说,你的代码……

 sorted_pois[point.file_type.to_sym] 

评估为nil (因为哈希值还没有这个键的值),然后你试图要求

 nil[point.file.to_sym] 

您可能对group_by感兴趣。

样品用法:

 birds = ["Golden Eagle", "Gyrfalcon", "American Robin", "Mountain BlueBird", "Mountain-Hawk Eagle"] grouped_by_first_letter = birds.group_by { |s| s[0] } # { "G"=>["Golden Eagle", "Gyrfalcon"], "A"=>["American Robin"], # "M"=>["Mountain BlueBird", "Mountain-Hawk Eagle"] } 

上面示例的明显问题是您尝试使用的嵌套哈希和数组不存在。 试试这个:

 sorted_pois = {} pois.each do |point| # sanitize data - convert to hash of symbolized keys and values poi = Hash[ %w{file_type file match}.map do |key| [key.to_sym, point.send(key).to_sym] end ] # create nested hash/array if it doesn't already exist sorted_pois[ poi[:file_type] ] ||= {} sorted_pois[ poi[:file_type] ][ poi[:file] ] ||= {} sorted_pois[ poi[:file_type] ][ poi[:file] ][ poi[:match] ] ||= [] sorted_pois[ poi[:file_type] ][ poi[:file] ][ poi[:match] ] << point end