2012-04-20 13 views
7

Sono uno sviluppatore PHP che sta cercando di acquisire un po 'di competenza in Ruby. Uno dei progetti su cui sto tagliando i denti ora è uno strumento di controllo del codice sorgente che analizza i file webapp per funzioni potenzialmente pericolose in diversi linguaggi di programmazione web. Quando vengono trovate le corrispondenze, lo script salva le informazioni rilevanti in una classe poi (punto di interesse) per la visualizzazione in seguito.Creazione dinamica di un hash multidimensionale in Ruby

Un'istanza esempio di quella classe sarebbe simile a questa (modellati in YAML):

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

In mostra, voglio organizzare questi punti di interesse in questo modo:

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

Così , prima della presentazione, sto cercando di strutturare una serie piatta di oggetti poi in un hash che rispecchia la struttura precedente. Ciò mi consentirà di eseguire semplicemente un'iterazione sugli elementi dell'hash per produrre l'organizzazione sullo schermo desiderata. (O almeno, questo è il piano.)

E ora, per la mia domanda: come faccio a farlo in Ruby?

In PHP, avrei potuto fare qualcosa di simile molto facilmente:

<?php 

$sorted_pois = array(); 
foreach($points_of_interest as $point){ 
    $sorted_pois[$point->file_type][$point->file][$point->match][] = $point; 
} 

?> 

Ho provato a tradurre quel pensiero da PHP a Ruby come questo, ma senza alcun risultato:

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 Ho passato un paio d'ore su questo, e in questo momento sto sbattendo la testa contro il muro, quindi presumibilmente sono fuori moda. Qual è il modo corretto di gestirlo in Ruby?

Aggiornamento:

Per avere un riferimento, questo è il metodo preciso ho definito:

# 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 

Questo è l'errore che ricevo quando faccio funzionare il codice:

./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 

La linea 62 (come probabilmente è possibile dedurre) è questa linea in particolare:

@points_of_interest.each_with_index do |point, index| 

Come ulteriore riferimento, ecco cosa (un frammento di) @points_of_interest sembra una volta convertito in 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 
+1

Cosa c'è di sbagliato con quello che avere? Risulta in errori o l'output non è quello che ti aspetti? Inoltre, fornire input/output di esempio è utile. –

+0

@AndrewMarshall, grazie per dare un'occhiata. Ho appena aggiornato la domanda. –

risposta

27

@Enumerable#group_by il suggerimento di Giovanni è un buon modo per risolvere le vostre esigenze.Un'altra potrebbe essere quella di creare un auto-Hash vivificando (come te sembrano avere in PHP) in questo modo:

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

Si noti che questa sorta di auto-vivificazione può essere 'pericoloso' se si accede a chiavi che non lo fanno esistere, in quanto li crea per voi:

p hash["does this exist?"] 
#=> {} 

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

è comunque possibile utilizzare il vivificante default_proc senza colpire questo pericolo se si utilizza key? per verificare la chiave prima:

val = hash["OH NOES"] if hash.key?("OH NOES") 
#=> nil 

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

FWIW, l'errore che si stanno ottenendo dice "Hey, si mette [] alla ricerca di qualcosa che ha valutato a nil, e nil non avere un metodo []." In particolare, il codice ...

sorted_pois[point.file_type.to_sym] 

valutati per nil (perché l'hash non ha ancora un valore per questa chiave) e poi ha tentato di chiedere

nil[point.file.to_sym] 
+1

Così esperto ... – texasbruce

+0

+1 Nizza! (Anche se è un po 'schiacciante per un principiante di Ruby.) –

+0

@Phrogz, grazie per aver trovato il tempo di spiegarmelo. Sto davvero cominciando ad apprezzare Ruby, ma amico, è difficile! Questo rende ovvio che ho un po 'più di lettura da fare :) –

2

Il problema evidente con l'esempio di cui sopra è che gli hash nidificati e gli array si tenta di utilizzare don esiste Prova questo:

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 
+0

Questo è il modo "più sicuro" per creare manualmente i raggruppamenti; vedi la mia risposta per un modo meno sicuro ma più comodo. – Phrogz

+0

Phrogz, hai ragione, grazie per averlo notato, l'ho risolto. –

7

Potresti essere interessato a group_by.

utilizzo Esempio:

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"] } 
+1

+1 per avere ragione; puoi raccogliere più voti se mostri come viene utilizzato oltre il collegamento ai documenti. – Phrogz