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
With modern Ruby, you wont even have to use merge unless you need to change the variable in place using the ! variant, you can just double splat (**) your way through. h = { a: 1 } h2 = { b: 2 } h3 = { c: 3 } merged_hash = { **h, **h2, **h3 } => { a: 1, b: 2, c:3 }
In Ruby you can create a Hash by assigning a key to a value with => , separate these key/value pairs with commas, and enclose the whole thing with curly braces.
A Hash is a collection of key-value pairs like this: "employee" = > "salary". It is similar to an Array, except that indexing is done via arbitrary keys of any object type, not an integer index.
In Ruby, Hash is a collection of unique keys and their values. Hash is like an Array, except the indexing is done with the help of arbitrary keys of any object type. In Hash, the order of returning keys and their value by various iterators is arbitrary and will generally not be in the insertion order.
@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:
hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
hash[:a][:b][:c] = 42
p hash
#=> {:a=>{:b=>{:c=>42}}}
Note that this sort of auto-vivification can be 'dangerous' if you access keys that don't exist, as it creates them for you:
p hash["does this exist?"]
#=> {}
p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}
You can still use the vivifying default_proc
without hitting this danger if you use key?
to test for the key first:
val = hash["OH NOES"] if hash.key?("OH NOES")
#=> nil
p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}
FWIW, the error you are getting says, "Hey, you put []
after something that evaluated to nil
, and nil
doesn't have a []
method." Specifically, your code...
sorted_pois[point.file_type.to_sym]
evaluated to nil
(because the hash did not yet have a value for this key) and then you attempted to ask for
nil[point.file.to_sym]
You might be interested in group_by.
Sample usage:
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"] }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With