I'm a PHP developer who's trying to gain some proficiency in Ruby. One of the projects I'm cutting my teeth on now is a source-code auditing tool that scans webapp files for potentially dangerous functions in several web programming languages. When matches are found, the script saves the relevant information in a poi
(point-of-interest) class for display later on.
An example instance of that class would look something like this (modeled in YAML):
poi:
file_type: "php"
file: "the-scanned-file.php"
line_number: 100
match: "eval()"
snippet: "echo eval()"
On display, I want to organize these points of interest like so:
- file_type
-- file
--- match (the searched payload)
Thus, before presentation, I'm trying to structure a flat array of poi
objects into a hash mirroring the structure above. This will allow me to simply iterate over the items in the hash to produce the desired on-screen organization. (Or at least, that's the plan.)
And now, for my question: how do I do that in Ruby?
In PHP, I could do something like this really easily:
<?php
$sorted_pois = array();
foreach($points_of_interest as $point){
$sorted_pois[$point->file_type][$point->file][$point->match][] = $point;
}
?>
I've tried translating that thought from PHP to Ruby like this, but to no avail:
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
I've spent a few hours on this, and I'm kind of banging my head against the wall at this point, so presumably I'm way off-base. What's the proper way to handle this in Ruby?
Update:
For reference, this is the precise method I have defined:
# 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
This is the error I receive when I run the code:
./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
Line 62 (as you can likely infer) is this line in particular:
@points_of_interest.each_with_index do |point, index|
As an additional reference, here's what (a snippet of) @points_of_interest
looks like when converted to YAML:
- !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
The obvious problem with the example above is that nested hashes and arrays you try to use don't exist. Try this:
@John's
Enumerable#group_by
suggestion is one good way to solve your needs. Another would be to create an auto-vivifying Hash (like you appear to have in PHP) like so:Note that this sort of auto-vivification can be 'dangerous' if you access keys that don't exist, as it creates them for you:
You can still use the vivifying
default_proc
without hitting this danger if you usekey?
to test for the key first:FWIW, the error you are getting says, "Hey, you put
[]
after something that evaluated tonil
, andnil
doesn't have a[]
method." Specifically, your code...evaluated to
nil
(because the hash did not yet have a value for this key) and then you attempted to ask forYou might be interested in group_by.
Sample usage: